zoukankan      html  css  js  c++  java
  • 装饰者模式

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

    这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能

    意图:动态地给一个对象添加一些额外的功能。就增加功能来说,装饰器模式相比生成子类更为灵活。

    主要解决为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀

     关键:讲原始对象作为一个参数传递给装饰者。装饰者对其进行扩展

    我曾经以为应该用继承处理一切。后来领教到运行时扩展,远比编译时期的继承威力大。
    本章可以称为“给爱用继承的人一个全新的设计眼界”。我们即将再度探讨典型的继承滥用问题。在本章将会学到如何使用对象组合的方式,做到在运行时装饰类。一旦你熟悉了装饰的技巧,你讲能在不修改任何底层代码的情况下,给你的(或别人的)对象赋予新的职责。
    新的例题:星巴兹是以扩张速度最快而闻名的咖啡连锁店。因为扩张速度实在太快,他们最北更新订单系统,以合乎他们的饮料供应要求。
    他们原来的类设计是这样:

    星巴兹
    购买咖啡时,也可以要求子啊其中加入各种调料,例如:蒸奶,豆浆,摩卡或覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。
    这是他们的第一个尝试:
    第一个尝试
    很明显,星巴兹为自己制造了一个维护噩梦,如果牛奶的价钱上扬,怎么办?新增一种焦糖调料风味时,怎么办?
    我们已经了解到利用组合和委托可以在运行时具有继承行为的效果。
    利用集成设计子类的行为,实在编译时静态决定的,而且所有的子类都会继承到相同的行为,然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态的进行扩展。
    我们可以利用此技巧把多个新职责,甚至是设计超类时还没有想到的职责加在对象上,而且,可以不用修改原来的代码。
    可以看出:
    开放-关闭原则:类应该对扩展开放,对修改关闭。

    认识装饰者模式:
    我们已经了解利用继承无法完全解决问题,在星巴兹遇到的问题有:类数量爆炸、设计死板、以及基类加入的新功能并不适用于所有的子类。
    所以在这里我们要以饮料为主体,然后在运行时以调料来“装饰”饮料。那么。要做的是:
    1:拿一个深焙咖啡(DarkRoast)对象。
    2:以摩卡(Mocha)对象装饰它。
    3:以奶泡(Whip)对象装饰它。
    4:调用cost()方法,并依赖委托(delegate)将调料的价钱加上去。
    但是如何“装饰”一个对象,而“委托”又要如何与此搭配使用?(提示:把装饰者对象当成“包装者”)
    以装饰者构造饮料订单
    1:以DarkRoast对象开始。DarkRoast继承自Beverage,且有一个用来计算饮料价钱的cost()方法。
    2:顾客想要摩卡(Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包(wrap)起来(将DarkRoast对象传入Mocha对象,return 0.20+beverage.cost(),0.2是Mocha价格。Mocha对象是一个装饰者,他的类型“反应”了它所装饰的对象(本例中就是Beverage)。所谓的“反应”,指的就是两者类型一致。
    3:顾客也想要奶泡(Whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包装起来。别忘了,DarkRoast继承自Beverage,并有一个cost()方法,用来计算饮料价钱。所以,被Mocha和Whip包起来的DarkRoast对象仍然可以具有DarkRoast的一切行为,包括调用它的cost()方法。
    4:现在,该是顾客算钱的时候了,通过调用最外圈装饰者(Whip)的cost()就可以办得到。Whip的cost()会先委托它装饰的对象(也就是Mocha)计算出价钱,然后再加上奶泡的价钱。


    到此。我们知道了:
    1.装饰者和被装饰对象有相同的超类型。
    2.你可以用一个或多个装饰者包装一个对象。
    3.几人装饰者和被装饰者对象有相同的超类型,所有在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
    4.装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。(关键点)
    5.对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
    定义装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
    看看它的类图:
    装饰者类图
    装饰者我们的饮料
    让星巴兹饮料也能符合此框架
    星巴兹装饰者类图
    在继承和组合之间,观念有一些混淆,下面有一组问答:
    问:看看类图。CondimentDecorator扩展自Beverage类,这里用到了继承,不是吗?
    答:的确如此。这么做重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方,在这里,我们利用继承达到“类型匹配”,而不是利用继承获得“行为”。
    问:我知道为何装饰者需要和被装饰者(即被包装的组件)有相同的“接口”,因为装饰者必须能取代被装饰者,但是行为又是从哪里来的?
    答:当我们将装饰者与组件组合时,就是在加入新的行为,所得到的新行为,并不是集成自超类,而是由组合对象得到的。
    问:继承Beverage抽象类,是为了有正确的类型,而不是集成它的行为,行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。
    答:正是如此。
    问:如果我们西药继承的是component类型,为什么不Beverage类设计成一个接口,而是设计成一个抽象类呢?
    答:当初我们从星巴兹拿到这个程序时,Beverage已经是一个抽象类。通常装饰者模式是采用抽象类,但是在java中可以使用接口,通常都努力避免修改现有的代码,所以,如果抽象类运作得好好的,还是别去修改它。
    写下星巴兹的代码
    先从Beverage类下手:

     abstract class Beverage{
         String description="Unknown Beverage";
         public String getDescription(){
             return description;
         }
         public abstract double cost();
    }

    让我们也来实现Condiment(调料)抽象类,也就是装饰者类:

    //首先必须让CondimentDecorator能够取代Beverage,所以将CondimentDecorator扩展自Beverage
    abstract class CondimentDecorator extends Beverage{
       //所有的调料装饰者都必须重新实现getDescription方法,稍后我们会解释
        @Override
        public abstract String getDescription();
    }

    写饮料的代码
    现在,已经有了基类,让我们开始实现一些饮料吧,先从浓缩咖啡(Espresso)开始,别忘了,我们需要为具体的饮料设置描述,而且还必须实现cost()方法。

    //首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。
    class Espresso extends  Beverage{
        //为了要设置饮料的描述,我们写了一个构造器,description实例变量继承自Beverage
        public Espresso(){
            description="Espresso";
        }
        //最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格返回即可
        @Override
        public double cost() {
            return 1.99;
        }
    }
    
    class HouseBlend extends  Beverage{
        public HouseBlend(){
            description="House Blend Coffee";
        }
        @Override
        public double cost() {
            return 0.89;
        }
    }
    
    class DarkRoast extends  Beverage{
        public DarkRoast(){
            description="DarkRoast";
        }
        @Override
        public double cost() {
            return 0.99;
        }
    }
    
    class Decaf extends  Beverage{
        public Decaf(){
            description="Decaf";
        }
        @Override
        public double cost() {
            return 1.05;
        }
    }

    写调料代码
    如果你回头看看装饰者模式的类图,将发现我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(CondimentDecorator)。现在, 我们就来实现具体装饰者,先从摩卡下手:

    //摩卡是一个装饰者,所以让它扩展自CondimentDecorator。别忘了,CondimentDecorator扩展自Beverage
    class Mocha extends  CondimentDecorator{
    
        Beverage beverage;
        public Mocha(Beverage beverage){
            this.beverage=beverage;
        }
        //返回加入调料后的描述
        @Override
        public String getDescription() {
            return beverage.getDescription()+",Mocha";
        }
        //返回装饰后的价格
        @Override
        public double cost() {
            return 0.20+beverage.cost();
        }
    }
    
    class Soy extends  CondimentDecorator{
    
        Beverage beverage;
        public Soy(Beverage beverage){
            this.beverage=beverage;
        }
    
        @Override
        public String getDescription() {
            return beverage.getDescription()+",Soy";
        }
    
        @Override
        public double cost() {
            return 0.15+beverage.cost();
        }
    }
    
    class Whip extends  CondimentDecorator{
    
        Beverage beverage;
        public Whip(Beverage beverage){
            this.beverage=beverage;
        }
    
        @Override
        public String getDescription() {
            return beverage.getDescription()+",Whip";
        }
    
        @Override
        public double cost() {
            return 0.10+beverage.cost();
        }
    }
    这里用来下订单的一些测试代码:
    class StarbuzzCoffee{
        public static  void main(String arg[]){
            Beverage beverage=new Espresso();
            beverage=new Mocha(beverage);
            beverage=new Whip(beverage);
    
            System.out.println(beverage.getDescription()+"$"+beverage.cost());
        }
    }

    真实世界的装饰者:Java I/O
    java.io包内的类太多,如果你已经知道装饰者模式,这些I/O相关的类对你来说应该更有意义,因为其中许多类都是装饰者。下面是一个典型的对象集合:
    装饰者I/O
    装饰java.io类
    装饰java.io类

    编写自己的Java I/O装饰者
    编写一个装饰者,把输入流内的所有大写字符转换成小写.

    class LowerCaseInputStream extends FilterInputStream{
    
        protected LowerCaseInputStream(InputStream in) {
            super(in);
        }
    
        @Override
        public int read() throws IOException {
            int c=super.read();
            return (c==-1?c:Character.toLowerCase(c));
        }
    
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int result=super.read(b,off,len);
            for(int i=off;i<off+result;i++){
                b[i]= (byte) Character.toLowerCase(b[i]);
            }
            return result;
        }
    }
    

     装饰者有能力为设计注入弹性,但是也有不好的地方,又是后会在设计中加入大量的小类,会导致别人不容易了解我的设计方式。拿Java I/O库来说,在人们第一次接触的时候无法轻易地理解,但是认识到这些类都是用来包装InputStream的,一切就简单多了。
    采用装饰者在实例化组件时,将增加代码的复杂度,一旦使用装饰者模式,不只需要实例化组件,还要把此组件包装进装饰者中,天知道有多少个。
    但是后面介绍的工厂模式和生成器模式,他们对这个问题有很大的帮助。

    https://www.baidu.com/link?url=y6yd8aiIwkbp1eAz84z8hbvDkJHyO4TOIMscWsogWGZLqYGAH7e80n8KsoEmaaW8&wd=&eqid=91343f3700005c10000000045ba1b405

    http://www.runoob.com/design-pattern/decorator-pattern.html

  • 相关阅读:
    [20180317]12c TABLE ACCESS BY INDEX ROWID BATCHED3.txt
    模态登录
    javascript unshift()和shift()
    登录拦截功能
    springmvc 拦截器的使用小结
    handsontable 和 echarts都定义了require方法,初始化时冲突了,怎么办?
    eclipse缓慢了么?
    springmvc处理日期格式
    hibernate设置了not-null 为什么无效?
    java 和 javascript CryptoJS 进行HmacSHA1加密
  • 原文地址:https://www.cnblogs.com/twoheads/p/9669721.html
Copyright © 2011-2022 走看看