嘿嘿嘿,你是不是很喜欢用继承呢?感觉没什么事情是一个“爸爸”类搞不定的,有的话就两个,快来跟我看看这个模式吧,它能让你“断奶”,给爱用继承的人一个全新的设计眼界。

直奔主题,你是否有听说过组合呢?你是否觉得继承往往有的时候没你想象的那么好用呢?如果有,你又是否尝试自己解决这个问题呢?让我们来看个例子~~~~

假设我们有家咖啡店,设计如下
饮料(Beverage)基类,有4个子类,分别是综合咖啡(HouseBlend),深焙咖啡 (DarkRoast),低咖啡因咖啡 (Decaf),(Espresso)浓缩咖啡,他们都是子类,继承了cost支付方法。
图片描述

嗯,看似目前没啥问题,假设我需要改变点需求呢?
喝咖啡可以加各种各样调料的,例如加牛奶,豆奶,摩卡,小饼干等等,而且每种饮料根据添加的调料不同还要再加对应的调料费用,那么这些使用继承的话。。。。。。恐怕会出现以下这样的情况。。
图片描述
想想以前这样的设计,是不是有种细思极恐的感觉。。

当然聪明一点的会这样设计,这里我就不自己画了,直接贴出来图例
图片描述
图片描述

其实这样的设计也非常有问题,跟上面"类爆炸"的设计没啥区别,父类和子类依赖性太高了,低耦合。
1:假设我在父类中添加了新的调料,那么所有子类都需要相应修改代码,加大了工作量。
2:在父类中改变cost方法,子类也需要跟着改变。。

总之,太滥用继承其实是给自己挖坑,继承往往有的时候不能设计出弹性和易扩展性的代码。这里就要说说和继承的“死对头”-->组合,而装饰者模式就充分运用了组合

设计原则一:类应该对扩展开放,对修改关闭。
注:这个原则刚开始听是感觉自相矛盾的,确实刚开始我也有这疑问,回过头看看观察者模式,通过加入新的“观察者”就相当于拓展开放了“主题”,而“主题”本身没有改变代码,对修改关闭了。不急请往下看。

依旧以咖啡店做例子
这里改变做法,以饮料为主体,然后用调料“装饰”饮料,
打个比方:如果顾客想要摩卡和奶泡深焙咖啡,要做的是:
1:拿一个深焙咖啡(HouseBlend)对象
2:以摩卡(Mocha)对象“装饰”它
3:以奶泡(Whip)对象“装饰”它
4:调用cost()方法,并依赖委托将调料价钱加上去
过程流程图如下(请多看几遍)
图片描述

装饰者模式定义:动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

放出装饰者模型的类图(重点看)
图片描述

然后下面是基于此模式下的咖啡店的类图
图片描述
是不是感觉这个设计焕然一新。
下面我要好好打造我的咖啡店了

饮料抽象类

package Beverage;
/**
 * 饮料抽象类
 * 被装饰者类
 * @author Joy
 *
 */
public abstract class Beverage {
    //饮料名称初始化
    String description="未知饮料";

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    //价钱方法抽象化,不需要具体实现
    public abstract double cost();
}

下面是四个咖啡类

package Beverage;

/**
 * 深焙咖啡 
 * 
 * @author Joy
 * 
 */
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = "深焙咖啡";
    }

    @Override
    public double cost() {
        return 0.99;
    }

}
package Beverage;

/**
 * 低咖啡因咖啡 
 * 
 * 
 * 
 */
public class Decaf extends Beverage {

    public Decaf() {
        description = "低咖啡因咖啡";
    }

    @Override
    public double cost() {
        return 1.05;
    }

}
package Beverage;
/**
 * 
 * 浓缩咖啡类
 * 
 *
 */

//继承饮料父类,因为它是饮料一种
public class Espresso extends Beverage {
    //写一个构造器,将饮料名称重定义
    public Espresso() {
        description="浓缩咖啡";
    }

    //最后计算浓缩咖啡价格,先不算调料价钱,一杯浓缩咖啡1.99
    @Override
    public double cost() {        
        return 1.99;
    }

}
    package Beverage;
/**
 * 综合咖啡类
 * 
 *
 */
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "综合咖啡";
    }

    @Override
    public double cost() {
        return 0.89;
    }

}

调料抽象类

package Condiment;

import Beverage.Beverage;

/**
 * 调料抽象类 装饰者类
 * 
 * @author Joy
 * 
 */
public abstract class CondimentDecorator extends Beverage {
    // 描述
    public abstract String getDescription();

}

下面是具体调料实现类

package Condiment;

import Beverage.Beverage;

/**
 * 摩卡类,装饰者
 * 
 * @author Joy
 * 
 */
// 摩卡是一个装饰者,让它继承装饰者父类
public class Mocha extends CondimentDecorator {

    // 设定饮料变量记录饮料,也就是被装饰者
    Beverage beverage;

    // 设置构造器将被装饰者(饮料)被记录到实例变量beverage中
    // 把饮料设置构造器参数,再有构造器将此记录到实例变量中
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    // 重写方法,将其饮料描述加上添加的调料
    @Override
    public String getDescription() {
        return beverage.getDescription() + ",加摩卡";
    }

    // 重写方法,将饮料价钱+调料价钱
    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }

}
package Condiment;

import Beverage.Beverage;

/**
 * 豆浆类,装饰者
 * 内容与前面摩卡一样
 * @author Joy
 *
 */
public class Soy extends CondimentDecorator {

    Beverage beverage;
    
    public Soy(Beverage beverage) {
    this.beverage=beverage;
    }
    
    @Override
    public String getDescription() {    
        return beverage.getDescription()+",加豆浆";
    }

    @Override
    public double cost() {
        
        return beverage.cost()+0.15;
    }

}
package Condiment;

import Beverage.Beverage;

/**
 * 奶泡类,装饰者 内容与前面摩卡一样
 * 
 * @author Joy
 * 
 */
public class Whip extends CondimentDecorator {
    Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ",加奶泡";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.1;
    }

}

测试类

package TestMain;

import java.math.BigDecimal;

import Beverage.Beverage;
import Beverage.DarkRoast;
import Beverage.Espresso;
import Beverage.HouseBlend;
import Condiment.Mocha;
import Condiment.Soy;
import Condiment.Whip;

public class TestMain {
    public static void main(String[] args) {
        //要一杯浓缩咖啡,什么调料都不加
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + "\t$" + beverage.cost());
        
        //要一杯深焙咖啡        
        Beverage beverage2=new DarkRoast();
        //加两份摩卡
        beverage2=new Mocha(beverage2);
        beverage2=new Mocha(beverage2);
        //加一份奶泡
        beverage2=new Whip(beverage2);
        System.out.println(beverage2.getDescription()+"\t$"+beverage2.cost());
        
        //要一杯加豆浆,加摩卡,加奶泡的综合咖啡
        Beverage beverage3=new HouseBlend();
        beverage3=new Whip(beverage3);
        beverage3=new Mocha(beverage3);
        beverage3=new Soy(beverage3);
        //这里对价钱保留两位小数四舍五入
        double money=beverage3.cost();
        BigDecimal b=new BigDecimal(money);
        double f1=b.setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
        System.out.println(beverage3.getDescription()+"\t$"+f1);
    }
}

效果图:
图片描述

要点:
1:继承属于扩展形式一种,但不见的是达到弹性设计的最佳方式,组合优于继承。
2:应该允许行为可以被拓展,而无需修改现有的代码。
3:装饰者模式意味着一群装饰者类,这些类用来包装具体组件。
4:装饰者类反映出被装饰组件类型。
5:可以使用无数个装饰者包装一个组件。
6:装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

装饰者模式结束,这个模式是个很有用的模式,可以说是提高代码素养的关键一步,模拟代码已经全部放出,注释也写的比较清楚,若有不理解欢迎留言。

感谢你看到这里,装饰者模式部分结束,本人文笔随便,若有不足或错误之处望给予指点,90度弯腰~很快我会发布下一个设计模式内容,生命不息,编程不止!

参考书籍:《Head First 设计模式》
参考网站:
Java中的继承与组合:http://www.importnew.com/12907.html


暖心先森
287 声望16 粉丝

生命不息,编程不止!