单例+责任链+装饰+代理

从看懂到看开——初探设计模式

本文档主要介绍了四种设计模式:单例模式、责任链模式、装饰模式和代理模式。

为什么选择这四种模式?因为设计模式是一种很玄的东西。作为初探,越看越迷糊,只能选择一些不那么迷糊的模式进行介绍,就当做抛砖引玉吧。

撰写本文档主要参考了《设计模式之禅》这本书,如有错漏还请告知。

1.单例模式

顾名思义,只有一个实例,而且自行实例化并向整个系统提供这个实例。

Ensure a class has only one instance, and provide a global point of access to it.

典型示例

首先是单例类。

public class SingleObject {

   private static SingleObject instance = new SingleObject();

   private SingleObject(){}

   public static SingleObject getInstance(){
      return instance;
   }

   public void showMessage(){
      System.out.println("Hello World!");
   }
}

然后是场景类。

public class SingletonPatternDemo {
   public static void main(String[] args) {

      SingleObject object = SingleObject.getInstance();

      object.showMessage();
   }
}
优缺点分析

下面两段摘书,单例模式比较简单,有所了解可以跳过。

优点:

1.单例模式只有一个实例,减少了内存的开销,尤其是当一个对象需要频繁地创建、销毁且创建或销毁的性能又无法优化时,单例模式的优势就非常明显。

2.由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

3.单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

4.单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

缺点:

1.没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。

2.单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。

3.单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

小结:

单例模式就像双刃剑,优点是只有一个,缺点也是只有一个。

使用情景

1.要求生成唯一序列号的环境;

2.在整个项目中需要一个共享访问点或共享数据,例如spring configuration或一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;

3.创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源;

4.需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式。

结合项目使用

MES的流程表单中有一个属性类型是自动生成的编码,那生成编码就可以做成单例的工具类。

2.责任链模式

使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.Chain the receiving objects and pass the request along the chain until an object handles it.

典型示例

目前的项目中都引入了log4j记录一些运行时信息,运行时信息被分成了不同的等级,比如errorinfodebug等,此处便以此为蓝本,简化出一个日志记录器来描述责任链模式。

首先定义处理对象的基类和三种日志类型。

public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;

   protected int level;

   protected AbstractLogger nextLogger;

   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }

   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }

   abstract protected void write(String message);

}

接下来是三种类型的实现类。

public class ConsoleLogger extends AbstractLogger {

   public ConsoleLogger(int level){
      this.level = level;
   }

   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}

public class ErrorLogger extends AbstractLogger {

   public ErrorLogger(int level){
      this.level = level;
   }

   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}

public class FileLogger extends AbstractLogger {

   public FileLogger(int level){
      this.level = level;
   }

   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}

最后是场景类。

public class ChainPatternDemo {

   private static AbstractLogger getChainOfLoggers(){

      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);

      return errorLogger;  
   }

   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();

      loggerChain.logMessage(AbstractLogger.INFO, 
         "This is an information.");

      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is an debug level information.");

      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}
优缺点分析

优点:

1.将请求和处理解耦,请求者不必关心是谁处理的,处理者也不必对请求者知根知底。

2.灵活,可以动态调配处理的顺序,也很容易对处理类进行扩展。

缺点:

在链很长的时候性能堪忧,也不方便调试,因此要注意控制链的长度。

小结:

责任链模式看起来有点像js中的冒泡事件,也有点像一个膨胀的switchcase的数量不受限制。

使用情景

从定义中的请求、处理、返回结果这些词,很难不让人联想到Servlet中的FilterChain。比起这种从设计最初就比较容易想到的场景,作为补救措施出现的可能性也比较高,比如需求变更导致的同质业务增加。

结合项目使用

维修平台项目中,注册环节,制造商管理员和普通用户需要填写的内容不同,处理也稍有不同。可以通过责任链模式进行处理,注册统一接口,设置下一个处理对象,依次判断处理。

设置下一个处理对象的模式也不一定要通过显式的方式,也许可以通过链表结构来实现。

3.装饰模式

动态地给一个对象添加一些额外的职责,装饰模式比继承更灵活。

Attach additional responsibilities to an object dynamically keeping the same interface.Decorators provide a flexible alternative to subclassing for extending functionality.

典型示例

装饰模式的结构稍微复杂一点,先上类图。

decorator pattern UML

  • component是一个接口,也就是最原始的对象。

  • concrete componentcomponent的具体实现。

  • decorator是装饰器。

  • concrete decorator是装饰器的具体实现。

下面以画图为例,描述装饰模式,首先是对象。

public interface Shape {
   void draw();
}

public class Rectangle implements Shape {

   @Override
   public void draw() {
      System.out.println("Shape: Rectangle");
   }
}

public class Circle implements Shape {

   @Override
   public void draw() {
      System.out.println("Shape: Circle");
   }
}

然后是装饰器。

public abstract class ShapeDecorator implements Shape {
   protected Shape decoratedShape;

   public ShapeDecorator(Shape decoratedShape){
      this.decoratedShape = decoratedShape;
   }

   public void draw(){
      decoratedShape.draw();
   }  
}

public class RedShapeDecorator extends ShapeDecorator {

   public RedShapeDecorator(Shape decoratedShape) {
      super(decoratedShape);     
   }

   @Override
   public void draw() {
      decoratedShape.draw();         
      setRedBorder(decoratedShape);
   }

   private void setRedBorder(Shape decoratedShape){
      System.out.println("Border Color: Red");
   }
}

最后是场景类。

public class DecoratorPatternDemo {
   public static void main(String[] args) {

      Shape circle = new Circle();

      Shape redCircle = new RedShapeDecorator(new Circle());

      Shape redRectangle = new RedShapeDecorator(new Rectangle());
      System.out.println("Circle with normal border");
      circle.draw();

      System.out.println("\nCircle of red border");
      redCircle.draw();

      System.out.println("\nRectangle of red border");
      redRectangle.draw();
   }
}
优缺点分析

优点:

对象和装饰器解耦,装饰器可以动态扩展,比较灵活。

缺点:

多层装饰是复杂的。因此要尽量减少装饰类的数量,以便降低系统的复杂度。

小结:

1.装饰模式是继承的一种替代方案,子类无限扩充是灾难,装饰太多也是。

2.继承是静态的,装饰是动态的。在某些情况下,装饰更加易于维护。

使用情景

就像定义提到的,继承可以使用的地方,都可以考虑是否装饰模式更合适。

比如需要动态添加或撤销功能的时候,如果是在继承关系中要拆掉中间层的封装,不改动对象基本上不可能,而装饰器就比较灵活,在场景类中去掉就可以。

类似模式对比

参见第四小节代理模式的模式对比。

结合项目使用

也许表单属性定义的继承实现可以用装饰模式替代。

4.代理模式(委托模式)

为其他对象提供一种代理以控制对这个对象的访问。

Provide a surrogate or placeholder for another object to control access to it.

典型示例

代理模式的结构稍微复杂一点,先上类图。

proxy pattern UML

  • Subject无特殊要求,就是对象需要实现的功能。

  • RealSubject是实现了Subject接口。

  • Proxy是代理类,在代理类中可进行预处理和善后工作。

下面以图片加载为例,描述代理模式,首先是加载图片的接口和实现。

public interface Image {
   void display();
}

public class RealImage implements Image {

   private String fileName;

   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }

   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }

   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}

然后是代理类,代理类也要实现功能。

public class ProxyImage implements Image{

   private RealImage realImage;
   private String fileName;

   public ProxyImage(String fileName){
      this.fileName = fileName;
   }

   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}

最后是场景类。

public class ProxyPatternDemo {

   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");

      image.display(); 
      System.out.println("");
      image.display();  
   }
}

第一次需要从磁盘加载,第二次就不需要了。(这个实例没有书中代练游戏的场景那么生动,有兴趣的可以看书。)

优缺点分析

优点:

1.职责清晰

真实的实现类就负责主要功能的实现,代理类则处理一下辅助功能。比如打游戏是主要功能,而结算游戏升级费用就是辅助功能。

2.高扩展性

真实的实现类在实际场景下难免发生变更,但是只要接口没有变更,代理类就无需修改。

3.智能化

在上一小结的例子中看不出来智能化在哪里,这一特点具体体现在“动态代理”的实现中。本文档对代理模式的扩展不做赘述,感兴趣的可以看书。

缺点:

有些类型的代理模式可能会造成请求的处理速度变慢;有些代理模式的实现非常复杂。

小结:

1.代理模式就是对目标方法进行拦截(控制),然后在目标方法的前后加塞其他功能(增强)。

2.代理模式有诸多扩展,看书的体会就是既视感很强,spring到底用了多少模式。

使用情景

1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护代理。 5、Cache代理。 6、防火墙代理。 7、同步化代理。 8、智能引用代理。

目前项目中已经用到的比如Spring AOP,利用切面做消息通知。

类似模式对比

与第三小节的装饰模式进行对比,两者的概念其实有些类似,但是从类图中可以看到,代理模式更加强势,它决定了实际对象执行或者不执行,装饰器就只是增强,代理更多的是控制

结合项目使用

利用现有的AOP做日志或消息通知。

5.小结

coding不易,架构更不易,且行且珍惜。