Architect's internal mental skill, a detailed explanation of the decorator model necessary for the reconstruction project experience

Posted by kutyadog on Fri, 28 Feb 2020 12:50:01 +0100

1, Application scenario of decorator mode

In our life, for example, add an egg to a pancake, add some fruit to the cake, decorate the house, etc. Extend the responsibilities of some extra objects to objects. Decorator Pattern means that it provides a more flexible alternative (extending the function of the original object) than inheritance without changing the original object.

The decorator mode is used in the following scenarios:

  • Used to extend the function of a class or add additional responsibilities to a class;
  • Add functions to an object dynamically, and these functions can be revoked dynamically.

1.1 decorator mode of eating pancakes for breakfast

All the students who commute to work in Beijing in the morning have pancakes from roadside stands. When you buy pancakes, you can ask him to add eggs, sausages, spicy strips, etc. Let's use the code to implement the case of this life scenario. First, create a pancake BatterCake class:

public class BatterCake {

    protected String getMsg() {
        return "A pancake";
    }

    protected BigDecimal getPrice() {
        return new BigDecimal(6.00);
    }

}

Then create an egg pancake BatterCakeWithEgg class:

public class BatterCakeWithEgg extends BatterCake {

    @Override
    protected String getMsg() {
        return super.getMsg() + "One egg added";
    }

    protected BigDecimal getPrice() {
        return new BigDecimal(6.00).add(new BigDecimal(1.00));
    }

}

Create a BattercakeWithEggAndSausage class with both eggs and sausages:

public class BatterCakeWithEggAndSausage extends BatterCakeWithEgg {

    @Override
    protected String getMsg() {
        return super.getMsg() + "One more sausage";
    }

    @Override
    protected BigDecimal getPrice() {
        return super.getPrice().add(new BigDecimal(2.00));
    }
}

Test the main method:

  public static void main(String[] args) {
    Battercake battercake = new Battercake();
    System.out.println(battercake.getMsg() + ",Total price:" + battercake.getPrice().doubleValue());
    Battercake battercakeWithEgg = new BattercakeWithEgg();
    System.out.println(battercakeWithEgg.getMsg() + ",Total price:" + battercakeWithEgg.getPrice().doubleValue());
    Battercake battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
    System.out.println(battercakeWithEggAndSausage.getMsg() + ",Total price:" +
            battercakeWithEggAndSausage.getPrice().doubleValue());
}

Operation result:

There is no problem with the running result of the above program. If you need to add two eggs and two sausages, the current implementation method can not meet the requirements, nor can you calculate the price, unless you customize another class to implement. It's obviously unscientific if you need to keep changing. Let's use the decorator pattern to solve the above problems. First, create an abstract Battercake class:

public abstract class Battercake {

    protected abstract String getMsg();

    protected abstract BigDecimal getPrice();

}

Create a basic pancake (or basic package) BaseBattercake class:

public class BaseBattercake extends Battercake {
    @Override
    protected String getMsg() {
        return "A pancake";
    }

    @Override
    protected BigDecimal getPrice() {
        return new BigDecimal(6.00);
    }
}

Then create an abstract decorator battercakedecorator class of the extension package:

public class BattercakeDecorator extends BaseBattercake {

    private Battercake battercake;

    public BattercakeDecorator(Battercake battercake) {
        this.battercake = battercake;
    }

    @Override
    protected String getMsg() {
        return this.battercake.getMsg();
    }

    @Override
    protected BigDecimal getPrice() {
        return this.battercake.getPrice();
    }
}

To create egg decorator class:

public class EggDecorator extends BattercakeDecorator {
    public EggDecorator(Battercake battercake) {
        super(battercake);
    }

    @Override
    protected String getMsg() {
        return super.getMsg() + "Add 1 egg";
    }

    @Override
    protected BigDecimal getPrice() {
        return super.getPrice().add(new BigDecimal(1.00));
    }
}

Create SausageDecorator class:

public class SausageDecorator extends BattercakeDecorator {
    public SausageDecorator(Battercake battercake) {
        super(battercake);
    }

    @Override
    protected String getMsg() {
        return super.getMsg() + "Add 1 sausage";
    }

    @Override
    protected BigDecimal getPrice() {
        return super.getPrice().add(new BigDecimal(2.00));
    }
}

Test code:

public static void main(String[] args) { Battercake battercake; //Buy a pancake at a roadside stall battercake = new BaseBattercake(); //Pancakes are a little small. I want to add another egg battercake = new EggDecorator(battercake); //Add another egg battercake = new EggDecorator(battercake); //I'm hungry. Add a sausage battercake = new SausageDecorator(battercake); System. Out. Println (batchake. Getmsg() + ", total price:" + batchake. Getprice(). Doublevalue()); }

Operation result:

Finally, take a look at the class diagram:

2, The embodiment of decorator pattern in source code

2.1 IO related classes

Decorator mode is also widely used in source code. The most obvious class in JDK is IO related classes, such as BufferedReader, InputStream and OutputStream. Take a look at the class structure diagram of InputStream commonly used:

2.2 decorator pattern in spring

We can also try to understand the TransactionAwareCacheDecorator class in Spring. This class is mainly used to process transaction caching. Here is the code:

public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;
    public TransactionAwareCacheDecorator(Cache targetCache) {
    Assert.notNull(targetCache, "Target Cache must not be null");
    this.targetCache = targetCache;
    }
    public Cache getTargetCache() {
    return this.targetCache;
    }
    ...
}

TransactionAwareCacheDecorator is a wrapper for Cache. Take another look at the decorator pattern HttpHeadResponseDecorator class in spring MVC:

public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
    public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
        super(delegate);
    }

    public final Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        return Flux.from(body).reduce(0, (current, buffer) -> {
            int next = current + buffer.readableByteCount();
            DataBufferUtils.release(buffer);
            return next;
        }).doOnNext((count) -> {
            this.getHeaders().setContentLength((long)count);
        }).then();
    }

    public final Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return this.setComplete();
    }
}

3, Advantages and disadvantages of decorator mode

Advantage:

  • The decorator is a powerful supplement of inheritance, which is more flexible than inheritance and dynamically extends an object's function without changing the original object, namely plug and play;

  • Different effects can be achieved by using different decoration classes and the arrangement and combination of these decoration classes;

  • The decorator fully abides by the opening and closing principle.

Disadvantages:

  • There will be more code, more classes and more program complexity;

  • Dynamic decoration, multi-layer decoration will be more complex.

Topics: Programming Spring JDK