装饰着模式

  装饰着模式拥有一个设计非常巧妙的结构,它可以动态添加对象功能。在基础的设计原则中,有一条重要的设计准则叫合成/聚合复用原则。根据该原则的思想,代码复用应该尽可能使用委托,而不是使用继承。因为 继承是一种紧密耦合,任何父类的改动都会影响其子类,不利于系统维护。而委托则是松耦合,只要接口不变,委托类的改动并不会影响其上层对象。

  装饰着模式就充分运用了这种思想,通过委托机制,复用系统中的各个组件,在运行时,可以将这些功能组件进行叠加,从而构建一个“超级对象”,使其拥有所有这些组件的功能。而各个子功能模块,被很好地维护在各个组件的相关类中,拥有整洁的系统结构。

  装饰着模式这种结构可以很好地将功能最难喝性能组件进行分离,彼此互不影响,并在需要的时候,有机地结合起来。为了跟好地理解装饰着模式如何做到性能模块地分离,首先,需要对装饰着模式做一个总体对了解。

  装饰着模式的基本机构如下图:

 

  装饰者(Decorator)和被装饰者(ConcreteComponent)拥有相同的接口Component。

被装饰者通常是系统的核心组件,完成特定的功能目标。而装饰者则可以在被装饰者的方法前

后,加上特定的前置处理和后置处理,增强被装饰者的功能。

  装饰者模式的主要角色如表:

 

 

装饰者模式的一个经典案例就是对输出结果进行增强。比如, 现在需要将一结果通过HTML进行发布,那么首先就需要将内容转化为一个HTML头。同时,由于内容需要在网络上通过HTTP流传,故,还需要为其增加HTTP头。当然作为一个更复杂的情况,可能还要为其安置TCP头等。但作为一个实例,这里做简化处理。

  装饰者模式但核心思想在于:无需将所有的逻辑,即,核心内容构建、HTML文本构造和HTTP头生成等3个功能模块粘合在一起实现。通过装饰着模式,可以将他们分解为3个几乎完全独立的组件,并在使用时灵活地进行装配。为实现这个功能,可以使用如图所示结构:

 

 

  IPacketCreator即装饰接口,用于处理具体的内容。PacketBodyCreator是具体的组件,它的功能是构造要发布信息的核心内容,但是它不负责将其构造成一个格式工整、可直接发布的数据格式。PacketHTTPHeaderCreator负责对给定的内容加上HTTP头部,PacketHTMLHeaderCreator负责将给定的内容格式化成HTML文本。如上图所示,3个功能模块相对独立且分离,易于系统维护。

  IPacketCreator的实现很简单,他是一个但方法的接口:

public interface  IPacketCreator {
     public String handleContent(); //用于内容处理
}
  PacketBodyCreator 用于返回数据包的核心数据:
public class PacketBodyCreator implements  IpacketCreator {
     @Override
     public String handleContent() {
        return "Content of Packet";     //构造核心数据,但不包括格式    
     }
}
  PacketDecorator维护核心组件component对象,它负责告知其子类,其核心业务逻辑应该全权委托component完成,
自己仅仅是做增强处理。
public abstract class PacketDecorator implements IPacketCreator {
    IPacketCreator component;

    public PacketDecorator(IPacketCreator c) {
        component = c;
    }
}
  PacketHTMLHeaderCreator是具体的装饰器,它负责对核心发布的内容进行HTML格式化操作。
需要特别注意的是,他委托了具体组件component进行核心业务处理。
public  class  PacketHTMLHeaderCreator extends PacketDecorator {

    public PacketHTMLHeaderCreator(IPacketCreator c) {
        super(c);
    }

    @Override
    public String handleContent() {
        StringBuffer sb = new StringBuffer();
        sb.append("<html>");
        sb.append("<body>");
        sb.append(component.handleContent());
        sb.append("</body>");
        sb.append("</html>\n");
        return sb.toString();
    }
}
  PacketHTTPHeaderCreator 与 PacketHTMLHeaderCreator类似,但是它完成数据包HTTP头部的处理。
其余业务处理依然交由内部的component完成。
public class PacketHTTPHeaderCreator extends PacketDecorator {
    public PacketHTTPHeaderCreator(IPacketCreator c) {
        super(c);
    }
    
    @Override
    public String handleContent() {         //对给定数据加上HTTP头信息
        StringBuffer sb = new StringBuffer();
        sb.append("Cache-control:no-cache\n");
        sb.append("Date:Mon,31Dec201204:25:57GMT\n");
        sb.append(component.handleContent());
        return sb.toString();
    }
}
  对于装饰者模式,另一个值得关注的地方是它的使用方法。在本例中,通过层层构造和组装这些装饰者和被装饰者到一个对象中,
使其有机地结合在一起工作。
public class Main {
    public static void main(String[] args) {
        IPacketCreator pc = new PacketHTTPHeaderCreator(
                new PacketHTMLHeaderCreator(
                        new PacketBodyCreator()));
        System.out.println(pc.handleContent());
    }
}
  可以看到,通过装饰者的构造函数,将被装饰对象传入。本例中,共生成3个对象实例,作为核心组件的PacketBodyCreator ,
最先被构造,其次是PacketHTMLHeaderCreator,最后才是PacketHTTPHeaderCreator。

  这个顺序表示,首先由PacketBodyCreator对象去生成核心发布内容,接着由PacketHTMLHeaderCreator对象对这个内容进行处理,将其转化为HTML,最后由PacketHTTPHeaderCreator对PacketHTMLHeaderCreator的输出安装HTTP头部。程序运行结果如下:

 

 

   下图是本例的调用堆栈,从调用堆栈中,应该可以更容易地理解各个组件的相互关系。

 

 

   在JDK的实现中,有不少组件也是用装饰着模式实现的。其中,一个最典型的例子就是OutputStream和InputStream类族的实现。以OutputStream为例,OutputStream对象提供的方法比较简单,功能也比较挼2,但通过各种装饰者但增强,OutputStream对象可以被赋予强大的功能。

  下图显示以OutputStream为核心的装饰者模式的实现。其中FileOutputStream为系统的核心类,它实现了向文件写入数据。使用DataOutputStream可以在FileOutputStream的基础上,增加对多种数据类型的写操作支持,而BufferedOutputStream装饰器,可以对FileOutputStream增加缓冲功能,优化I/O的性能。以BufferedOutputStream为代表的性能组件,是将性能模块和功能模块分离的一种典型实现。

 

 

 

    public static void main(String[] args) throws IOException {
        //生成一个有缓冲功能的流对象 //spend:21
        DataOutputStream dout = 
                new DataOutputStream(
                        new BufferedOutputStream(
                                new FileOutputStream("C:\\a.txt")));
        //没有缓冲功能的流对象 //spend:592
        DataOutputStream dout1 = new DataOutputStream(
                new FileOutputStream("C:\\a.txt"));

        long begin = System.currentTimeMillis();
        for (int i =0;i<100000;i++){
            dout.write(i);
            //dout1.write(i);
        }
        System.out.println("spend:" + (System.currentTimeMillis() - begin));
    }

  以上代码显示FileOutputStream的典型应用。加粗部分是两种建立OutputStream的方法,第一种假如了性能组件BufferedOutputStream,第二种则没有。因此,第一种方法产生的OutputStream拥有更好的I/O性能。

  下面看一下装饰者模式如何通过性能组件增强I/O性能。在运行时,工作流程如图:

 

   在FileOutputStream.write()的调用之前,会首先调用BufferedOutputStream.write(),它的实现如下:

 

 

  可以看到,并不是每次BufferedOutputStream.write()调用都会去磁盘写入数据,而是将数据写入缓存中,当缓冲写满时,才调用FileOutputStream.write()方法,实际写入数据。以此实现性能组件与功能组件的完美分离。

 

posted @ 2019-09-17 17:21  加了冰的才叫可乐  阅读(349)  评论(0编辑  收藏  举报