美文网首页AndroidAndroid知识Java学习笔记
装饰器模式(从放弃到入门)

装饰器模式(从放弃到入门)

作者: DakerYi | 来源:发表于2016-10-22 13:50 被阅读401次

    前面介绍了两篇设计模式,策略模式和观察者模式,其实自己也是在学习阶段,感觉收益很大。所以想继续分享,把java中的23中设计模式都总结一遍,在以后才能在实践中灵活运用。感兴趣的童鞋可以看看前面分享的两篇:

    策略模式
    观察者模式

    前面两篇都是上来就是例子,需求,我想改变一下套路,今天先介绍装饰器的理论结构,再说例子。还是要再声明:例子来自于《HeadFirst 设计模式》,推荐大家看看原书,写得很浅显易懂。

    理论知识

    6.png

    实际问题

    今天的例子是一个 coffe 的例子,相信大家都去星巴克喝过咖啡,或者奶茶,我们在买coffe的时候,首先选择一个基本的coffe类型,比如 卡布奇洛,然后添加各种佐料:摩卡,豆浆,蒸奶等。基本类型是基础价,佐料又要宁外算钱。(真是聪明)

    现在当前的系统设计是:

    1.png

    本来想自己画UML图,发现自己画也一样,还损失了书上一些重要信息,所以直接盗图好了,大家不要介意。
    Beverage: 饮料,抽象类,getDescription() 返回描述(类型,佐料...),cost() 抽象方法,每种饮料话费不一样,所以子类自己去实现。
    然后派生了4中饮料的子类: HoseBlend, DarkRoast, Decaf, Espresso(尼玛,我都没喝过),分别实现 cost方法,返回价格。

    代码很简单,这里就不贴了,然后这个时候,如果饮料有100种,呵呵,这种设计类图就是这样:

    2.png

    类爆炸!这种设计,明显重用率太低,好吧,换种思路,我们把所有的佐料的放到公共父类中,让所有子类都拥有所有的佐料,只是在类中判断到底加没加,例如这样:

    3.png

    代码:

    Beverage .java

    public abstract class Beverage {
        protected String description;
        
        protected boolean milk;
        protected boolean soy;
        protected boolean mocha;
        protected boolean whip;
        
        public Beverage(){
            this.description = "unknown beverage";
        }
    
        public String getDescription() {
            return description;
        }
        
        public abstract double cost();
    }
    

    HoseBlend.java

    public class HoseBlend extends Beverage {
    
        public HoseBlend() {
            description = "HoseBlend";
    
            milk = true;
            soy = false;
            mocha = false;
            whip = false;
        }
    
        public double cost() {
            double money = 10.0;
            if (milk)
                money += 2.0;
            if (soy)
                money += 3.0;
            if (mocha)
                money += 3.0;
            if (whip)
                money += 2.0;
            return money;
        }
    }
    

    使用:

    Beverage hoseBlend = new HoseBlend();
    System.out.println(hoseBlend.getDescription());
    System.out.println(hoseBlend.cost());
    

    其他类省略了,当然这里写得不规范,每种佐料的价格应该写成常量,这里直接用了数字,这里不是重点。这样的类设计出来,我们可以用一段话来描述:一个抽象的Beverage类,里面包含了很多佐料,子类决定是否添加这些佐料,并且计算初始价格和佐料价格。

    与前一种不同的是,这种更加规范,父类决定了所有的佐料和价格,只是你填不填加,自己决定。突然想到了一种更好的方法,为何不让Beverage去计算价格呢?

    Beverage.java

    public abstract class Beverage {
        protected String description;
        protected double money;
        
        public Beverage(){
            this.description = "unknown beverage";
            this.money = 10.0;
        }
    
        public String getDescription() {
            return description;
        }
        
        public double cost(){
            return money;
        }
    
        public void addMilk() {
            this.money += 2.0;
        }
        
        public void addSoy(){
            this.money += 3.0;
        }
        
        public void addMocha(){
            this.money += 3.0;
        }
        
        public void addWhip(){
            this.money += 2.0;
        }
    }
    

    添加4中 addXXX() 方法,然后计算money,将计算价格留给父类,子类只需要添加佐料:

    HoseBlend .java

    public class HoseBlend extends Beverage {
        public HoseBlend() {
            description = "HoseBlend";
            addMilk();
        }
    }
    

    感觉这样写重用可以更好,并且子类工作也更少了。呵呵,书上没写,自己瞎想的。但是上面的这种设计,当遇到添加一种佐料时,都必须修改Beverate类,在设计模式原则中有一个非常重要的原则,就是开闭原则:对扩展开放,对修改关闭。上面的几种设计都存在一定的问题。我们来看看今天的主角,装饰器模式,怎么来完成。

    装饰器模式

    先来看一张形象的图:

    4.png

    最里层的 DarkRoast 是饮料类型外面添加一层 Mocha, 再外层添加 Whip。最终cost() 就从最外层,一直调用到最里层,累加得到价格,呵呵,是不是有点像递归,对多态的递归,看看类结构:

    5.png

    当然这是在最初,我们类爆炸那个例子中的结构扩展的:
    Beverage依然是那个抽象类,依然有4种饮料继承与它。不同的是,多了一个 CondimentDecorator,继承于 Beverage,然后4种佐料都继承与 CondimentDecorator,并都包含一个 beverage 的引用,表示自己装饰的对象。

    好了,看看代码:

    Beverage .java 还是长这样

    public abstract class Beverage {
        protected String description;
        
        public Beverage(){
            this.description = "unknown beverage";
        }
    
        public String getDescription() {
            return description;
        }
        
        public abstract double cost();
    }
    

    HouseBlend .java 第一种饮料,最低10.0元

    public class HouseBlend extends Beverage {
        
        public HouseBlend(){
            description = "HoseBlend";
        }
    
        public double cost() {
            return 10.0;
        }
    }
    
    

    DarkRoast.java 第二种饮料,最低12.0元

    public class DarkRoast extends Beverage {
        
        public DarkRoast(){
            description = "DarkRoast";
        }
    
        public double cost() {
            return 12.0;
        }
    }
    

    CondimentDecorator .java 让子类都重写getDescription() 方法

    public abstract class CondimentDecorator extends Beverage {
        public abstract String getDescription();
    }
    

    Milk.java : 牛奶,每加一份2.0 元

    public class Milk extends CondimentDecorator {
    
        Beverage beverage;
    
        public Milk(Beverage beverage) {
            this.beverage = beverage;
        }
    
        public String getDescription() {
            return this.beverage.getDescription() + "," + "Milk";
        }
    
        public double cost() {
            return this.beverage.cost() + 2.0;
        }
    }
    

    Mocha.java 摩卡,每份3.0元

    public class Mocha extends CondimentDecorator {
    
        Beverage beverage;
    
        public Mocha(Beverage beverage) {
            this.beverage = beverage;
        }
    
        public String getDescription() {
            return this.beverage.getDescription() + "," + "Mocha";
        }
    
        public double cost() {
            return this.beverage.cost() + 3.0;
        }
    }
    

    Soy.java 酱油,每份3.0元

    public class Soy extends CondimentDecorator {
    
        Beverage beverage;
    
        public Soy(Beverage beverage) {
            this.beverage = beverage;
        }
    
        public String getDescription() {
            return this.beverage.getDescription() + "," + "Soy";
        }
    
        public double cost() {
            return this.beverage.cost() + 3.0;
        }
    }
    

    好了,看看我们怎么调用:

    public class Main {
        public static void main(String[] args) {
            Beverage b1 = new HouseBlend();
            System.out.println(b1.getDescription() + " $" + b1.cost());
            
            Beverage b2 = new DarkRoast();
            b2 = new Mocha(b2);
            b2 = new Soy(b2);
            b2 = new Milk(b2);
            System.out.println(b2.getDescription() + " $" + b2.cost());
        }
    }
    

    输出:

    HoseBlend $10.0
    DarkRoast,Mocha,Soy,Milk $20.0
    

    总结

    1. 解耦合了吧,饮料和佐料分开,想怎么加怎么加,如果要添加新饮料或者佐料,只需继续添加,而无需修改以前的结构。这就是对扩展开放,对修改关闭
    2. 我前面提到了一句话:递归多态,哈哈哈,自己瞎编的!为什么会出现这种结果,如果你对多态熟悉的话,就很好理解了,看上面代码的 b2:
    • 为什么 Milk 可以赋值给 Beverage , 因为 Milk 的父类继承于 Beverage
    • b2调用 cost() 是调用 Milk 的 cost() = 2.0+ this.beverage.cost(), this.beverage指向的是 上一个b2,及 Soy, Soy调用cost(), 及调用 Mocha.cost() + 3.0 ... , 知道最后调用 beverage 的 cost() , 是不是很像递归。

    再一句话概括装饰者模式吧:

    装饰者模式,就是装饰者(Docorator)需要继承与被装饰者(Component),并且持有被装饰者的引用(Component),从而可以通过复写,在原来 Component 的基础上对方法做一定的修饰。

    相关文章

      网友评论

      本文标题:装饰器模式(从放弃到入门)

      本文链接:https://www.haomeiwen.com/subject/aichuttx.html