• 设计模式之装饰者模式


    装饰者模式

     
       我曾经以为男子汗应该用继承处理一切。后来领教到运行时扩展,远比编译时期的继承威力大。
      本章我称为“给爱用继承的人一个全新的设计眼界”。
     
    欢迎来到星巴克咖啡
     starbuck原先的订单系统如下:

    购买咖啡时,可以要求加入各种调料,,starbuck会根据所加入的调料收取不同的费用,所以,starbuck订单系统必须考虑这个问题。
     这是他们原先的设计:

    这简直就是“类爆炸”。class explosion。

      利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展

    笨透了,干嘛设计这么多类,利用实例变量和继承,就可以追踪这些变量啊!;好吧!就来试试。先从Beverage类下手,加上实例变量代表是否加上调料(牛奶,豆浆等)。

    定义了父类后,加入子类,超类cost()将计算所有调料的价钱,而子类覆盖过的cost()会扩展超类的功能,把指定的饮料类型的价钱也加进来。

    伪代码如下:
    public class Beverage {
    //为milkCost,soyCost等增加实例变量
    //为milk soy 声明getter和setter方法
    public double cost()
    {
         float condimentCost=0.0;
         if(hasMilk()){
              condimentCost+=milkCost;
              }
        if(hasSoy()){
              condimetnCost+=soyCost;
              }
         .....
         return condimetnCost;
         
    }
    
    public class DarkRoast extends Beverage{
         public DarkRoast(){
         description="most dark roast";
         }
         public double cost(){
         return 1.99+super.cost();
         }
    }
    这个设计怎么样? 
    当哪些需求或因素改变时会影响这个设计?
     
    调料价钱的改变会使更改现有代码。
    一旦出现新的调料,我们就需要加上新的方法,并改变超类中的cost()方法。
    以后可能开发新的饮料,对这些饮料而言(例如,冰茶),某些调料可能并不合适,但在这个设计方式中,tea子类仍将继承那些不适合的方法,例如:hasWhip();
    万一顾客想要双倍摩卡咖啡,怎么办?
    。。。
    这些问题是很糟糕的。
    开发关闭原则
      类应该对外扩展开放,对修改关闭。
     
    我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可以搭配新的行为,如能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
     

    认识装饰者模式

     
    我们已经了解到利用继承无法完全解决问题,在starbuck遇到的问题有:类数量爆炸,设计死板,以及基类加入的新功能并不适用所有的子类。
     
     在这里,要采用不一样的做法:我们要以饮料为主体,然后在运行时以调料来“装饰”decorate饮料。比方说,如果顾客想要摩卡和奶泡深培咖啡,那么要做的是:
     
    1.拿一个深培(DarkRoast)对象
    2.以摩卡(Mocha)对象装饰它
    3.以奶泡(Whip)对象装饰它
    4.调用cost()方法,并依赖委托(delegate)将调料的价格加上去。
     
    好了,但是如何”装饰“一个对象,而”委托“又要如何与此搭配使用呢?给一个暗示:把装饰者对象当成”包装者“,让我们来看看这是如何工作的:
    以装饰者构造饮料订单:

    好了,这是目前所知道的一切:
     
    装饰者和被装饰对象有相同的超类型
    你可以用一个或多个装饰者包装一个对象
    既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装)的场合,可以用装饰过的对象代替他。
    装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,已达到特定的目的。
    对象可以在任何时候被装饰,可以在运行时动态地,不限量地用你喜欢的装饰者来装饰对象。
     

    定义装饰者模式:

     装饰者模式 动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
     

    装饰我们的对象

     装饰者可以增加一些新方法,新行为是通过在旧行为前面或后面做一些计算来添加的。

    好吧,让starbuck也能符合我们的框架:

    代码如下:

    public abstract class Beverage 
    {
        String description="unknown beverage";
        public String getDescription()
        {
            return description;
            
        }
        
        public abstract double cost();
         
    
    }
    public abstract class CondimentDecorator extends Beverage
    {
        public abstract String getDescription();
        
    
    }
    public class HouseBlend extends Beverage {
        public HouseBlend() {
            description = "House Blend Coffee";
        }
     
        public double cost() {
            return .89;
        }
    }
    public class HouseBlend extends Beverage {
        public HouseBlend() {
            description = "House Blend Coffee";
        }
     
        public double cost() {
            return .89;
        }
    }

    可以自行添加其他的饮料类,做法一样。

    写调料代码。

    我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlend),也有了抽象装饰者(condimentDecroator),现在,我们来实现具体装饰者。

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

    订单的测试代码如下:

    public class StarbuzzCoffee {
     
        public static void main(String args[]) {
            Beverage beverage = new Espresso();
            System.out.println(beverage.getDescription() 
                    + " $" + beverage.cost());
     
            Beverage beverage2 = new DarkRoast();
            beverage2 = new Mocha(beverage2);
            beverage2 = new Mocha(beverage2);
            beverage2 = new Whip(beverage2);
            System.out.println(beverage2.getDescription() 
                    + " $" + beverage2.cost());
     
            Beverage beverage3 = new HouseBlend();
            beverage3 = new Soy(beverage3);
            beverage3 = new Mocha(beverage3);
            beverage3 = new Whip(beverage3);
            System.out.println(beverage3.getDescription() 
                    + " $" + beverage3.cost());
        }
    }
    输出:

    % java StarbuzzCoffee
    Espresso $1.99
    Dark Roast Coffee, Mocha, Mocha, Whip $1.49
    House Blend Coffee, Soy, Mocha, Whip $1.34

    问:装饰者知道这一连串装饰链条中其他装饰者的存在吗?比方说,我想要让getDescription输出whip,Double Mocha,而不是

    Mochar Whip Mochar ,这需要最外圈的装饰者知道有哪些装饰者牵涉其中了。

     装饰者该做的是,就是添加行为到被包装的对象上,当需要窥视装饰者链中的每一个装饰者时,就超出他们的天赋了。但是,并不是做不到,可以写一个CondimentPrettyPrint装饰者,然后把whip,Double Mocha 变成

    Mochar Whip Mochar ,如果能把getDescription的返回值变成ArrayList类型,让每个调料名称独立开来,那么这个函数更加容易写。

    真实世界的装饰者:java i/o:

    java包内的类太多了,其中许多类都是装饰者。下面是一个典型的对象几乎。用装饰者将功能结合起来,以读取文件数据。

    BufferedInputStream及LineNumberInputStream都扩展自FilterInputStream,而FilterInputStream是一个抽象的装饰类。

    我们可以组合各种”输入“流装饰者来符合你的用途。

     你会发现”输出“流的设计也是一样。可能还会发现Reader/witer也是一样。

     但是java i/o也引出装饰者模式的一个缺点:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成困扰。

    package headfirst.decorator.io;
    
    import java.io.*;
    
    public class LowerCaseInputStream extends FilterInputStream {
    
        public LowerCaseInputStream(InputStream in) {
            super(in);
        }
     
        public int read() throws IOException {
            int c = super.read();
            return (c == -1 ? c : Character.toLowerCase((char)c));
        }
            
        public int read(byte[] b, int offset, int len) throws IOException {
            int result = super.read(b, offset, len);
            for (int i = offset; i < offset+result; i++) {
                b[i] = (byte)Character.toLowerCase((char)b[i]);
            }
            return result;
        }
    }
    package headfirst.decorator.io;
    
    import java.io.*;
    
    public class InputTest {
        public static void main(String[] args) throws IOException {
            int c;
    
            try {
                InputStream in = 
                    new LowerCaseInputStream(
                        new BufferedInputStream(
                            new FileInputStream("test.txt")));
    
                while((c = in.read()) >= 0) {
                    System.out.print((char)c);
                }
    
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    要点:

    继承属于扩展形式之一,但不见得是达到弹性设计的最佳方法
    组合和委托可用于在运行时动态地加上新的行为
    装饰者模式意味着一群装饰者类,这些类用来包装具体组件
    装饰者类反映出被装饰的组件类型(事实上,他们有相同的类型,都经过接口或继承实现)。
     
    装饰者会导致设计中出现许多的小对象,如果过度使用,让会程序变得很复杂
     

     自己写的装饰者demo:

    先构造一个基础类Horse,在构造一个装饰者HorseChild。重写run方法。

    public class Horse {
        protected int height;
        protected int weight;
        public Horse(int height,int weight)
        {
            this.height=height;
            this.weight=weight;
        }
        public Horse(Horse horse)
        {
            height=horse.height;
            weight=horse.weight;
        }
        public void run()
        {
            System.out.println("run fast");
        }

    HorseChild如下:

    public class HorseChild extends Horse{
        public HorseChild()
        {
            super(50,30);
        }
        public HorseChild(Horse horse)
        {
            
            super(horse);
            
        }
        public void run()
        {
            super.run();
            System.out.println("run fast more");
            
        }
    
    }

    public HorseChild(Horse horse) 遇到了很大的问题。最开始这么写:

    public HorseChild(Horse horse)
    {


    weight=horse.weight;
      height=horse.weight;
    }
    报错:Implicit super constructor Horse() is undefined. Must explicitly invoke another constructor

    跟c++一样,构造子类时都会先调用父类的构造函数,这里我们没有用super去调用父类中我们定义的构造函数,那么就应该去调用默认的构造函数。

    是java跟c++一样,一旦我们定义了自己的构造函数后,就不在产生默认的构造函数Horse(),所以我们必须显式定义不带参数的构造函数:

    public Horse()
    {
    }

    加上这个之后,就运行正常了。

    以下来自csdn帖子:

    class Aa {
    
           public Aa(String a,String b) {   }
    
    }
    
    public class Bb extends Aa {
    
        public Bb(String a,String b) {//隐掉下面那句话后在此构造函数处提示:Implicit super constructor Aa() is undefined. Must explicitly invoke another constructor,为什么会这样啊?
    
    //      super(a,b);
    
        }
    
        public static void main(String args []) {
    
            Aa a = new Aa("Hi","Tom");
    
            Aa b = new Bb("Hi","Bart");
    
        }
    
    }

    答:
    子类构造器:
    public Bb(String a,String b)  
    { }
    若没有明确调用super()或this(),则编译程序会自动帮它生成一个,即:
    public Bb(String a,String b)  
    { }

    等价于:

    public Bb(String a,String b)  
    {   
     super();//这是编译程序自动帮它生成
    }

    而在父类Aa中,又没有定义默认构造器,故报错.

    注:编译程序之所以会自动帮它生成super();,主要是确保:子类对象中所包含的父对象空间一定会先初始化.

    子类的构造方法上如果没有明确的调用父类的构造方法,它会默认地调用父类的不带参数的方法
    如果父类有定义带了参数的构造方法但是没有定义不带参数的构造方法,则系统不会默认为其创建个不带参数的构造方法
    这样子类调用的时候必然就出错咯。http://topic.csdn.net/u/20090224/17/7745d504-3d9e-46a1-98a8-7c21760cf5ea.html

     

     
  • 相关阅读:
    页面布局
    Vue学习指南
    《前端JavaScript重点》学习笔记 6-12
    复习3----作用域和闭包
    复习1-变量类型和计算
    复习2--js原型与原型链2
    慕课网《前端JavaScript面试技巧》学习笔记(2)-原型和原型链
    旋转图片
    UITextView添加行距
    YYKit之YYText
  • 原文地址:https://www.cnblogs.com/youxin/p/2678648.html
Copyright © 2020-2023  润新知