装饰器设计模式

2. 装饰器设计模式

2.1 实现原理

装饰器设计模式(Decorator)是一种结构型设计模式,它允许动态地为对象添加新的行为。它通过创建一个包装器来实现,先将对象放入一个装饰器类中,再将装饰器类放入另一个装饰器类中,以此类推,形成一条包装链。这样可以在不改变原有对象的情况下,动态地添加新的行为或修改原有行为。

在 Java 中,实现装饰器设计模式的步骤如下:

(1)定义一个接口或抽象类,作为被装饰对象的基类

java 复制代码
/**
 * 接口描述:装饰接口
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/12/14 21:15
 */
public interface Component {
    /**
     * 用于定义被装饰对象的基本行为
     */
    void operation();
}

(2)定义一个具体的被装饰对象,实现基类中的方法

java 复制代码
/**
 * 类描述:具体的被装饰对象类
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/12/14 21:17
 */
@Slf4j
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        log.info(">>>ConcreteComponent is doing something...");
    }
}

(3)定义一个抽象装饰器类,继承基类,并将被装饰对象作为属性

java 复制代码
/**
 * 类描述:装饰器类
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/12/14 21:19
 */
public abstract class Decorator implements Component {

    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        this.component.operation();
    }
}

(4)定义具体的装饰器类,继承抽象装饰器类,并实现增强逻辑。

java 复制代码
/**
 * 类描述:具体的装饰器类
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/12/14 21:21
 */
@Slf4j
public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    /**
     * 重写方法,并做增强
     */
    @Override
    public void operation() {
        // 我们先调用被装饰对象的同名方法,然后添加新的行为
        super.operation();
        log.info("ConcreteDecoratorA is adding new behavior...");
    }
}

(5)使用装饰器增强被装饰对象

java 复制代码
/**
 * 类描述:装饰器设计模式测试
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2023/12/14 21:25
 */
@Slf4j
public class DecoratePatternTest {

    @Test
    public void test() {
        Component component = new ConcreteComponent();
        ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);
        concreteDecoratorA.operation();
    }
}

测试效果:

markdown 复制代码
>>> ConcreteComponent is doing something...
>>> 增强:ConcreteDecoratorA is adding new behavior...

虽然装饰器模式和静态代理模式有一些相似之处,但它们之间还是有区别的:

代理模式的目的是为了控制对对象的访问,它在对象的外部提供一个代理对象来控制对原始对象的访问。代理对象和原始对象通常实现同一个接口或继承同一个类,以保证二者可以互相替代。

装饰器模式的目的是为了动态地增强对象的功能,它在对象的内部通过一种包装器的方式来实现。装饰器模式中,装饰器类和被装饰对象通常实现同一个接口或继承同一个类,以保证二者可以互相替代。装饰器模式也被称为包装器模式。

需要注意,装饰器模式虽然可以实现动态地为对象增加行为,但是会增加系统的复杂性,因此在使用时需要仔细权衡利弊。

2.2 使用场景

2.2.1 从IO库的设计理解装饰器

InputStream 是一个抽象类,FileInputStream 是专门用来读取文件流的子类。BufferedInputStream 是一个支持带缓存功能的数据读取类,可以提高数据读取的效率,具体的代码如下:

java 复制代码
InputStream in = new FileInputStream("D:/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
    while (bin.read(data) != -1) {
    //...
}

上面代码中先创建一个FileInputStream 对象,然后再传递给 BufferedInputStream 对象来使用。Java IO 为什么不设计一个继承 FileInputStream 并且支持缓存的BufferedFileInputStream 类呢?比如:

java 复制代码
// 实际IO没有这样设计
InputStream bin = new BufferedFileInputStream("/user/wangzheng/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
	//...
}

(1)基于继承的设计方案

如果 InputStream 只有一个子类 FileInputStream 的话,那我们在 FileInputStream基础之上,再设计一个孙子类 BufferedFileInputStream,也算是可以接受的,毕竟继承结构还算简单。但实际上,继承 InputStream 的子类有很多。我们需要给每一个 InputStream 的子类,再继续派生支持缓存读取的孙子类。

除了支持缓存读取之外,如果我们还需要对功能进行其他方面的增强,比如下面的DataInputStream 类,支持按照基本数据类型(int、boolean、long 等)来读取数据。

java 复制代码
FileInputStream in = new FileInputStream("/user/wangzheng/test.txt");
DataInputStream din = new DataInputStream(in);
int data = din.readInt();

在这种情况下,如果我们继续按照继承的方式来实现的话,就需要再继续派生出DataFileInputStream、DataPipedInputStream 等类。如果我们还需要既支持缓存、又支持按照基本类型读取数据的类,那就要再继续派生出BufferedDataFileInputStream、BufferedDataPipedInputStream 等 n 多类。这还只是附加了两个增强功能,如果我们需要附加更多的增强功能,那就会导类继承结构变得无比复杂,代码既不好扩展,也不好维护。

(2)基于装饰器模式的设计方案

讲到"组合优于继承",可以使用组合来替代继承。针对刚刚的继承结构过于复杂的问题,我们可以通过将继承关系改为组合关系来解决。下面的代码展示了 Java IO 的这种设计思路。

java 复制代码
public abstract class InputStream {
    //...
    public int read(byte b[]) throws IOException {
    	return read(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
    	//...
    }
    public long skip(long n) throws IOException {
    	//...
    }
    public int available() throws IOException {
    	return 0;
    }
    public void close() throws IOException {}
    public synchronized void mark(int readlimit) {}
    public synchronized void reset() throws IOException {
    	throw new IOException("mark/reset not supported");
    }
    public boolean markSupported() {
    	return false;
    }
}

public class BufferedInputStream extends InputStream {
    protected volatile InputStream in;
    // 包装InputStream对象
    protected BufferedInputStream(InputStream in) {
    	this.in = in;
    }
    //...实现基于缓存的读数据接口...
}
public class DataInputStream extends InputStream {
    protected volatile InputStream in;
    // 包装InputStream对象
    protected DataInputStream(InputStream in) {
    	this.in = in;
    }
	//...实现读取基本类型数据的接口
}

装饰器类和原始类继承同样的父类,这样我们可以对原始类"嵌套"多个装饰器类。

装饰器类是对功能的增强。

2.2.2 mybatis的缓存设计

org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache创建缓存的过程:

java 复制代码
public class MapperBuilderAssistant extends BaseBuilder {
	// 。。。
	public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
        // 根据类型生成实例,并进行配置
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }
}

默认的缓存如下,本质就是维护了一个简单的HashMap:

java 复制代码
public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  // ...省略其他的简单的方法
}

缓存的实现当然不可能这么简单,事实上他的构造过程(org.apache.ibatis.mapping.CacheBuilder#build)如下:

java 复制代码
public class CacheBuilder {
	private final String id;
	private Class<? extends Cache> implementation;
	private final List<Class<? extends Cache>> decorators;
	public Cache build() {
    	// 设置默认的cache实现,并绑定默认的淘汰策略
    	setDefaultImplementations();
        // 利用反射创建实例
    	Cache cache = newBaseCacheInstance(implementation, id);
        // 设置properties属性
    	setCacheProperties(cache);
        // issue #352, do not apply decorators to custom caches
        // 自定义缓存需要自己实现对应的特性,如淘汰策略等
		// 通常情况自定义缓存有自己的独立配置,如redis、ehcache
        if (PerpetualCache.class.equals(cache.getClass())) {
          for (Class<? extends Cache> decorator : decorators) {
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
          }
          // 这是标准的装饰器,这里使用了装饰器设计模式
          cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
          cache = new LoggingCache(cache);
        }
        return cache;
  }
}

mybatis会使用装饰者设计模式,对默认cache进行装饰,使其具有LRU的能力,如下:

java 复制代码
private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
}

最后使用其他的装饰器对cache进行装饰,使其就有更多的能力.

java 复制代码
private Cache setStandardDecorators(Cache cache) {
     // 包装增强cache
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
}
相关推荐
苹果醋315 分钟前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
Hello.Reader35 分钟前
深入解析 Apache APISIX
java·apache
菠萝蚊鸭1 小时前
Dhatim FastExcel 读写 Excel 文件
java·excel·fastexcel
旭东怪1 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
007php0071 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
∝请叫*我简单先生1 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
ssr——ssss1 小时前
SSM-期末项目 - 基于SSM的宠物信息管理系统
java·ssm
一棵星2 小时前
Java模拟Mqtt客户端连接Mqtt Broker
java·开发语言
鲤籽鲲2 小时前
C# Random 随机数 全面解析
android·java·c#
zquwei2 小时前
SpringCloudGateway+Nacos注册与转发Netty+WebSocket
java·网络·分布式·后端·websocket·网络协议·spring