【23 Design Patterns】Decorator Mode

Personal homepage:Golden Scales Stepping on the Rain

Personal introduction: Hello everyone, I amJinlin, a fledgling Java novice< /span>

Current situation: A 22nd ordinary undergraduate graduate. After many twists and turns, he now works for a large and well-known domestic daily chemical company, engaged in Java development

My blog: This is CSDN, where I learn technology and summarize knowledge. I hope to communicate with you and make progress together ~

Use composition instead of inheritance to enhance native object methods and add new behaviors and capabilities.

1. Implementation principle

Decorator is astructural design pattern that allowsdynamic Add new behavior to the object. It does this by creating a wrapper, i.e.putting the object into a decorator class and then putting the decorator class into another decorator class , and so on, forming a packaging chain.

Dynamicly add new behaviors or modifications without changing the original object Original behavior.

Code

1. Define an interface or abstract class as the base class of the decorated object.

public interface Component {
    void operation();
}

In this example, we define an interface called Component, which contains an abstract method called operation that defines the basic behavior of the decorated object.

2. Define a specificdecorated object and implement the methods in the base class.

public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("ConcreteComponent is doing something...");
    }
}

In this example, we define a concrete implementation class named ConcreteComponent that implements the operation method in the Component interface.

3. Define an abstract decorator class, inherit the base class, and the decorated object asattributes.

public abstract class Decorator implements Component {

    // 装饰器设计模式 使用组合的形式进行装饰
    protected Component component;

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

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

In this example, we define an abstract class named Decorator, which inherits the Component interface and uses the decorated object as a property. In the operation method, we call the method of the same name on the decorated object.

4. Defineconcrete decorator class, inherit the abstract decorator class, and implement enhancement Logic.

public class ConcreteDecoratorA extends Decorator {

    public ConcreteDecoratorA(Component component) {
        System.out.println("这是第一次包装...");
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorA is adding new behavior...");
    }
}

public class ConcreteDecoratorB extends Decorator {

    public ConcreteDecoratorB(Component component) {
        System.out.println("这是第二次包装...");
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        System.out.println("ConcreteDecoratorB is adding new behavior...");
    }
}

In this example, we define a concrete decorator class named ConcreteDecoratorA, inherit the Decorator abstract class, and implement the enhanced logic of the operation method. In the operation method, we first call the method of the same name on the decorated object and then add the new behavior.

5. Use decorators to enhance the decorated objects.

public class Main {
    public static void main(String[] args) {
        Component component = new ConcreteComponent();
        // 进行第一次包装
        component = new ConcreteDecoratorA(component);
        // 进行第二次包装
        component = new ConcreteDecoratorB(component);
        component.operation();
    }
}

In this example, we first create a decorated object ConcreteComponent, then create a decorator through the ConcreteDecoratorA class and pass the decorated object as a parameter. Finally, the decorator's operation method is called, so that the decorated object can be enhanced.

Decorator design mode,can continue to be packaged after packaging...similar to matryoshka dolls. Layer by layer, gradually strengthen! ! !

The difference between decorators and static proxies

Decorator mode andStatic proxy modeThe largest Difference——The purpose is different!

The purpose of the proxy pattern is tocontrol access to the object. It provides a proxy object outside the object to control access to the original object. access. The proxy object and the original object usually implement the same interface or inherit the same class to ensure that they can replace each other.

The purpose of the decorator pattern is todynamically enhance the functionality of the object. It uses a wrapper inside the object to accomplish. In the decorator pattern, the decorator class and the decorated object usually implement the same interface or inherit the same class to ensure that they can replace each other. Decorator pattern is also known as wrapper pattern.

It should be noted that although the decorator pattern can dynamically add behaviors to objects, it willincreasesystem Complexity, so the pros and cons need to be weighed carefully when using them.

2. Usage scenarios

In Java, the decorator pattern is widely used, especially in I/O operations. The I/O class library in Java uses the decorator pattern to implementconversion and enhancement between different data streams.

1. Understand decorators from the design of IO library

Open the file test.txt and read data from it.

Among them, InputStream is an abstract class, and FileInputStream is a subclass specially used to read file streams. BufferedInputStream is a data reading class that supports caching function, which can improve the efficiency of data reading

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

At first glance at the above code, we will feel that the usage of Java IO is more troublesome. We need to create a FileInputStream object first and then pass it to the BufferedInputStream object for use.

Why doesn't Java IO design a BufferedFileInputStream class that inherits FileInputStream and supports caching?

Wouldn't it be simpler to directly create a BufferedFileInputStream class object and open the file to read data?

InputStream bin = new BufferedFileInputStream("/user/wangzheng/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {
    //...
}

(1) Design scheme based on inheritance (can be realized, but unreliable)

If InputStream has only onesubclassFileInputStream, Then we can design a grandchild class BufferedFileInputStream based on FileInputStream. This is completely possible. After all, the inheritance structure is quite simple!

But in fact,there are manysubclasses that inherit InputStream. We need to subclass each InputStream and continue to derive subclasses that support cached reading.

In addition to supporting cache reading, if we need to enhance the function in other aspects, for example, the following DataInputStream class supports reading data according to basic data types (int, boolean, long, etc.).

FileInputStream in = new FileInputStream("/user/wangzheng/test.txt");
DataInputStream din = new DataInputStream(in);
int data = din.readInt();

In this case, if we continue to implement it through inheritance, we need to continue to derive classes such as DataFileInputStream and DataPipedInputStream. If we still need a class that supports both caching and reading data according to basic types, we will continue to derive n more classes such as BufferedDataFileInputStream and BufferedDataPipedInputStream.

This is just two additional enhancements. If we need to add more enhancements, it willlead to a combinatorial explosion, The class inheritance structure has become extremely complex, and the code is neitherextendable nor easy to maintain.

(2) Design scheme based on decorator pattern

Composition is better than inheritance. It is recommended to use composition toreplace inheritance! ! !

In response to the problem ofthe inheritance structure is too complex, we can change the inheritance relationship to Combination relationship to solve.

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;
    protected BufferedInputStream(InputStream in) {
    	this.in = in;
    }
    
    //...实现基于缓存的读数据接口...  
}

public class DataInputStream extends InputStream {
    protected volatile InputStream in;
    protected DataInputStream(InputStream in) {
    	this.in = in;
    }
    
    //...实现读取基本类型数据的接口
}
So is the decorator pattern simply "replacing inheritance with composition"?

of course not! From the perspective of Java IO design, the decorator pattern has two special features compared to the simple combination relationship!

1. The decorator class and the original classinherit the same parent class, so that we can modify the original class "Nest" multiple decorator classes.

For example, in the following piece of code, we have nested two decorator classes for FileInputStream:BufferedInputStream and DataInputStream, so that it supports both Cache reading also supports reading data according to basic data types.

// 逐次增强!
InputStream in = new FileInputStream("/user/wangzheng/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();

2. The decorator class isan enhancement of functionality, which is also an important feature of the application scenario of the decorator pattern.

In fact, there are many design patterns that conform to the code structure of "composition relationship", such as the proxy pattern mentioned before and the current decorator pattern. Although their code structures are similar, theintent of each design pattern is different! ! !

For example, in the proxy mode,the proxy class adds functions that are independent of the original class. In the decorator pattern, the decorator class adds enhanced functionality related to the original class.

// 代理模式的代码结构(下面的接口也可以替换成抽象类)
public interface IA {
    void f();
}
public class A impelements IA {
    public void f() { 
        //... 
    }
}
public class AProxy impements IA {
	private IA a;
    public AProxy(IA a) {
        this.a = a;
    }
    public void f() {
        // 新添加的代理逻辑
        a.f();
        // 新添加的代理逻辑
    }
}



// 装饰器模式的代码结构(下面的接口也可以替换成抽象类)
public interface IA {
    void f();
}
public class A impelements IA {
    public void f() {
        // ...
    }
}
public class ADecorator impements IA {
    private IA a;
    public ADecorator(IA a) {
        this.a = a;
    }
    public void f() {
        // 功能增强代码
        a.f();
        // 功能增强代码
    }
}
Why doesn't BufferedInputStream directly inherit InputStream?

In fact, if you look at the source code of the JDK, you will find that BufferedInputStream and DataInputStream are not inherited from InputStream, but another one called FilterInputStream class. So what is the design intention for introducing such a class?

InputStream is anabstract class rather than an interface, and most of its functions (such as read(), available()) have Default implementation. Logically speaking, we only need to re-implement the functions that need to be added to the cache function in the BufferedInputStream class. Other functions inherit the InputStream class. Default implementation. But in reality, this doesn’t work!

Even for functions that do not need to add caching functions, BufferedInputStream still must be re-implemented by simply wrapping the InputStream object function call; if it is not reimplemented, the BufferedInputStream class will not be able to delegate the final task of reading data to the passed InputStream object.

public class BufferedInputStream extends InputStream {
    protected volatile InputStream in;
    protected BufferedInputStream(InputStream in) {
        this.in = in;
    }

    // f()函数不需要增强,只是重新调用一下InputStream in对象的f()
    public void f() {
        in.f();
    }  
}

Actually,DataInputStream also has the same problem as BufferedInputStream. In order to avoid code duplication, Java IO abstracts a decorator parent class FilterInputStream.

All decorator classes of InputStream (BufferedInputStream, DataInputStream) inherit from this decorator parent class. In this way, the decorator class only needs to implement the methods it needs to enhance, and other methods inherit the default implementation of the decorator parent class.

public class FilterInputStream extends InputStream {

    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        return in.read(b, off, len);
    }

    public long skip(long n) throws IOException {
        return in.skip(n);
    }

    public int available() throws IOException {
        return in.available();
    }

    public void close() throws IOException {
        in.close();
    }

    public synchronized void mark(int readlimit) {
        in.mark(readlimit);
    }

    public synchronized void reset() throws IOException {
        in.reset();
    }
    
    public boolean markSupported() {
        return in.markSupported();
    }
}

In general, when BufferedInputStream inherits from FilterInputStream, it can very easily extend the behavior of FilterInputStream to implement an input stream buffering function. If BufferedInputStream directly inherits from InputStream, then it needs toreimplement all InputStream methods, including some methods that may not need to be modified, which will make the code change. complex and error-prone.

2.MyBatis cache design

The process of creating a cache

The useNewCache method is used to configure Mapper to use a brand new cache instance instead of sharing the cache instance.

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;
}

The default cache is as follows, which essentially maintains a simpleHashMap

public class PerpetualCache implements Cache {

  private final String id;

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

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

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

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }
  // ...省略其他的简单的方法

}

Cache build process

public Cache build() {
    // 设置默认的cache实现,并绑定默认的淘汰策略
    setDefaultImplementations();
    // 利用反射创建实例
    Cache cache = newBaseCacheInstance(implementation, id);
    // 设置properties属性
    setCacheProperties(cache);
    // 不应用装饰自定义缓存,自定义缓存需要自己实现对应的特性,如淘汰策略等
    // 通常情况自定义缓存有自己的独立配置,如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 will use the decorator design pattern to decorate the default cache so thathas LRU capabilities

private void setDefaultImplementations() {
    if (implementation == null) {
        implementation = PerpetualCache.class;
        if (decorators.isEmpty()) {
            // decorators是成员变量,装饰器,饰器具备LRU的能力
            decorators.add(LruCache.class);
        }
    }
}

LruCache is implemented as follows. The default LRU algorithm implementation is based onLinkedHashMap

public class LruCache implements Cache {

    // 代理目标缓存
    private final Cache delegate;
    private Map<Object, Object> keyMap;
    private Object eldestKey;

    // LruCache用来装饰默认的缓存,这里实现了缓存的高级特性
    public LruCache(Cache delegate) {
        this.delegate = delegate;
        setSize(1024);
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

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

    // 设置长度,构建一个LinkedHashMap,重写removeEldestEntry
    public void setSize(final int size) {
        // 第三个参数accessOrder为true,可以使LinkedHashMap维护一个【访问顺序】
        // 最近被访问的数据会被放在链表的尾部,天然实现lru
        keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
            private static final long serialVersionUID = 4267176411845948333L;

            // 重写该方法,父类直接返回false
            // 只要实际容量size() 大于 初始化容量 size 认定当前的缓存已经满了
            // 该方法会在LinkedHashMap的afterNodeInsertion方法中被主动调用
            // 会将头节点当作eldest删除
            @Override
            protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
                
                boolean tooBig = size() > size;
                if (tooBig) {
                    // 同时将这个这个key复制给成员变量eldestKey
                    eldestKey = eldest.getKey();
                }
                return tooBig;
            }
        };
    }

    // put一个缓存的过程
    // 放入当前的缓存值,淘汰eldestKey
    @Override
    public void putObject(Object key, Object value) {
        delegate.putObject(key, value);
        cycleKeyList(key);
    }

    // get一个缓存的过程
    // 获得该值,同时提升key热度,主动访问一下keyMap.get(key)
    @Override
    public Object getObject(Object key) {
        keyMap.get(key); 
        return delegate.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        return delegate.removeObject(key);
    }

    @Override
    public void clear() {
        delegate.clear();
        keyMap.clear();
    }

    // 循环key的集合
    private void cycleKeyList(Object key) {
        keyMap.put(key, key);
        if (eldestKey != null) {
            delegate.removeObject(eldestKey);
            eldestKey = null;
        }
    }

}

Finally, use other decorators to decorate the cache so that it has more capabilities

private Cache setStandardDecorators(Cache cache) {
    try {
        MetaObject metaCache = SystemMetaObject.forObject(cache);
        // 设置大小,默认1024
        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);
    }
}

3. Summary

The decorator pattern mainly solvesthe problem of overly complicated inheritance relationships, usually through instead of inheritance. Composition

Its main function isto add functionality to the original class. This is also an important basis for judging whether to use the decorator mode. In addition, the decorator pattern has another feature, that is, can be used to nest the original class. decorators. In order to meet this application scenario, during design, the decorator class needs to inherit the same abstract class or interface as the original class.

Q: When learning the proxy design pattern, we added the cache reading function to InputStream through the decorator pattern. So for the application scenario of "adding cache", should we use the proxy mode or the decorator mode? What do you think about this issue?

In fact, for most "add cache" business scenarios, the core purpose is to enhance the function of the object (that is, to increase the cache function), rather than to control access to the object, so the decorator mode might be more suitable. But if we want to force the addition of a local cache to the persistence layer, the proxy design pattern is also a good choice.

The article ends here. If you have any questions, you can point them out in the comment area~

I hope to work hard with the big guys and see you all at the top

Thank you again for your support, friends! ! !

Guess you like

Origin blog.csdn.net/weixin_43715214/article/details/133896753