• 装饰模式Decorator


    装饰(Decorator)模式又名包装(Wrapper)模式。Decorator以对客户端透明的方式扩展对象的功能,是继承的一种代替方案。

    1.什么时候使用

    1. 需要动态的扩展一个类,这些扩展也可以动态的撤销,并保持原有类的静态定义的情况。
    2. 需要增加由一些基本功能排列组合贰产生的非常强大的功能,并使继承关系变得不实现,典型的Wrapper应用。

    模拟类图:

     

    在装饰模式中的各个角色有:

    • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
    • 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
    • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
    • 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任。

     看headFirst中星巴兹咖啡的使用

     

    写下星巴兹的代码

    先从Beverage类下手,这不需要改变星巴兹原始的设计。如下

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

    Beverage是一个抽象类,有两个方法:getDescription()及cost()。

    getDescription()已经在此实现了,但是cost()必须在子类中实现。

    Beverage很简单。让我们也来实现Condiment(调料)抽象类,也就是装饰者类吧:

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

    1.首先,必须让Condiment Decorator能够取代Beverage,所以将CondimentDecorator扩展自 Beverage 类。

    写饮料的代码

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

    //首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。
    public class Espresso extends Beverage {
    //为了要设置饮料的描述,我们写了一个构造器。记住,description实例变量继承自Beverage。
    public Espresso() {
    description = "Espresso";
    }
    //最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格$1.99返回即可。
    public double cost() {
    return 1.99;
    }
    }
    
    
    
    public class HouseBlend extends Beverage {
    public HouseBlend() {
    description = "House Blend Coffee";
    }
    //这是另一种饮料,做法和Espresso一样,只是把Espresso名称改为"House Blend Coffee",并返回正确的价钱$0.89。
    public double cost() {
    return .89;
    }
    }

    写调料代码

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

    //摩卡是一个装饰者,所以让它扩展自CondimentDecorator。
    public class Mocha extends CondimentDecorator {//别忘了,CondimentDecorator扩展自Beverage。
    Beverage beverage;
    public Mocha(Beverage beverage) {
    //要让Mocha能够引用一个Beverage,做法如下:(1)用一个实例变量记录饮料,也就是被装饰者。
    //(2)想办法让被装饰者(饮料)被记录到实例变量中。这里的做法是:把饮料当作构造器的参数,再由构造器将此饮料记录在实例变量中。
    this.beverage = beverage;
    }
    public String getDescription() {
    //我们希望叙述不只是描述饮料(例如“DarkRoast”),而是完整地连调料都描述出来(例如“DarkRoast, Mocha”)。
    //所以首先利用委托的做法,得到一个叙述,然后在其后加上附加的叙述(例如“Mocha”)。
    return beverage.getDescription() + ", Mocha";
    }
    public double cost() {
    //要计算带Mocha饮料的价钱。首先把调用委托给被装饰对象,以计算价钱,然后再加上Mocha的价钱,得到最后结果。
    return .20 + beverage.cost();
    }
    }
    public class StarbuzzCoffee {
    public static void main(String args[]) {
    Beverage beverage = new Espresso();
    System.out.println(beverage.getDescription()+ " $" + beverage.cost());//订一杯Espresso,不需要调料,打印出它的描述与价钱
    Beverage beverage2 = new DarkRoast();//制造出一个DarkRoast对象。
    beverage2 = new Mocha(beverage2);//用Mocha装饰它。
    beverage2 = new Mocha(beverage2);//用第二个Mocha装饰它。
    beverage2 = new Whip(beverage2);//用Whip装饰它。
    System.out.println(beverage2.getDescription()
    + " $" + beverage2.cost());
    Beverage beverage3 = new HouseBlend();
    //最后,再来一杯调料为豆浆、摩卡、奶泡的HouseBlend咖啡。
    beverage3 = new Soy(beverage3);
    beverage3 = new Mocha(beverage3);
    beverage3 = new Whip(beverage3);
    System.out.println(beverage3.getDescription()
    + " $" + beverage3.cost());
    }
    }

    结果

     

     java IO包中的Decorator模式

    JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。

      首先来看一段用来创建IO流的代码:

    try { 
     OutputStream out = new DataOutputStream(new FileOutputStream("test.txt")); 
    } catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    }

     这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用DataOutputStream封装了一个FileOutputStream。这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator。将代码改成如下,将会更容易理解:

    try { 
     OutputStream out = new FileOutputStream("test.txt"); 
     out = new DataOutputStream(out); 
    } catch(FileNotFoundException e) { 
     e.printStatckTrace(); 
    }

     由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:

     OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:

    public abstract class OutputStream implements Closeable, Flushable { 
     public abstract void write(int b) throws IOException; 
     ... 
    }

    它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。
    ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:

    public class ByteArrayOutputStream extends OutputStream { 
     protected byte buf[]; 
     protected int count; 
     public ByteArrayOutputStream() { 
      this(32); 
     } 
     public ByteArrayOutputStream(int size) { 
      if (size 〈 0) { 
       throw new IllegalArgumentException("Negative initial size: " + size); 
      } 
      buf = new byte[size]; 
     } 
     public synchronized void write(int b) { 
      int newcount = count + 1; 
      if (newcount 〉 buf.length) { 
       byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)]; 
       System.arraycopy(buf, 0, newbuf, 0, count); 
       buf = newbuf; 
      } 
      buf[count] = (byte)b; 
      count = newcount; 
     } 
     ... 
    }

    它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。

    接着来看一下FilterOutputStream,代码如下:

    public class FilterOutputStream extends OutputStream { 
     protected OutputStream out; 
     public FilterOutputStream(OutputStream out) { 
      this.out = out; 
     } 
     public void write(int b) throws IOException { 
      out.write(b); 
     } 
     ... 
    }

    同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建FilterOutputStream。由于out既可以是指向FilterOutputStream类型的引用,也可以是指向ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。

      BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:

    public class BufferedOutputStream extends FilterOutputStream { 
     ... 
     private void flushBuffer() throws IOException { 
      if (count 〉 0) { 
       out.write(buf, 0, count); 
       count = 0; 
      } 
     } 
     public synchronized void write(int b) throws IOException { 
      if (count 〉= buf.length) { 
       flushBuffer(); 
      } 
      buf[count++] = (byte)b; 
     } 
     ... 
    }

    并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。

      下面,将使用Decorator模式,为IO写一个新的输出流。
      自己写一个新的输出流

      了解了OutputStream及其子类的结构原理后,我们可以写一个新的输出流,来添加新的功能。这部分中将给出一个新的输出流的例子,它将过滤待输出语句中的空格符号。比如需要输出"java io OutputStream",则过滤后的输出为"javaioOutputStream"。以下为SkipSpaceOutputStream类的代码:

    import java.io.FilterOutputStream; 
    import java.io.IOException; 
    import java.io.OutputStream; 
    /** 
    * A new output stream, which will check the space character 
    * and won’t write it to the output stream. 
    * @author Magic 
    * 
    */ 
    public class SkipSpaceOutputStream extends FilterOutputStream { 
     public SkipSpaceOutputStream(OutputStream out) { 
      super(out); 
     } 
     /** 
     * Rewrite the method in the parent class, and 
     * skip the space character. 
     */ 
     public void write(int b) throws IOException{ 
      if(b!=’ ’){ 
       super.write(b); 
      } 
     } 
    }

    它从FilterOutputStream继承,并且重写了它的write(int b)方法。在write(int b)方法中首先对输入字符进行了检查,如果不是空格,则输出。

      以下是一个测试程序:

    import java.io.BufferedInputStream; 
    import java.io.DataInputStream; 
    import java.io.DataOutputStream; 
    import java.io.IOException; 
    import java.io.InputStream; 
    import java.io.OutputStream; 
    /** 
    * Test the SkipSpaceOutputStream. 
    * @author Magic 
    * 
    */ 
    public class Test { 
     public static void main(String[] args){ 
      byte[] buffer = new byte[1024]; 
    
      /** 
      * Create input stream from the standard input. 
      */ 
      InputStream in = new BufferedInputStream(new DataInputStream(System.in)); 
    
      /** 
      * write to the standard output. 
      */ 
      OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out)); 
    
      try { 
       System.out.println("Please input your words: "); 
       int n = in.read(buffer,0,buffer.length); 
       for(int i=0;i〈n;i++){ 
        out.write(buffer[i]); 
       } 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
    }

     执行以上测试程序,将要求用户在console窗口中输入信息,程序将过滤掉信息中的空格,并将最后的结果输出到console窗口。比如:

    Please input your words: 
    a b c d e f 
    abcdef

    总 结

      在java.io包中,不仅OutputStream用到了Decorator设计模式,InputStream,Reader,Writer等都用到了此模式。而作为一个灵活的,可扩展的类库,JDK中使用了大量的设计模式,比如在Swing包中的MVC模式,RMI中的Proxy模式等等。对于JDK中模式的研究不仅能加深对于模式的理解,而且还有利于更透彻的了解类库的结构和组成。

     

     Activity组件通过其父类ContextThemeWrapper和ContextWrapper的成员变量mBase来引用了一个ContextImpl对象,这样,Activity组件以后就可以通过这个ContextImpl对象来执行一些具体的操作,例如,启动Service组件、注册广播接收者和启动Content Provider组件等操作。同时,ContextImpl类又通过自己的成员变量mOuterContext来引用了与它关联的一个Activity组件,这样,ContextImpl类也可以将一些操作转发给Activity组件来处理。

  • 相关阅读:
    FRAM在智能电子式电表中的应用
    element UI实现动态生成多级表头
    小微信小程序开发相关网站
    响应式开发bootstrap
    媒体查询
    前端导出功能实现的两种方式
    watch监听对象遇坑
    vue中使用v-for时为什么要用到key?为什么不能用index作为key?
    vue中我改变了data中的一个值,但现在视图上没有实时更新,请问我怎么拿到更新后的值?
    python 链接数据库的模块
  • 原文地址:https://www.cnblogs.com/mingfeng002/p/3465791.html
Copyright © 2020-2023  润新知