This is the 16th day of my participation in Gwen Challenge

concept

A pattern that dynamically adds responsibilities (that is, additional functionality) to an existing object without changing its structure. It is a structural pattern within design patterns. Its main feature is the decorator pattern that dynamically attaches responsibility to objects. To extend functionality, decorators provide more flexible extensions than inheritance. The Decorator pattern allows you to extend the functionality of an object without creating more subclasses. Corresponding UML:

Based on the class diagram above, we can summarize the roles in decoration mode:

  • Abstract Component Role: Gives an abstract interface to specify objects that are ready to receive additional responsibilities.

  • ConcreteComponent Role: Defines a class that will receive additional responsibilities. (That is, the class that needs decoration)

  • Decorator role: Holds an instance of a Component object and defines an interface consistent with the abstract Component interface.

  • The ConcreteDecorator role: Responsible for “attaching” additional responsibilities to component objects.

The advantages and disadvantages

  1. Both decorator and inheritance are intended to extend the functionality of objects, but decorator can provide more flexibility than inheritance. Decoration mode allows the system to dynamically decide to “paste” a needed decoration or remove an unwanted decoration. Inheritance relationships, on the other hand, are static and are determined before the system runs.
  2. By using different concrete decoration classes and permutations of these decoration classes, designers can create many combinations of different behaviors.
  3. The general problem with adding design patterns is that using decorative patterns produces more objects than using inheritance relationships. Having more objects makes it difficult to find errors.

Code sample

We eat pancakes in the morning and at the subway entrance you can see a lot of people add sausage and egg and spicy sticks to the basic pancakes. But no matter what you add, it’s still a pancake, but it’s “enriched.” So we’re going to simulate this with code, and we’re going to decorate our pancake.

  1. Let’s define an abstract pancake
public interface Pancakes {

    /** * The name of the pancake */
    String pname(a);

}

Copy the code
  1. Object 1: multigrain pancakes
public class ZaliangPancakes implements Pancakes{

    @Override
    public String pname(a) {

        System.out.println("I'm a multigrain pancake.");

        return null; }}Copy the code
  1. Object 2: Red bean pancake
public class HongDouPancakes implements Pancakes{

    @Override
    public String pname(a) {

        System.out.println("I'm a red bean pancake.");

        return null; }}Copy the code
  1. Defining decorator classes
public class DecoratorPancakes implements Pancakes {

    private Pancakes pancakes;

    public DecoratorPancakes(Pancakes pancakes) {
        this.pancakes = pancakes;
    }

    @Override
    public String pname(a) {

        // Delegate to a specific builder
        returnpancakes.pname(); }}Copy the code
  1. Specific decoration, add a sausage
public class KaoChangPanvakes extends DecoratorPancakes {
    public KaoChangPanvakes(Pancakes pancakes) {
        super(pancakes);
    }

    @Override
    public String pname(a) {
        super.pname();
        System.out.println("Add a grilled sausage.");
        return null; }}Copy the code
  1. For specific decorations, add an egg
public class JiDanPanvakes extends DecoratorPancakes {
    public JiDanPanvakes(Pancakes pancakes) {
        super(pancakes);
    }

    @Override
    public String pname(a) {
        super.pname();
        System.out.println("Add an egg.");
        return null; }}Copy the code
  1. Write a client call
public class Client {

    public static void main(String[] args) {
        Pancakes pancakes = new ZaliangPancakes();// Choose multi-grain pancakes
        pancakes = new KaoChangPanvakes(pancakes);		/ / add sausage
        System.out.println(pancakes.pname());
        System.out.println("----- splitter ------");
        Pancakes pancakes2 = new ZaliangPancakes();// Choose multi-grain pancakes
        pancakes2 = new JiDanPanvakes(pancakes2);		/ / add the eggs
        System.out.println(pancakes2.pname());
        System.out.println("----- splitter ------");

        Pancakes pancakes3 = new ZaliangPancakes();// Choose multi-grain pancakes
        pancakes3 = new KaoChangPanvakes(new JiDanPanvakes(pancakes3));// It comes with egg and sausageSystem.out.println(pancakes3.pname()); }}Copy the code
  1. The results of

I’m a multigrain pancake with a sausage

—– split line ——

I’m a multigrain pancake with an egg

—– split line ——

I’m a multigrain pancake with an egg and a sausage

Application of decorative patterns

One of the most common decorator applications we develop is the IO part of Java. Since Java I/O libraries require many combinations of capabilities, if these capabilities are implemented using inherited methods, each combination requires a class, resulting in a large number of classes with repetitive performance. If decorator pattern is used, the number of classes is greatly reduced and the duplication of performance is minimized. The decorator pattern is therefore the basic pattern of the Java I/O library. Let’s look at the InputStream class:

Let’s take a quick look at the characters:

  • Abstract Component role: Played by InputStream. This is an abstract class that provides a unified interface for subtypes.
  • ConcreteComponent roles: Played by ByteArrayInputStream, FileInputStream, PipedInputStream, StringBufferInputStream, etc. They implement the interfaces specified by the abstract component role.
  • Decorator role: Played by FilterInputStream. It implements the interface specified by InputStream.
  • ConcreteDecorator role: Played by classes BufferedInputStream, DataInputStream, and two less commonly used classes LineNumberInputStream and PushbackInputStream.

Based on the above analysis, let’s take a look at the corresponding code for InputStream we often use:

        // Stream reads files
        DataInputStream dis = null;
        try{
            dis = new DataInputStream(
                    new BufferedInputStream(
                            new FileInputStream("a.txt")));// Read the contents of the file
            byte[] bs = new byte[dis.available()];
            dis.read(bs);
            String content = new String(bs);
            System.out.println(content);
        }catch (Exception e) {
            e.printStackTrace();
        } finally{
            dis.close();
        }
Copy the code

So this is a little bit familiar, the innermost layer is a FileInputStream object, and we pass it to a BufferedInputStream object, BufferedInputStream, It then passes the processed object to the DataInputStream, which is essentially the assembly of the decorator. The FileInputStream is the original decorated object, The BufferedInputStream and DataInputStream objects act as decorators.