zoukankan      html  css  js  c++  java
  • 设计模式:灵活编程(装饰模式)

    装饰模式,英文叫Decorator Pattern,又叫装饰者模式。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

    在设计模式中,一直推荐多用组合,少用继承的结构。使用组合来处理对象的行为,可以在运行时动态的进行扩展。而继承是共享父类特性的一种简单的方法,但可能会使你将需要改变的特性硬编码到继承体系中,而这常会降低系统灵活性。

    问题

    将所有功能都寄托于继承体系上会导致类的增多,代码的冗余!

    首先我定义一个饮品 Drink 及其子类 Cappuccino :

    abstract class Drink
    {
        abstract function getPrice();
    }
    
    class Cappuccino extends Drink 
    {
        private $_price = 50;
        
        public function getPrice()
        {
            return $this->_price;
        }
    }

     getPrice 方法用于返回当前饮品的价格,对于单一价格来说,这种结构还不错。但是,如果我希望 Cappuccino 加糖和加牛奶都会加收金额,并且获取到加价后的价格怎么处理?有一个办法是从 Cappuccino 对象派生:

    class CappuccinoAddSugar extends Cappuccino
    {
        public function getPrice()
        {
            return parent::getPrice() + 2;
        }
    }
    
    class CappuccinoAddMilk extends Cappuccino
    {
        public function getPrice()
        {
            return parent::getPrice() + 4;
        }
    }

    这样就可以很轻松获得一个加糖的卡布奇诺价格:

    $price = new CappuccinoAddSugar();
    echo $price->getPrice(); // 52

     

    看到这也能看到这种结构的弊端,如果我们需要既加糖又加奶的价格呢?OK,可以再加一个派生类。但是,如果需要加蜂蜜的呢?既加蜂蜜又加牛奶的呢?...... 随着功能增多你会发现类成“爆炸式”的增长,而且代码结构都是重复的。

    实现

    装饰模式使用组合和委托而不是只使用继承来解决功能变化的问题。装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。通过使用不同的装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。

    我重写下上面的示例:

    abstract class Drink
    {
        abstract function getPrice();
    }
    
    class Cappuccino extends Drink
    {
        private $_price = 50;
    
        public function getPrice()
        {
            return $this->_price;
        }
    }
    
    abstract class DrinkDecorator extends Drink
    {
        protected $drink;
    
        public function __construct(Drink $drink)
        {
            $this->drink = $drink;
        }
    }

    这里我引入了一个新类 DrinkDecorator ,定义了以一个 Drink 对象为参数的构造方法,传入的对象被保存在 $drink 属性中,该属性被声明为 protected ,以便子类可以访问它。下面我重新定义 CappuccinoAddSugar 和 CappuccinoAddMilk 类。

    class CappuccinoAddSugarDecorator extends DrinkDecorator
    {
        public function getPrice()
        {
            return $this->drink->getPrice() + 2;
        }
    }
    
    class CappuccinoAddMilkDecorator extends DrinkDecorator
    {
        public function getPrice()
        {
            return $this->drink->getPrice() + 4;
        }
    }

    这些类都扩展自 DrinkDecorator 类,这意味着它们拥有指向 Drink 对象的引用。当 getPrice 方法被调用时,这些类都会先调用所引用的 Drink 对象的 getPrice 方法,然后执行自己特有的操作。

    通过像这样使用组合和委托,可以在运行时轻松地合并对象。

    // 基础价格
    $price = new Cappuccino();
    echo $price->getPrice(); // 50
    
    // 加糖价格
    $price = new CappuccinoAddSugarDecorator(new Cappuccino());
    echo $price->getPrice(); // 52
    
    // 加奶价格
    $price = new CappuccinoAddMilkDecorator(new Cappuccino());
    echo $price->getPrice(); // 54
    
    // 既加糖又加奶
    $price = new CappuccinoAddMilkDecorator(new CappuccinoAddSugarDecorator(new Cappuccino()));
    echo $price->getPrice(); // 56

    这样的模型极具扩展性。可以非常轻松地添加新的装饰器或者新的组件。通过使用大量的装饰器,可以在运行时创建极为灵活的结构。

    总结

    组合和继承通常都是同时使用的。装饰模式提供了更多的灵活性,但也提高了代码的复杂性。因为装饰对象作为子对象的包装,所以保持基类中的方法尽可能少是很重要的。如果一个基类具有大量特性,那么装饰对象不得不为它们包装的对象的所有 public 方法加上委托。你可以用一个抽象的装饰类来实现,不过这仍旧会带来耦合,并可能导致 bug 出现。感谢阅读,再会!

  • 相关阅读:
    ACM成长之路
    洛谷P1047 校门外的树
    洛谷P1046 陶陶摘苹果
    2017 ACM-ICPC 亚洲区(南宁赛区)网络赛 F题
    图论:POJ2186-Popular Cows (求强连通分量)
    DFS:POJ1562-Oil Deposits(求连通块个数)
    DFS:POJ3620-Avoid The Lakes(求最基本的联通块)
    map函数的应用:UVa156-Ananagrams
    set的应用:UVa10815-Andy's First Dictionary
    水题:UVa253-Cube painting
  • 原文地址:https://www.cnblogs.com/bndong/p/9198611.html
Copyright © 2011-2022 走看看