【一起学系列】之剩下的设计模式们

1,649 阅读6分钟

前言

【开发】:老大,你教了我挺多设计模式的,已经全部教完了吗?

【BOSS】:没呢,还有好几个设计模式没说过呢,今天再传授你三个吧,分别是建造者模式,责任链模式,备忘录模式,如何?

【开发】:好啊,我最喜欢学习了!

建造者模式

意图

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

核心代码

定义建造接口

public interface Builder {

    void buildPartOne();

    void buildPartTwo();

    void buildPartThr();

    /***
     * 一般情况肯定是一个复杂的对象
     */
    String getResult();
}

定义实际建造工人

public class ConcreteBuilder implements Builder {

    private StringBuffer buffer = new StringBuffer();

    @Override
    public void buildPartOne() {
        buffer.append("i am part one\n");
    }

    @Override
    public void buildPartTwo() {
        buffer.append("i am part two\n");
    }

    @Override
    public void buildPartThr() {
        buffer.append("i am part Thr\n");
    }

    @Override
    public String getResult() {
        return buffer.toString();
    }
}

如何创建不同的表示?

定义督公

public class Director {

    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void setBuilder(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildPartOne();
        builder.buildPartTwo();
        builder.buildPartThr();
    }
}

模拟调用

public class App {

    /***
     * 建造者模式
     *     建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
     *
     * 主要解决
     *     主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定
     *
     * 何时使用
     *      一些基本部件不会变,而其组合经常变化的时候。
     *
     * 如何解决
     *     将变与不变分离开。
     *
     * 关键代码
     *     建造者:创建和提供实例
     *     建造者接口:依赖接口编程
     *     指导者:管理建造出来的实例的依赖关系
     *     产品:建造者所生产的产品
     * 建造者作为参数进入指导者构造方法,通过特定普遍的构造顺序或算法执行,得到产品
     *
     * 应用实例:
     *     1.去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"
     *     2.StringBuilder
     */
    public static void main(String[] args){
        // 创建建造者接口并指向具体建造者 - 包含最终产品
        Builder concreteBuilder = new ConcreteBuilder();

        // 创建指导者, 把具体建造者即工人作为参数传入, 通过统一方法执行相应的构建命令
        Director director = new Director(concreteBuilder);
        director.construct();

        // 从工人即具体建造者获取产品
        String result = concreteBuilder.getResult();
        System.out.println(result);
    }
}

建造者的延展思考:链式调用

链式调用让代码更优雅~

public class MyBuilder {

    // 省略不必要的代码

    MyBuilder withName(String name) {
        this.setName(name);
        return this;
    }


    MyBuilder withYear(String year) {
        this.setYear(year);
        return this;
    }


    MyBuilder withSex(String sex) {
        this.setSex(sex);
        return this;
    }
}

UML图

代码见下方~

责任链模式

意图

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连城一条链,并沿着这条链传递该请求,直到有一个对象处理它为止

典型应用

Log4J 日志系统即是使用了责任链的思想,通过不同日志级别的传递,按级别处理日志

简单实现一个Log等级系统

抽象类

定义日志等级,设置下一个处理器,抽象出写入方法

public abstract class AbstractLogger {

    // 责任级别
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;

    // 当前级别
    int level;

    //责任链中的下一个元素
    AbstractLogger nextLogger;
    public void setNextLogger(AbstractLogger nextLogger){
        this.nextLogger = nextLogger;
    }

    // 记录日志
    public void logMessage(int level, String message){
        if(this.level <= level){
            write(message);
        }

        if(nextLogger != null){
            nextLogger.logMessage(level, message);
        }
    }

    // 抽象方法 -> 重写具体日志输出类型
    abstract protected void write(String message);
}

具体日志类

public class InfoLoger extends AbstractLogger {

    public InfoLoger(int level){
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("InfoLoger Console::Logger: " + message);
    }
}

为了避免重复,只展示一个类

实际调用

public class App {
    
    public static void main(String[] args){
        AbstractLogger log = getChainOfLoggers();
        log.logMessage(AbstractLogger.INFO, "i am info");
        log.logMessage(AbstractLogger.DEBUG, "i am debug");
        log.logMessage(AbstractLogger.ERROR, "i am error");
    }

    private static AbstractLogger getChainOfLoggers(){
        AbstractLogger error = new ErrorLoger(AbstractLogger.ERROR);
        AbstractLogger debug = new DebugLoger(AbstractLogger.DEBUG);
        AbstractLogger info = new InfoLoger(AbstractLogger.INFO);

        error.setNextLogger(debug);
        debug.setNextLogger(info);
        return error;
    }
}

// 输出结果:
// InfoLoger Console::Logger: i am info
//
// ------------------------
//
// DebugLoger Console::Logger: i am debug
// InfoLoger Console::Logger: i am debug
//
// ------------------------
//
// ErrorLoger Console::Logger: i am error
// DebugLoger Console::Logger: i am error
// InfoLoger Console::Logger: i am error

总结

多种形式

  • 当前pattern下类似日志级别形式, 只要等级比A大,那B,C都会处理
  • 如A->B->C 由低到高级别执行,只要执行就返回等
  • 最高级形式: 低级发起请求后, 高级任一处理后,请求反馈即可(涉及到异步相关,线程通信)

优点

  • 降低耦合度。它将请求的发送者和接收者解耦
  • 简化了对象。使得对象不需要知道链的结构
  • 增强给对象指派职责的灵活性,通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任
  • 增加新的请求处理类很方便

缺点

  • 不能保证请求一定被接收
  • 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用
  • 可能不容易观察运行时的特征,有碍于除错

使用场景

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
  • 可动态指定一组对象处理请求

UML图

代码见下方~

备忘录模式

意图

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后可将对象恢复到原先保存的状态

核心代码

备忘录

/**
 * ******************************
 * description:  备忘录,确定数据结构即可
 * ******************************
 */
public class Memento {
    Map<String, String> data;
}

模拟短信场景

/**
 * ******************************
 * description:  模拟短信场景
 * ******************************
 */
public class MessageData {

    private String time;

    private String message;

    /**
     * 存储数据
     */
    public Memento saveMemento () {
        Map<String, String> map = Maps.newHashMap();
        map.put("TIME",    time);
        map.put("MESSAGE", message);
        return new Memento(map);
    }

    /**
     * 取出数据
     */
    public void getFromMemento(Memento memento){
        time    = memento.getData().get("TIME");
        message = memento.getData().get("MESSAGE");
    }

    // 省略部分代码
}

备忘录存储容器

public class MementoTaker {

    private List<Memento> mementoList = new ArrayList<>();

    public void add(Memento state){
        mementoList.add(state);
    }

    public Memento get(int index){
        return mementoList.get(index);
    }
}

核心调用代码

public class App {
    public static void main(String[] args) throws InterruptedException {

        // 创建备忘录管理者
        MementoTaker mementoTaker = new MementoTaker();

        MessageData messageData = new MessageData();
        messageData.setTime(System.currentTimeMillis() + "");
        messageData.setMessage("This is messgae first.");
        mementoTaker.add(messageData.saveMemento());

        System.out.println("First: -> " + messageData);

        Thread.sleep(2000);

        messageData.setTime(System.currentTimeMillis() + "");
        messageData.setMessage("This is messgae second.");
        mementoTaker.add(messageData.saveMemento());

        System.out.println("Second: -> " + messageData);

        Thread.sleep(2000);

        // 回复初次状态
        messageData.getFromMemento(mementoTaker.get(0));

        System.out.println("********************检测数据是否回到初始状态******************");
        System.out.println(messageData);
    }
}

模式总结:其实该模式非常简单,即确定好数据结构在容器中存储一份,以便后续恢复,或者重新使用等等

总结

主要解决

所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态

备忘录思想的实践

  • 打游戏时的存档
  • Windows 里的 ctri + z
  • 数据库的事务管理

UML图

相关代码链接

GitHub地址

  • 兼顾了《HeadFirst》以及《GOF》两本经典书籍中的案例
  • 提供了友好的阅读指导