Advanced learning journey - design mode (decorator mode & observer mode)

Posted by ronniebrown on Tue, 28 Dec 2021 07:06:05 +0100

1. Course objectives

1. Master the characteristics and application scenarios of decorator mode.

2. Master the fundamental difference between decorator mode and adapter mode.

3. Application and implementation principle of observer mode in source code.

4. Understand the advantages and disadvantages of decorator mode and observer mode.

2. Decorator mode

2.1 definition of decorator mode

Decorator pattern refers to attaching functions to objects without changing the original objects, providing a more flexible alternative than inheritance (expanding the functions of the original objects).

It belongs to structural mode

2.2 applicable scenarios of decorator mode

1. It is used to expand the functions of a class or add additional responsibilities to a class.

2. Dynamically add functions to an object, and these functions can be dynamically revoked

2.3 Demo cases

Case of buying pancakes

v1: traditional scheme

@Data
public class Battercake {
    public String getMsg (){
        return "A pancake";
    }

    public int getPrice(){
        return 5;
    }
}


/**
 * @PackageName: com.raven.pattern.decorator.battercake.v1
 * @ClassName: BattercakeWithEgg
 * @Blame: raven
 * @Date: 2021-08-07 18:52
 * @Description: A pancake and an egg
 */
public class BattercakeWithEgg extends Battercake{
    @Override
    public String getMsg() {
        return super.getMsg() + "+ An egg";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v1
 * @ClassName: BattercakeWithSausage
 * @Blame: raven
 * @Date: 2021-08-07 18:54
 * @Description: One PANCAKE + one ham
 */
public class BattercakeWithSausage extends Battercake {
    @Override
    public String getMsg() {
        return super.getMsg() + "+ A ham";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 2;
    }
}


package com.raven.pattern.decorator.battercake.v1;

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v1
 * @ClassName: BattercakeTest
 * @Blame: raven
 * @Date: 2021-08-07 18:55
 * @Description:The traditional way of test class expands the original class "Battercake", and new classes need to be created constantly
 * When the requirements change slightly, you need to create a new class to meet the existing requirements
 */
public class BattercakeTest {

    public static void main(String[] args) {
        Battercake battercake = new Battercake();
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");

        battercake = new BattercakeWithEgg();
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");

        battercake = new BattercakeWithSausage();
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");

        // If you need to buy pancakes with two eggs and two hams, you need to create a new class. With the change of needs, there will be more and more classes.

    }
}

V2: use decorator mode

package com.raven.pattern.decorator.battercake.v2;

import lombok.Data;

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v1
 * @ClassName: Battercake
 * @Blame: raven
 * @Date: 2021-08-07 18:49
 * @Description:Simulated sales pancake case pancake entity
 */
@Data
public abstract class Battercake {
    /**
     * Purchase pancake details
     * @return
     */
    public abstract String getMsg();

    /**
     * Pancake price
     * @return
     */
    public abstract int getPrice();
}

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v2
 * @ClassName: BaseBattercake
 * @Blame: raven
 * @Date: 2021-08-07 19:03
 * @Description:Describe the information of a basic pancake
 */
public class BaseBattercake extends Battercake {
    @Override
    public String getMsg() {
        return "A pancake";
    }

    @Override
    public int getPrice() {
        return 5;
    }
}

Decorator

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v2
 * @ClassName: BattercakeDecorator
 * @Blame: raven
 * @Date: 2021-08-07 19:05
 * @Description: The decorator parent class of pancake regulates the behavior of egg decorator,
 */
public class BattercakeDecorator extends Battercake {

    private Battercake battercake;

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

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

    @Override
    public int getPrice() {
        return this.battercake.getPrice();
    }
}

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v2
 * @ClassName: EggDecorator
 * @Blame: raven
 * @Date: 2021-08-07 19:10
 * @Description: Garnish with 1 egg
 */
public class EggDecorator extends BattercakeDecorator {
    public EggDecorator(Battercake battercake) {
        super(battercake);
    }

    @Override
    public String getMsg() {
        return super.getMsg() + "+ An egg";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 1;
    }
}

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v2
 * @ClassName: SausageDecorator
 * @Blame: raven
 * @Date: 2021-08-07 19:13
 * @Description:
 */
public class SausageDecorator extends BattercakeDecorator {
    public SausageDecorator(Battercake battercake) {
        super(battercake);
    }

    @Override
    public String getMsg() {
        return super.getMsg() + "+ A ham";
    }

    @Override
    public int getPrice() {
        return super.getPrice() + 2;
    }
}


main

/**
 * @PackageName: com.raven.pattern.decorator.battercake.v2
 * @ClassName: BattercakeTest
 * @Blame: raven
 * @Date: 2021-08-07 19:13
 * @Description: Pancake selling case encapsulated by decorator mode
 */
public class BattercakeTest {

    public static void main(String[] args) {
        // After wrapping in decorator mode, you can buy + pancakes of different styles, and there is no need to create more classes after the requirements change
        Battercake battercake = new BaseBattercake();
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");

        battercake = new EggDecorator(battercake);
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");

        battercake = new EggDecorator(battercake);
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");

        battercake = new SausageDecorator(battercake);
        System.out.println("buy" + battercake.getMsg() + "Total need" + battercake.getPrice() + "element");
    }
}

2.4 comparison between decorator mode and adapter mode

\Decorator modeAdapter mode
formIs a very special adapter patternThere is no hierarchical relationship, and the decorator mode has hierarchical relationship
definitionThe decorator and the dresser have implemented the same interface, and the main purpose is to retain the OOP relationship after expansionThe adapter has no necessary connection with the Adaptee, and is usually wrapped in the form of inheritance or proxy
relationshipSatisfy is-a relationSatisfy has-a relation
functionFocus on coverage and expansionFocus on compatibility and conversion
DesignPre considerationPost consideration

2.5 decorator mode in the source code

Decorator mode in jdk: IO stream

Decorator mode in spring:
TransactionAwareCacheDecorator,

HttpHeadResponseDecorator

Decorator mode in mybatis:
LruCache,FifoCache

2.6 advantages of decorator mode

1. Decorator mode is a powerful supplement to inheritance, which is more flexible than inheritance. It dynamically expands the function of an object without changing the original object, plug and play

2. Different effects can be achieved by using decorative classes and the arrangement and combination of these decorative classes

3. The decorator shall fully abide by the opening and closing principle.

2.7 disadvantages of decorator mode

1. More codes and classes will appear, increasing the complexity of the program.

2. In dynamic decoration, multi-layer decoration will be more complex.

2.8 difference between decorator mode and static agent

1. The main difference between decorator mode and static agent is different responsibilities

2. Static agents do not have to meet the is-a relationship. Static agents will enhance their functions, and the same responsibility will become different

3. The decorator mode is more about expansion

3. Observer mode

3.1 definition of observer mode

1. Observer pattern defines one to many dependencies between objects, allowing multiple observer objects to listen to a subject object at the same time. When the subject object sends a change, all its dependents (observers) will receive a notification and update.

2. It belongs to behavioral model.

3. Observer mode is sometimes called publish subscribe mode.

3.2 Demo case

Community Q & A cases:

Observed (producer):

/**
 * @PackageName: com.raven.pattern.observer.advice
 * @ClassName: Coder
 * @Blame: raven
 * @Date: 2021-08-07 19:30
 * @Description: The observer is implemented by inheriting Observable, which is an observer implementation method provided by JDK
 */
@Data
public class Coder extends Observable {

    private String name = "Coder Ecosphere";
    private static volatile Coder coder = null;

    private Coder() {
    }

    public static Coder getInstance() {
        if (Objects.isNull(coder)) {
            synchronized (Coder.class) {
                if (Objects.isNull(coder)) {
                    coder = new Coder();
                }
            }
        }
        return coder;
    }

    public void publishQuestion(Question question) {
        System.out.println(question.getUsername() + "stay" + name + "Asked a question");
        // Submit questions through this access
        setChanged();
        // Inform all observers of this class to pass the problem on
        notifyObservers(question);
    }

}

@Data
public class Question {
    private String username;

    private String content;
}

Observer (consumer)

public class Teacher implements Observer {
    private String name;

    public Teacher(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        // Observed
        Coder coder = (Coder) o;
        System.out.println("=================");
        Question question = (Question) arg;
        System.out.println(name + "Teacher, where are you" + coder.getName() + "I got it"+question.getUsername()+"Questions raised,\n"
        + "The content of the question is:\n" + question.getContent());
    }
}

main

/**
 * @PackageName: com.raven.pattern.observer.advice
 * @ClassName: ObserverTest
 * @Blame: raven
 * @Date: 2021-08-07 19:52
 * @Description: JDK Observer mode demo test class
 */
public class ObserverTest {
    public static void main(String[] args) {

        // Get an observed coder
        Coder coder = Coder.getInstance();
        Question question = new Question();
        question.setContent("How to learn design patterns well?");
        question.setUsername("Xiao Wang");

        // Create an observer teacher
        Teacher teacher = new Teacher("raven");

        // Bind observer and observed
        coder.addObserver(teacher);

        // The observed person submits the problem
        coder.publishQuestion(question);
    }
}

Event event listening case

main

public class MouseEventTest {

    public static void main(String[] args) throws NoSuchMethodException {
        MouseEventCallback callback = new MouseEventCallback();

        Mouse mouse = new Mouse();

        mouse.addListener(MouseEventType.ON_CLICK, callback);

        mouse.click();
        mouse.doubleClick();
        // When the addListener method is called, the event named onClick is registered in the events event management

        // When the click method is called, the registered events in events will be executed, and the unregistered events will not be triggered
    }
}

event

@Data
public class Event {

    /**
     * Event source, who initiated the event
     */
    private Object source;

    /**
     * Who should be notified when the event is triggered
     */
    private Object target;

    /**
     * What action should be taken when the event is triggered
     */
    private Method callback;

    /**
     * Event trigger, what event to trigger
     */
    private String trigger;

    private long time;

    public Event(Object target, Method callback) {
        this.target = target;
        this.callback = callback;
    }
}

public class EventListener {

    /**
     * JDK The bottom listener is usually designed like this
     */
    private Map<String, Event> eventMap = Maps.newHashMap();

    public void addListener(String eventType, Object target) {
        try {
            this.addListener(eventType,
                    target,
                    target.getClass().getMethod("on" + toUpperFirstCase(eventType),Event.class));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private String toUpperFirstCase(String eventType) {
        char[] chars = eventType.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }

    /**
     * Add an event to the event list
     * @param eventType
     * @param target
     * @param callback
     */
    private void addListener(String eventType, Object target, Method callback) {
        eventMap.put(eventType, new Event(target, callback));
    }

    /**
     * Event name trigger
     * @param trigger
     */
    public void trigger(String trigger){
        if (eventMap.containsKey(trigger)){
            this.eventMap.get(trigger).setTrigger(trigger);
            trigger(this.eventMap.get(trigger));
        }
    }

    private void trigger(Event event){
        event.setSource(this);
        event.setTime(System.currentTimeMillis());
        try {
            event.getCallback().invoke(event.getTarget(),event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

public class Mouse extends EventListener {

    public void click(){
        System.out.println("Call click method");
        trigger(MouseEventType.ON_CLICK);
    }

    public void doubleClick(){
        System.out.println("Call the double-click method");
        trigger(MouseEventType.ON_DOUBLE_CLICK);
    }
}

public interface MouseEventType {

    String ON_CLICK = "click";

    String ON_DOUBLE_CLICK = "doubleClick";


}

EventCallback

public class MouseEventCallback {

    public void onClick(Event event){
        System.out.println("=======Trigger mouse click event======" + "\n" + event);
    }
    public void onDoubleClick(Event event){
        System.out.println("=======Trigger mouse double click event======" + "\n" + event);
    }
}

guava

guava event

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>${guava.version}</version>
</dependency>

GuavaEvent

public class GuavaEvent {

    @Subscribe
    public void register(String name){
        System.out.println(name + "Joined coder Forum!");
    }
}

main

public class GuavaEventTest {

    public static void main(String[] args) {

        // Message bus
        EventBus eventBus = new EventBus();

        // Message event
        GuavaEvent guavaEvent = new GuavaEvent();

        // Register message events
        eventBus.register(guavaEvent);

        // Send message, receive in real time and trigger event
        eventBus.post("Zhan San");

        // The obServer of jdk and the Event in spring are class oriented, and guava is method oriented.
    }
}

3.3 applicable scenarios of observer mode

Observer mode is mainly used to establish a set of trigger mechanisms between related behaviors

3.4 advantages of observer mode

1. An abstract coupling is established between the observer and the observed.

2. Observer mode supports broadcast communication.

3.5 disadvantages of observer mode

1. There is too much detail dependence between observers, which increases time consumption and program complexity.

2. Use properly and avoid circular call

3.6 application of observer mode in source code

contextLoaderListener

Topics: Java Design Pattern