美文网首页
设计模式之装饰对象模式

设计模式之装饰对象模式

作者: 零星瓢虫 | 来源:发表于2020-07-07 23:32 被阅读0次

    简介

    装饰者模式,简而言之,可以针对已经存在的行为基础进行装饰。在进行装饰后达到自己想要的实现的功能。

    同样,我们引入现实例子对装饰者模式进行层层分析。

    现在,假如我们开了一家咖啡点,想要做一套点咖啡的系统,系统主要是根据选择的各种原料进行计算咖啡的价格;

    直接进入设计代码环节吧。

    public abstract  class Coffee {
        public String descriprion;
        
        public String getDescription(){
            return descriprion;
        };
        
        public abstract int  cost();
    }
    
    
    public class KaBuQiNuo extends Coffee{
        
        @Override
        public String getDescription() {
            // TODO Auto-generated method stub
            descriprion = "kabuqinuo";
            return super.getDescription();
        }
    
        @Override
        public int cost() {
            // TODO Auto-generated method stub
            return 1000;
        }
    
    }
    
    public class JiaoTang extends Coffee{
        
        @Override
        public String getDescription() {
            // TODO Auto-generated method stub
            descriprion = "JiaoTang ";
            return super.getDescription();
        }
    
        @Override
        public int cost() {
            // TODO Auto-generated method stub
            return 2000;
        }
    
    }
    

    上面直接定义了 Coffee 基类,同时对应不同的类似于 KaBuQiNuo(卡布奇诺)、JiaoTang 咖啡种类直接继承父类并实现相应 Description 和 cost 方法即可,当我们需要计算的时候进行 Cost 叠加。

    上述写法看上去没有什么问题,但是当要对咖啡进行拓展的时候,我们需要加 YeGuo(椰果)、YanMai(燕麦) 之类的配料之后就要重新新建子类(YeGuoCoffee)并且进行实现cost方法,甚至如果我们要区分亚洲和美洲口味的 Coffee。最后组合起来子类就会越来越多,简直就是类的大集合大爆炸,这样肯定是不行的 。

    本着希望不需要太多类的原则,对基类 Coffee 进行优化,抽取基本原材料:

    public abstract  class Coffee {
        public String descriprion;
        
        public Milk milk;
        
        public YeGuo yeGuo;
        
        public YanMai yanmai;
        
        public String getDescription(){
            return this.descriprion;
        }
        
    
        public void setMiik(Milk milk){
            this.milk = milk;
        }
        
        public boolean hasMilk(){
            if(milk != null){
                return true;
            }
            return false;
        };
        
        public void setYeGuo(YeGuo yeGuo){
            this.yeGuo = yeGuo;
        }
        public boolean hasYeGuo(){
            if(yeGuo != null){
                return true;
            }
            return false;
            
        };
        
    
        public void setYanMai(YanMai yanmai){
            this.yanmai = yanmai;
        }
        
        public boolean hasYanMai(){
            if(yanmai != null){
                return true;
            }
            return false;
            
        };
        
        public int cost (){
            int cost = 0;
            if(hasMilk()){
                cost += 1000;
            }
            if(hasYeGuo()){
                cost += 2000;
            }
            if(hasYanMai()){
                cost += 3000;
            }
        }
        
    }
    

    对基类 Coffee 进行改造,将 Coffee 相关基本原材料放在基类中,并对应判断是否有相关原料,在子类中仅仅需要去实现 cost 在基类基础上加上新增加原材料的价格即可。

    
    public class ChinaCoffee extends Coffee{
        
        @Override
        public String getDescription() {
            // TODO Auto-generated method stub
            descriprion = "kabuqinuo";
            return super.getDescription();
        }
    
        @Override
        public int cost() {
            // TODO Auto-generated method stub
                return super.cost() + 50;
        }
    
    }
    
    

    Coffee 的基本材料固定在基类中进行相关处理,而 Coffee 的种类根据各种搭配则可能进行变化。针对变化的情况。子类只需要实现相应的 cost 方法即可。

    到这里,看上去这个点咖啡的系统已经不错了,如果我们新增了美式咖啡,只需要在子类对应增加相应价格。而不要对应再去关心是否有 YanMai 或者 YeGuo 等。

    但是再仔细想想,如果提出下面几个问题,这么系统是否还很方便呢?
    1 调整现有的 Coffee 的基本原料的价格(需要更改基类现有代码)
    2 增加 Coffee 的基本原料 (需要更改基类代码)
    3 假如需要推出南美口味 Coffee 新品,可能不再需要 Milk 原料了。子类却依然会继承到 hasMilk 方法。

    这样看来,系统仍然不够强壮。每次修改或者增加依然会修改到现有的代码。需要继续优化,使得这个系统尽量不去修改原有代码,而对外进行扩展。

    这里就涉及到设计模式的一个重要原则:

    类应该对扩展开发过,对修改关闭

    接下里正式进入到装饰者模式。

    把装饰者模式先按照案例进行拆分:

    1 先拿到一个 Coffee 的对象
    2 Milk 进行装饰
    3 YeGuo 进行装饰
    3 依次调用 cost 方法,依赖 委托方式 将调料的价格加上去

    类关系图.png

    类似于上述图中,进行一层层地把 Coffee 基类进行包装,并层层调用 cost 方法,最后进行累加。

    1 首先调用最外层 YeGuo 的 cost 方法。
    2 YeGuo 调用 Milk 的 cost 方法。
    3 Milk 调用 Coffee 的 cost 方法。
    4 Coffee 返回价格。
    5 Milk 在结果上加上自己的价格。
    6 YeGuo 在结果上加上自己的价格,返回总价格。

    分析完成步骤,接下来具体看看实现方式。

    先从 Coffee 类来写:

    public abstract  class Coffee {
        public String descriprion = "UnKnow Coffee";
        
        public String getDescription(){
            return descriprion;
        }
        
        public abstract double cost();
    }
    

    接下来实现一个装饰者的基类:

    public abstract class SimpleDecorator extends Coffee {
        public abstract String getDescription();
    }
    

    有了基类,可以实现一些地方的 Coffee 。

    public class ChinaCoffee extends Coffee {
        
        public ChinaCoffee() {
            // TODO Auto-generated constructor stub
            descriprion = "ChinaCoffee";
        }
    
        @Override
        public double cost() {
            // TODO Auto-generated method stub
            return 2.99;
        }
    
    }
    

    接下来,我们就用 Milk 装饰 Coffee。

    public class Milk extends SimpleDecorator{
        Coffee coffee;
        
        public Milk(Coffee coffee) {
            this.coffee = coffee;
        }
    
        @Override
        public String getDescription() {
            return coffee.getDescription() + ",Milk";
        }
    
        @Override
        public double cost() {
            return 3.99+ coffee.cost();
        }
    
    }
    

    再加一个 YeGuo 类进行装饰:

    public class YeGuo extends SimpleDecorator{
        Coffee coffee;
        
        public YeGuo (Coffee coffee) {
            this.coffee = coffee;
        }
    
        @Override
        public String getDescription() {
            return coffee.getDescription() + ",YeGuo ";
        }
    
        @Override
        public double cost() {
            return 4.99+ coffee.cost();
        }
    
    }
    

    装饰对象 Milk 实现了简单的装饰基类,同时在内部维护了 Coffee 的引用类型。这里维护的 Coffee 引用类型保证了我们的装饰效果层层进行调用。

    最后我们来看看装饰者模式的实现情况。

    public class Main {
        
        public static void main(String args []){
            Coffee coffee = new ChinaCoffee();
            System.out.println(coffee.getDescription()+"RMB" + coffee.cost());
            
            coffee = new Milk(coffee);
            System.out.println(coffee.getDescription()+"RMB" + coffee.cost());
            
            coffee = new YeGuo(coffee);
            System.out.println(coffee.getDescription()+"RMB" + coffee.cost());
        }
    
    }
    

    可以看到上述代码,先定了一杯 ChinaCoffee,然后直接用 Milk 和 YeGuo 进行装饰。 运行上述代码:

    ChinaCoffee RMB2.99
    ChinaCoffee,Milk RMB6.98
    ChinaCoffee,Milk,YeGuo  RMB11.97
    

    得到运行结果。

    结合 UML 图再看下装饰者模式:

    UML 图.png

    就这样通过装饰者模式,完成了一套稳定的 Coffee 点单系统。
    再回到上面提到的三个问题。
    1 修改价格,直接在 ChinaCoffee 或者对应装饰原料 Milk 中修改,不必修改基类;
    2 如果要新增原料,继承 SimpleDecorator 实现对应方法。
    3 不必在继承不必要的方法。

    同时看到,对内 cost 的修改进行了封闭,同时可以进行拓展新的品种 JapanCoffee 或者 新的原料 YanMai 等。

    Java 中其实也有装饰者模式的类,例如 InputStream 和它的相关子类。具体可自行在 Java 源码中进行查看。

    相关文章

      网友评论

          本文标题:设计模式之装饰对象模式

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