关于后端:设计模式之装饰器模式

40次阅读

共计 8539 个字符,预计需要花费 22 分钟才能阅读完成。

本文由老王将建好的书房打算请小王来帮忙,小王却想谋权篡位,老王通过教育他引出装璜器设计模式,第二局部针对老王提出的建设性意见实现装璜器模式,第三局部针对装璜器模式在 Jdk 中的 IO、Spring 中的缓存管理器、Mybatis 的使用来增强咱们的了解,第四局部阐明装璜器模式和代理模式的区别及他们各自的利用场景。

读者能够拉取残缺代码到本地进行学习,实现代码均测试通过后上传到码云。

一、引出问题

上篇文章对老王的书架革新当前,老王是相当的称心,看小王能力突出,这不老王又有了新的需要。

通过组合模式当前老王的书被治理的东倒西歪,然而随着书的增多,老王就有一些忙不过来了,老王就想让小王帮他解决一些额定的事,比方在买书之前清扫一下书房,在早晨的时候把书房的门锁一下;或者有人借书之前做一下记录,借书者还书当前小王接管一下,等等。

小王听完说这有何难,说完撸起袖子就筹备改老王的代码。老王急忙拦住了他,你真是个呆瓜,我写的代码你凭什么要动,你改了会不会影响我的业务逻辑,平时让你多看书你不听,之前学的设计模式呢?不拿进去用,眼看着让他吃灰。

小王不好意思的挠挠头,翻出来了他的设计模式宝典,开始寻找适合的设计模式。

小王大喊有了,之前说过的代理模式能够很好的解决这个问题,代理模式能够动静的加强对象的一些个性,我筹备应用代理模式实现这个需要。

老王听完止不住的摇摇头,看来你是打算谋权篡位了,你是想要我整个书房的权力呀!

老王解释说,代理模式是能够实现这个需要,然而在这个场景下显然代理模式不适合,代理模式是着重对对象的管制,而咱们明天的需要是在该对象的根底之上减少他的一些性能,咱们各自的业务独立倒退互不烦扰。

二、装璜器模式概念与应用

实际上,在原对象的根底之上减少其性能就是属于装璜器模式。

装璜器模式(Decorator Pattern)容许向一个现有的对象增加新的性能,同时又不扭转其构造。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

在装璜器模式中应该是有四个角色:

①Component 形象构件(老王形象办法)

    ②ConcreteComponent 具体构件(老王实现办法)③Decorator 装璜角色(装璜者小王)④ConcreteDecorator 具体装璜角色(装璜者小王实现办法)

在装璜器模式中,须要加强的类(被装璜者)要实现接口,装璜者继承被装璜者的接口,并将被装璜者的实例传进去,在具体装璜角色中调用被装璜者的办法,在其前后定义加强的办法,在理论利用中往往装璜角色和具体装璜角色合二为一。

咱们看下具体的代码实现:

形象构件:

/**
 * 书的形象构件
 * @author tcy
 * @Date 10-08-2022
 */
public abstract class ComponentBook {

    /**
     * 借书
     */
    public abstract void borrowBook();

    /**
     * 买书
     */
    public abstract void buyBook();}

书的具体构件:

/**
 * 书的具体构件
 * @author tcy
 * @Date 10-08-2022
 */
public class ConcreteComponentBook extends ComponentBook{
    @Override
    public void borrowBook() {System.out.println("老王的书借出去...");

    }

    @Override
    public void buyBook() {System.out.println("老王的书买回来...");

    }
}

装璜角色:

/**
 * 书的装璜者
 * @author tcy
 * @Date 10-08-2022
 */
public class DecoratorBook extends ComponentBook{

    private ComponentBook componentBook;

    DecoratorBook(ComponentBook componentBook){this.componentBook=componentBook;}

    @Override
    public void borrowBook() {this.componentBook.borrowBook();
    }

    @Override
    public void buyBook() {this.componentBook.buyBook();
    }
}

书的具体装璜角色:

/**
 * 子类里写了并且应用了无参的构造方法然而它的父类(先人)中却至多有一个是没有无参构造方法的
 * @author tcy
 * @Date 10-08-2022
 */
public class ConcreteDecoratorBook1 extends DecoratorBook{ConcreteDecoratorBook1(ComponentBook componentBook) {super(componentBook);
    }

    public void cleanRoom(){System.out.println("清扫书房...");
    }

    public void shutRoom(){System.out.println("敞开书房...");
    }

    public void recordBook(){System.out.println("记录借出记录...");
    }

    public void returnBook(){System.out.println("收到借出去的书...");
    }

    @Override
    public void buyBook() {this.cleanRoom();
        super.buyBook();
        this.shutRoom();
        System.out.println("----------------------------");
    }

    @Override
    public void borrowBook() {this.recordBook();
        super.borrowBook();
        this.returnBook();
        System.out.println("----------------------------");
    }
}

如果读者的 Java 根底扎实,了解装璜器还是比拟轻松的,装璜器的实现形式很直观,须要特地指出的是,在书的具体装璜角色中,要显示的定义一个构造方法。

根底不太扎实的读者可能会有一个疑难,在 Java 的类中默认不是会有一个无参的构造方法吗?为什么这里还须要定义呢?

在 java 中一个类只有有父类,那么在它实例化的时候,肯定是从顶级的父类开始创立。

也就是说当你用子类的无参构造函数创立子类对象时,会去先递归调用父类的无参构造方法,这时候如果某个类的父类没有无参构造方法就会编译出差。所以咱们在子类中能够手动定义一个无参办法,或者在父类中显示的定义一个构造方法。

客户端:

/**
 * @author tcy
 * @Date 09-08-2022
 */
public class  {public static void main(String[] args) {ComponentBook componentBook=new ConcreteComponentBook();
        componentBook=new ConcreteDecoratorBook1(componentBook);

        componentBook.borrowBook();

        componentBook.buyBook();}
}

办法调用后咱们能够看到执行后果:

 记录借出记录...
老王的书借出去...
收到借出去的书...
----------------------------
清扫书房...
老王的书买回来...
敞开书房...
----------------------------

在老王借书和买书的这个事件中,胜利的织入进去小王的办法,这样也就实现了装璜器模式。

为了增强了解咱们接着看装璜器模式在咱们常常接触的源码中的使用。

三、利用

1、jdk 中的利用 IO

装璜器在 java 中最典型的利用就是 IO,咱们晓得在 IO 家族中有各种各样的流,而流往往都是作用在子类之上,而后减少其附加性能,咱们以 InputStream 举例。

InputStream 是字节输出流,此抽象类是示意字节输出流的所有类的超类。

FileInputStream 是 InputStream 的一个实现父类,BufferedInputStream 是 FileInputStream 的实现父类。

理论 BufferedInputStream 就是装璜者,InputStream 就是形象构件,FileInputStream 是具体构件,BufferedInputStream 就是对 FileInputStream 进行了包装。

咱们看具体的利用:

FileInputStream fileInputStream = new FileInputStream(filePath); 
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

间接将 FileInputStream 的实例传给 BufferedInputStream 的构造方法,就能调用 BufferedInputStream 加强的一些办法了。

咱们具体看 BufferedInputStream 装璜器类:

public class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    protected int marklimit;

    public BufferedInputStream(InputStream in) {this(in, DEFAULT_BUFFER_SIZE);
    }

      ...
    // 加强的办法
    private void fill() throws IOException {byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            if (markpos > 0) {  /* can throw away early part of the buffer */
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                markpos = -1;   /* buffer got too big, invalidate mark */
                pos = 0;        /* drop buffer contents */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

   ...

BufferedInputStream 类中有许多其余的办法,就是对 FileInputStream 类的加强。

2、Spring 中的使用

Spring 应用装璜器模式有两个典型的特色,一个是类名中含有 Wrapper,另一类是含有 Decorator,性能也即动静的给某些类减少一些额定的性能。

TransactionAwareCacheDecorator 是解决 spring 有事务的时候缓存的类,咱们在应用 spring 的 cache 注解实现缓存的时候,当呈现事务的时候,那么缓存的同步性就须要做相应的解决了,于是就有了这个装璜者。

public class TransactionAwareCacheDecorator implements Cache {

    // 形象构件
   private final Cache targetCache;

   /**
    * Create a new TransactionAwareCache for the given target Cache.
    * @param targetCache the target Cache to decorate
    */
   public TransactionAwareCacheDecorator(Cache targetCache) {Assert.notNull(targetCache, "Target Cache must not be null");
      this.targetCache = targetCache;
   }

   /** 间接调用未加强
    * Return the target Cache that this Cache should delegate to.
    */
   public Cache getTargetCache() {return this.targetCache;}

    // 间接调用未加强
   @Override
   public String getName() {return this.targetCache.getName();
   }

// 间接调用未加强
   @Override
   public Object getNativeCache() {return this.targetCache.getNativeCache();
   }

// 间接调用未加强
   @Override
   @Nullable
   public ValueWrapper get(Object key) {return this.targetCache.get(key);
   }

// 间接调用未加强
   @Override
   public <T> T get(Object key, @Nullable Class<T> type) {return this.targetCache.get(key, type);
   }

// 间接调用未加强
   @Override
   @Nullable
   public <T> T get(Object key, Callable<T> valueLoader) {return this.targetCache.get(key, valueLoader);
   }

// 先进行判断确定是否须要加强
   @Override
   public void put(final Object key, @Nullable final Object value) {if (TransactionSynchronizationManager.isSynchronizationActive()) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {TransactionAwareCacheDecorator.this.targetCache.put(key, value);
            }
         });
      }
      else {this.targetCache.put(key, value);
      }
   }
}

Cache 是形象构件,TransactionAwareCacheDecorator 就是装璜者,而 Cache 的实现类就是具体构件。

因为并非所有的办法都会应用事务,有的一般办法就不须要装璜,有的就须要,所以就应用了装璜者模式来实现。

比方 put() 办法:

  @Override
   public void put(final Object key, @Nullable final Object value) {if (TransactionSynchronizationManager.isSynchronizationActive()) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {TransactionAwareCacheDecorator.this.targetCache.put(key, value);
            }
         });
      }
      else {this.targetCache.put(key, value);
      }
   }

会在 put 前判断是否开启了事务 TransactionSynchronizationManager.isSynchronizationActive(),如果开启事务就调用一下额定的办法,如果没有开始事务就调用默认的办法。

咱们举的这个例子调用的就是 TransactionSynchronizationManager.registerSynchronization() 办法,也即是为以后线程注册一个新的事务同步。

在 Spring 中将装璜角色和具体装璜角色合二为一,间接在装璜者中实现要减少的办法。

3、MyBatista 的使用

理解过 MyBatis 的大抵执行流程的读者应该晓得,Executor 是 MyBatis 执行器,是 MyBatis 调度的外围,负责 SQL 语句的生成和查问缓存的保护;CachingExecutor 是一个 Executor 的装璜器,给一个 Executor 减少了缓存的性能。此时能够看做是对 Executor 类的一个加强,故应用装璜器模式是适合的。

咱们首先看下 Executor 类的继承构造。

咱们将要害的 CachingExecutor 代码放上:

public class CachingExecutor implements Executor {
    // 持有组件对象
  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager();
    // 构造方法,传入组件对象
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
      // 转发申请给组件对象,能够在转发前后执行一些附加动作
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
  //...
 }

Executor 就是形象构件,BaseExecutor 是具体构件的实现,CachingExecutor 就是装璜角色,那具体装璜角色在哪呢?

理论中具体装璜角色间接在装璜角色中集成了,并没有将具体装璜角色齐全独立进去。

另外,Mybatis 的一级缓存和二级缓存也是应用的装璜者模式,有趣味的读者能够拉取 Mybatis 的源代码本地进行调试钻研。

四、总结

到此为止,咱们就将装璜器模式的内容解说分明了,看到这读者可能发现,针对某一类需要可能会有很多设计模式都能实现需要,但肯定是有最合适的那一个,就像咱们明天举的例子无论是用装璜器模式还是代理模式都能够实现这个需要。

但咱们看代理模式中咱们列举的例子是以租房做例子,中介将房子的权力齐全移交过来,中介齐全管制房子做一些革新,明天书房的需要只是让小王来帮忙的,还是以老王为主体,小王只是做一些附加。

装璜器模式就是在瓶外面插了一朵花,而代理模式是把瓶子都给人家了,让人家轻易折腾。

如果咱们的需要是日志收集、拦截器,代理模式是最适宜的。如果是动静的减少对象的性能、限度对象的执行条件、参数管制和查看等应用适配器模式就更加适合了。

举荐读者,参考软件设计七大准则 认真浏览往期的文章,认真领会。

创立型设计模式

一、设计模式之工厂办法和形象工厂

二、设计模式之单例和原型

三、设计模式之建造者模式

结构型设计模式

四、设计模式之代理模式

五、设计模式之适配器模式

六、桥接模式

七、组合模式

正文完
 0