美文网首页设计模式那些事儿
设计模式详解之装饰者模式

设计模式详解之装饰者模式

作者: CryFace | 来源:发表于2020-07-12 23:55 被阅读0次

    定义概念

    装饰者模式也是我们23种设计模式一个比较常见的设计模式,它的定义是:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类(继承)更为灵活。

    装饰模式主要是四个角色构成:

    Component抽象构件

    Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对
    象。(在装饰模式中,必然有一个最基本、最核心、最原始的接口或者抽象类充当Component抽象构件)

    ConcreteComponent具体组件

    ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现。

    Decorator装饰角色

    一般是一个抽象类,里面一定会有一个private变量指向Component抽象构件,用来装饰。

    ConcreteDecorator具体装饰角色

    继承我们的装饰角色,把原本最核心、最原始的、最基本的东西装饰成其他东西。

    咖啡例子

    如果只是上面这样介绍的话,很难理清里面抽象的关系。关于装饰者模式一个比较好的例子就是《HeadFirst设计模式》里面的咖啡例子,我们下面也会用代码来说明。

    假如我们的Beverage是一个饮料抽象类,下面是一些具体饮品的实现类。

    但是如果我们的客户要求在下面的饮品中加一些东西如牛奶,巧克力,坚果之类怎么办呢?我们难道又要继承一下,再出一种新的饮品?而且这些饮品里面仅仅只是加了一些调料品。那么最大的坏处就是我们的饮品单上又多了几页选择,我们的类关系也会如下:

    最好的解决办法就是用我们的装饰者模式来改造,我们需要再继承原始的抽象类生成一个装饰抽象类,然后再用子类去实现,这样我们就是在原来的的饮品上面加了调料,而不是重新做!最后的运用了装饰者模式后架构如下:

    我们以一杯奶泡深焙咖啡为例,需要做的是:

    1. 拿一个深焙咖啡(DarkRoast)对象
    2. 以摩卡(Mocha)对象装饰它
    3. 以奶泡(Whip)对象装饰它
    4. 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去

    我们具体呢用代码来实现:

    我们的饮品抽象类(Beverage)

    package decorator;
    
    public abstract class Beverage {
        
        protected String description = "Unknown Beverage";
    
        public String getDescription() {
            return description;
        }
    
        public abstract double getPrice();
    }
    

    我们的饮品装饰者抽象类(BeverageDecorator)

    package decorator;
    
    /**
     * 饮料装饰者抽象类
     */
    public abstract class BeverageDecorator extends Beverage {
    
        //所有具体的调料装饰者都必须要实现这个方法,因为我们所要的是一个具体的描述
        public  abstract String getDescription();
    
    }
    

    然后就是具体一些饮品的实现,比如我们以上面深焙咖啡为例子(DarkRoast)

    package decorator;
    
    //饮品抽象的实现,深焙咖啡
    public class DarkRoast extends Beverage {
        
        public DarkRoast(){
            description = "深焙咖啡";
        }
        
        @Override
        public double getPrice() {
            return 20;  //我们假设这杯饮品值20块钱
        }
    }
    

    我们去实现具体的饮品调料实现类,摩卡(Mocha)和奶泡(Whip)之类的

    package decorator;
    
    public class Mocha extends BeverageDecorator {
    
        //通过组合的方式引入我们具体要被装饰的饮品
        private Beverage beverage;
    
        public Mocha(Beverage beverage) {
            this.beverage = beverage;
        }
    
        @Override
        public String getDescription() {
            return  beverage.getDescription() + ",加摩卡";
        }
    
        @Override
        public double getPrice() {
            return beverage.getPrice() + 5; //那么这杯被装饰的饮品的价格就是我们原来饮品的价格加上我们的调料
        }
    }
    
    package decorator;
    
    public class Whip extends BeverageDecorator {
    
        //通过组合的方式引入我们具体要被装饰的饮品
        private Beverage beverage;
        
        public Whip(Beverage beverage) {
            this.beverage = beverage;
        }
    
        @Override
        public String getDescription() {
            return  beverage.getDescription() + ",加奶泡";
        }
    
        @Override
        public double getPrice() {
            return beverage.getPrice() + 5; //那么这杯被装饰的饮品的价格就是我们原来饮品的价格加上我们的调料
        }
    }
    

    我们在Test类中实例一个具体的饮品,并加入调料,可以得到最后的结果

    package decorator;
    
    public class Test {
        public static void main(String[] args) {
            //先做一杯深焙咖啡出来
            Beverage darkRoast = new DarkRoast();
            //对我们的咖啡添加摩卡
            darkRoast = new Mocha(darkRoast);
            //对我们的咖啡添加奶泡
            darkRoast = new Whip(darkRoast);
            //输出我们的饮品名字和价钱
            System.out.println("我们的饮品名字是:"+ darkRoast.getDescription());
            System.out.println("我们的饮品价格是:"+ darkRoast.getPrice());
        }
    }
    

    运行结果

    我们的饮品名字是:深焙咖啡,加摩卡,加奶泡
    我们的饮品价格是:30.0
    
    Process finished with exit code 0
    

    可以看到我们在计算价格和名字是像剥洋葱一样一层一层往下剥去调用我们的被装饰的类然后去增加我们的饮品名字和价格,然后最后向上返回了我们的结果。

    但是具体使用的装饰者模式可能要比我的例子要复杂,如果是像我这样比较简单的例子,我们可以我定义的装饰者抽象类好像没什么作用,确实,在这里如果省掉也是可以运行的。但是如果在我们要大量的装饰者的时候是一定要加上的,不然全都继承最原始的抽象类,和我们最开始直接添加新品有什么差别?你细品

    I/O(真实的装饰者模式)

    上面我们也只是用例子来说明了装饰者模式,但其实在我们Java中装饰者就有被用到,I/0就是非常典型的例子。仔细想想,我们在平时用的时候是不是一层套一层Stream,然后来使用,这便是我们在装饰它!

    比如下面这个例子

    inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("text.txt"))););
    

    我们像剥洋葱一样,一层一层剥开。其实从里面来看可以是一层一层被套上装饰

    我们的输入流有很多,具体的关系之间可以用下图来展示:

    虽然这里我们是以输入流来展示,但其实这与输出流差异不大。我们可以看到Java帮我们装饰了各种输入流供我们使用,这里只能说明装饰者模式,不能具体展开介绍各个具体装饰者的作用,因为这又是一大堆的知识。

    总结

    总结的话,我们的装饰者模式可以动态地将责任附加到对象上。想要扩展功能,装饰者提供有别于继承的另一种选择。

    优点

    总结一下装饰者模式的优点

    • 装饰者类和被装饰者类可以独立发展,而不会相互耦合。很符合我们的单一职责原则!
    • 装饰模式是代替继承的一个强有力的方案。毕竟我们不然套了多少层,最后返回的对象还是我们最初的抽象类!继承是静态的附加责任,装饰者则是动态的附加责任。
    • 可以动态扩展一个实现类的功能。具体的就是我们装饰的功能!

    总结一下装饰者模式的缺点

    如果套了多层装饰的话,那么整个被装饰的对象就会显得比较复杂。因为万一里面出了问题,我们需要剥洋葱一样,剥了很多层才发现是里面出现了问题,不好处理而且还需要维护大量的代码。

    应用场景

    • 需要扩展一个类的功能,或给一个类增加附加功能。
    • 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
    • 需要为一批的兄弟类进行改装或加装功能,当然是首选装饰模式。

    参考资料

    HeadFirst设计模式

    设计模式之禅(第二版)

    相关文章

      网友评论

        本文标题:设计模式详解之装饰者模式

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