MyException - 我的异常网
当前位置:我的异常网» 设计模式 » headfirst设计模式(三)—装饰者模式

headfirst设计模式(三)—装饰者模式

www.MyException.Cn  网友分享于:2013-10-16  浏览:0次
headfirst设计模式(3)—装饰者模式

好久没写设计模式了,自从写了两篇之后,就放弃治疗了,主要还是工作太忙了啊(借口,都是借口),过完年以后一直填坑,填了好几个月,总算是稳定下来了,可以打打酱油了。

为什么又重新开始写设计模式呢?学习使我快乐啊(我装逼起来我自己都害怕),其实主要是最近填坑的时候看源代码有点晕,很多代码不知道他们为什么要那么写啊,好气啊

当时第二篇写完,其实就在准备第三篇了,但是,一直也没有写,看了好几遍,但是一直掌握不到精髓(其实现在也掌握不到),感觉挺模糊的,也就一直拖啊拖,拖延症晚期患者已经不用抢救了。。。

先来举个栗子

故事背景:星巴兹咖啡,由于快速扩展,他们现在的订单系统已经跟不上他们的饮料供应需求了,先看一下当前的设计

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {
    
    protected String description;//描述
    
    public abstract double cost();//消费金额
    
    public Beverage(String description){
        this.description = description;
    }

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

 

下面是各种咖啡,这里就只展示一种:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend(String description) {
        super(description);
    }

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

}

很简单的代码,咖啡继承饮料超类。

啥都不说了,先提需求:

1,购买咖啡时,要求可以在其中加入各种调料,例如:蒸奶,豆浆,摩卡...等等(产品每次给我们说的就是,具体的他们还没定好,能不能做成活的?就是随时可以扩展的那种。知道我为啥要来学设计模式了吗?会被玩死的,真的)

2,各种调料也会参与计价,而且每种调料的价格不一定相同

 

需求清楚了吧?不清楚?没关系,反正下周上线,不存在的,嘿嘿嘿

先来给出第一版实现(直接扩展超类):

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {
    
    protected String description;//描述
    
    public abstract double cost();//消费金额
    
    private boolean milk;//牛奶
    
    private boolean soy;//豆浆
    
    private boolean mocha;//摩卡
    
    private boolean whip;//奶泡
    
    //省略get set方法...
}

 

不就是下周上线吗?不就是要扩展吗?要多少,就在超类里面加就好了啊,然后再对每一种饮料进行处理,像这样:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend(String description) {
        super(description);
    }

    @Override
    public double cost() {
        
        double cost = 3.5;
        if(hasMilk()){
            cost += 0.5;
        }
        
        if(hasMocha()){
            cost += 0.6;
        }
        
        if(hasSoy()){
            cost += 0.3;
        }
        
        if(hasWhip()){
            cost += 0.4;
        }
        
        return cost;
    }

}

感觉每一种都要写这么多if好麻烦啊,不存在的,这种东西,抽出来嘛:

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {
    
    /**
     * 调料消费
     * @return
     */
    protected double flavourCost(){
        double cost = 0.0;
        if(hasMilk()){
            cost += 0.5;
        }
        if(hasMocha()){
            cost += 0.6;
        }
        if(hasSoy()){
            cost += 0.3;
        }
        if(hasWhip()){
            cost += 0.4;
        }
        return cost;
    }
        //...
}

子类:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend(String description) {
        super(description);
    }

    @Override
    public double cost() {
        return 3.5 + flavourCost();
    }

}

感觉也挺好的啊,每次要添加的时候,先去超类添加一个属性,然后在超类的 flavourCost()方法中,添加一段代码,以前的子类完全不用动,逻辑也是妥妥的,一切都是很OK的

但是,这样写至少有三处是不符合逻辑的:

1,所有的子类的cost方法都必须要加上一句 xxx + flavourCost(),不觉得写的次数太多了吗?一般一个方法写N次,那大部分最后会变成坑

2,超类的所有属性并不是每一个子类都能用上的

3,违背了开闭原则,每一次扩展虽然不用修改子类,但是却会去修改父类的属性,以及父类的flavourCost()方法

在现在的这个需求下,这些不合理都是体现不出来有什么问题的,但是,代码中不符合逻辑的东西早晚有一天会对整个模块的设计造成巨大的影响,两种情况除外:

1,你不干了(钱没给够或者我不开心毕竟程序员都是非常任性的),对模块的影响跟你没有半毛钱的关系

2,这个模块不扩展,不维护,或者扩展、维护还没有达到临界点(时间根据前期逻辑混乱程度成反比)

别问我怎么知道的,因为我TM还没走,所以就来学习来了,我擦,跑题了。。。

 

下面来聊聊刚学的正确姿势:

装饰者模式

先说超类 Beverage(不变,保持最最原始的样子):

/**
 * 饮料超类
 * @author Skysea
 *
 */
public abstract class Beverage {
    
    protected String description;//描述
    
    public abstract double cost();//消费金额

    public Beverage(String description){
        this.description = description;
    }

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

为什么不变?

要回答这个问题,首先要考虑,整个模块中,到底是什么东西一直变来变去?没错,就是调料啊。

一般变化的怎么处理?

抽离出来,把它和稳定的东西抽离出来,即保证了它的扩展性,又提高了代码的稳定性,遵循了开闭原则

所以为什么不变?1,它很稳定。2,它很稳定。没有3,我要逼死强迫症

接下来,贴一下它的子类代码,调整的地方:

    public HouseBlend(String description) {
        super(description);
    }

把 description放在构造函数内写死,为啥?你创建一个咖啡叫啥名字,难道还要客户端帮你想好?

让他去做也是可以的,但是有两个问题你要想清楚:

1,他知道你住哪里吗?

2,你走夜路吗?

卧槽,走远了走远了...

继续:

/**
 * 首选咖啡
 * @author Skysea
 *
 */
public class HouseBlend extends Beverage{

    public HouseBlend() {
        super("首选咖啡");
    }
    @Override
    public double cost() {
        return 3.5;
    }
}

为了保证最后的测试类可以正常的跑,我把其他的也写一些出来...

/**
 * 浓咖啡
 * @author Skysea
 *
 */
public class Espresso extends Beverage {

    public Espresso() {
        super("浓咖啡");
    }

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

}

 

/**
 * 焦炒咖啡
 * @author Skysea
 *
 */
public class DarkRoast extends Beverage{

    public DarkRoast() {
        super("焦炒咖啡");
    }

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

 

然后来到最重要的装饰超类CondimentDecorator (第一版):

/**
 * 调料装饰类
 * @author Skysea
 *
 */
public abstract class CondimentDecorator extends Beverage {
    
    protected Beverage beverage;

    public CondimentDecorator(String description) {
        super(description);
    }
    
    //重写cost方法
    @Override
    public String getDescription(){
        return this.beverage.getDescription() + ", " +this.description;
    }
    
    //直接实现cost方法
    public double cost(){
        return this.beverage.cost() + condimentCost();
    }
    
    protected abstract double condimentCost();//重新抽象一个调料的价格方法

}

 装饰子类(Mocha ):

/**
 * 调料:摩卡
 * @author Skysea
 *
 */
public class Mocha extends CondimentDecorator {
    public Mocha() {
        super("摩卡");
    }
    @Override
    protected double condimentCost() {
        return 0.6;
    }
}

是不是感觉很简单?所有的逻辑都在装饰超类里面处理好了,感觉妥妥的,没什么不对,而且其他的子类实现起来也是非常的轻松

上面的装饰类是我自己的第一版实现,写完之后,我发现,这个东西,和我们最初想要的装饰类有很大的区别:

不灵活

 现在我们来看看装饰者模式的定义:

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

想要应对越复杂的变化,那么就要给子类赋予越大的权限,让它接触越多的东西,只有这样才能做灵活,当然,灵活也是相对的,这个也是必须在实际项目中取舍

所以,上面的装饰类实现,根本无法满足当前需求所需要的灵活性,比如:使用摩卡的时候,所有的饮料满20减5块怎么玩?是不是感觉玩不动了?

问题在哪里呢?

    //直接实现cost方法
    public double cost(){
        return this.beverage.cost() + condimentCost();
    }

就是在这里,这个地方把cost方法定的太死板了,也不能说这样不对,只是,这样所有继承CondimentDecorator的其实只能算装饰者模式的特例,要实现这样的东西,就必须在CondimentDecorator与Beverage中间再抽象一层。

就像这样CondimentDecorator extends Decorator,Decorator extends Beverage,其他的调料继承 Decorator (咳,别听我乱BB)

 

讲讲正确的装饰类及实现吧:

/**
 * 调料装饰类
 * @author Skysea
 *
 */
public abstract class CondimentDecorator extends Beverage {
    
    protected Beverage beverage;
    
    public CondimentDecorator(Beverage beverage, String description){
        super(description);
        this.beverage = beverage;
    }
    
    //根据情况来觉得是不是要抽象出来让子类实现,原因和刚才抽象condimentCost()一样,分情况,没有什么是对不对的
    @Override
    public String getDescription(){
        return this.beverage.getDescription() + ", " +this.description;
    }

}

 

调料实现类:

/**
 * 调料:牛奶
 * @author Skysea
 *
 */
public class Milk extends CondimentDecorator{

    public Milk(Beverage beverage) {
        super(beverage, "牛奶");
    }

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

}
/**
 * 调料:摩卡
 * @author Skysea
 *
 */
public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage, "摩卡");
    }

    @Override
    public double cost() {
        return 0.6 + beverage.cost();
    }
    
}
/**
 * 调料:豆浆
 * @author Skysea
 *
 */
public class Soy extends CondimentDecorator{

    public Soy(Beverage beverage) {
        super(beverage, "豆浆");
    }

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

}

....

 

测试类:

/**
 * 装饰模式测试类
 * @author Skysea
 *
 */
public class Test {
    public static void main(String[] args) {
        //初始化浓咖啡
        Beverage beverage = new Espresso();//$2.5
        System.out.println(beverage.getDescription() + " $"+ beverage.cost());
        
        //初始化焦炒咖啡
        Beverage beverage2 = new DarkRoast();//$3.2
        //用调料来装饰它
        beverage2 = new Mocha(beverage2);//$0.6
        beverage2 = new Mocha(beverage2);//$0.6
        beverage2 = new Soy(beverage2);//$0.3 
        
        System.out.println(beverage2.getDescription() + " $"+ beverage2.cost());
        
        
        //初始化焦炒咖啡
        Beverage beverage3 = new HouseBlend();//3.5
        beverage3 = new Mocha(beverage3);//$0.6
        beverage3 = new Milk(beverage3);//$0.5
        beverage3 = new Soy(beverage3);//$0.3 
        
        System.out.println(beverage3.getDescription() + " $"+ beverage3.cost());
    }
}

运行结果:

为了证明我没有随便拿个假截图来骗你们,我特意把价格都标到测试类后面的

为啥后面那么长一串?这肯定不是我的锅啊,double的锅,推荐用BigDecimal来玩这个,当然也可以用long型,或者int都是可以的,实际项目也是这样

用什么主要看:

1,精度要求高不高

2,速度要求快不快

3,看产品会不会让你保留小数点2位和保留小数点3位换着来玩的情况(都是泪,不说了..)

 

文章评论

当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
中美印日四国程序员比较
中美印日四国程序员比较
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
Java程序员必看电影
Java程序员必看电影
编程语言是女人
编程语言是女人
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
每天工作4小时的程序员
每天工作4小时的程序员
程序员都该阅读的书
程序员都该阅读的书
代码女神横空出世
代码女神横空出世
那些争议最大的编程观点
那些争议最大的编程观点
程序员和编码员之间的区别
程序员和编码员之间的区别
10个调试和排错的小建议
10个调试和排错的小建议
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
我的丈夫是个程序员
我的丈夫是个程序员
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
老程序员的下场
老程序员的下场
 程序员的样子
程序员的样子
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
为什么程序员都是夜猫子
为什么程序员都是夜猫子
鲜为人知的编程真相
鲜为人知的编程真相
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
程序员应该关注的一些事儿
程序员应该关注的一些事儿
漫画:程序员的工作
漫画:程序员的工作
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
总结2014中国互联网十大段子
总结2014中国互联网十大段子
我是如何打败拖延症的
我是如何打败拖延症的
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
旅行,写作,编程
旅行,写作,编程
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
程序员的鄙视链
程序员的鄙视链
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有