23种设计模式全面总结 | 快速复习(附PDF+MD版本)

本篇文章是对于23种设计模式的一个全面的总结,受限于文章篇幅无法对每个设计模式做到全面的解析,但几乎每个设计模式都提供了案例和类图结构,非常适合快速复习和在学习设计模式之前的全预习把握。
💡文章的 pdf + markdown 版本可通过链接获取:设计模式

文章目录

    • 单例模式(Singleton Pattern)
    • 简单工厂模式(Simple Factory Pattern)
    • 工厂模式(Factory Pattern)
    • 抽象工厂模式(Abstract Factory Pattern)
    • 装饰器模式(Decorator Pattern)
    • 适配器模式(Adaptor Pattern)
    • 观察者模式(Observer Pattern)
    • 外观模式(Facade Pattern)
    • 状态模式(State Pattern)
    • 策略模式(Strategy Pattern)
    • 代理模式(Proxy Pattern)
    • 责任链模式(Chain Of Responsibility Pattern)
    • 模板方法模式(Template Method Pattern)
    • 享元模式(FlyWeight Pattern)
    • 命令模式(CommandPattern)
    • 原型模式(Prototype Pattern)
    • 备忘录模式(Memento Pattern)
    • 迭代器模式(Iterator Pattern)
    • 组合模式(Composite Pattern)
    • 桥接模式(Bridge Pattern)
    • 中介者模式(Mediator Pattern)
    • 访问者模式(Visitor Pattern)
    • 解释器模式(Interpreter Pattern)

单例模式(Singleton Pattern)

确保一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,也就表示它的构造方法不对外公开,而是通过暴露一个 static 的 getInstance 方法来进行实例对象的获取。

根据类的实例化时机可以分为饿汉式和懒汉式

  • 饿汉式即在类初始化的时候就进行实例化
class SignalPattern {
    private final static SignalPattern signalPattern = new SignalPattern();

    private SignalPattern() {}
    
    public static SignalPattern getInstance() {
        return signalPattern;
    }
}
  • 懒汉式则是在类第一次被使用的时候才进行实例化
class SignalPattern {
    private static SignalPattern signalPattern;

    private SignalPattern() {}

		// 加锁保证时候实现一次
    public synchronized static SignalPattern getInstance() {
        if (signalPattern == null) {
            signalPattern = new SignalPattern();
        }
        return signalPattern;
    }
}

观察上面的懒汉式获取实例的方法中,如果我们将锁加到方法体的外面,在多线程的情况下,线程获取对象实例的时候必须等待其他线程获取完成,这个过程是非常冗余的,所以可以考虑将锁放到方法体中,来减小锁的控制范围,来构造一个双重检查锁。

比如上面的方法中,就可以将锁放置到 if 语句内,最终的效果是这样的:

class SignalPattern {
    private volatile static SignalPattern signalPattern;
    
    private SignalPattern() {}
    public static SignalPattern getInstance() {
        if (signalPattern == null) {
            synchronized (SignalPattern.class) {
                if (signalPattern == null) {
                    signalPattern = new SignalPattern();
                }
            }
        }
        return signalPattern;
    }
}

里面进行了两次判断,这也就是为什么它被称为双重检查锁;此时就只有第一次获取到实例为空的时候才会使用到锁;但熟悉双重检查锁的朋友一定知道双重检查锁必须配合 volatile 关键字来使用,这是为什么呢?

为了提升程序运行的性能,编译器和处理器会选择对要执行的指令进行重排序,比如上面的 new SignalPattern() 构造方法其实是由三个指令构成的:1)分配内存 2)初始化对象 3)指向刚刚分配的地址;指令重排序可能会打乱上面的顺序,使得执行的情况可能会变成这样 1 → 3 → 2,这样的重排在单线程的情况下是没有任何影响的,但是在多线程的情况下就会产生影响,甚至导致严重的安全性问题。

比如在上面的实例获取方法中,如果 3 在 2 之前先执行,即使对象没有初始化完成,此时其他线程同样会判断 signalPattern 是非空的,从而拿到一个不完整的错误对象,此时就出现了问题;那么应该如何避免这种情况呢?就是通过 volatile 关键字,volatile 关键字可以保证:当写 volatile 关键字修饰的变量,可以确保 volatile 写之前的操作不会被编译器重排序到 volatile 写之后。

通过这个关键字就能保证 volatile 关键字修饰的变量写入顺序的正确性,从而避免了双重检查锁由于指令重排序导致的安全问题。

单例模式可以保证项目中使用的是一个实例,从而避免了重复的实例化导致的性能开销,但同样的,它的问题也是由于只有一个实例,所以一个线程去操控它的变量的时候,其他线程也可以同时操作,所以单例模式的表现形式应该是无状态的,以工具类的形式呈现。

在 Spring 框架中,默认情况下,Spring 容器使用单例模式(Singleton)来管理 bean 的依赖。这意味着,对于每个定义的 bean,Spring 容器会创建并维护一个单一的实例,并在每次需要该 bean 时返回这个实例。

与单例相对,Spring 也支持原型作用域,这意味着每次请求该 bean 时,都会创建一个新的实例,可以通过注解的方式修改

@Scope("prototype")
@Component
public class MyClass {
    // ...
}

简单工厂模式(Simple Factory Pattern)

有称为静态工厂方法,它属于类创建型模式,在简单工厂模式中,可以根据参数的不同返回不同的类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,创建的实例通常都拥有一个共同的父类。

在这里插入图片描述

比如常用的日期格式化类 DateFormat 中就使用了工厂模式来管理类的创建:

在这里插入图片描述

public class SimpleFactory {
    // 管理 Product 的创建
    public static Product getProduct(String type) {
        switch (type) {
            case "A": return new ProductA();
            case "B": return new ProductB();
        }
        throw new RuntimeException("type error");
    }
    public static void main(String[] args) {
        Product a = getProduct("A");
        a.print();
    }
}
abstract class Product{
    public abstract void print();
}
class ProductA extends Product{
    @Override
    public void print() {
        System.out.println("A");
    }
}
class ProductB extends Product{
    @Override
    public void print() {
        System.out.println("B");
    }
}

工厂类的优点是使用者不用关心类是如何创建的,这个过程交给工厂类负责,缺点就是工厂类不够灵活,新增或者删除其创建的实例都需要去修改代码。

工厂模式(Factory Pattern)

先来回顾一下简单工厂模式的缺点,简单工厂模式需要知道类的构造细节,所以在对类进行修改或者新增的时候,势必会导致简单工厂类业务代码的修改,这就违背了软件设计原则的开闭原则,即一个软件的实体,如 类、模块 等,应该对扩展开放,对修改关闭;导致了无法灵活的拓展。

在工厂模式中,核心工厂变为了一个抽象的接口,它不再负责实例的创建,而是定义创建实例需要实现的方法,交给它的子类去完成创建,也就是子工厂。

现在就可以给出工厂模式的定义了,也就是定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使得类的实例化延迟到了它的子类;最终的类图结构是这样的:

在这里插入图片描述

在 JDK 中也大量使用到了工厂模式,比如我们常见的集合类,里面定义了一个 iterator 方法,用于创建迭代器,而这个创建就是延迟到子类中去实现的。

抽象工厂模式(Abstract Factory Pattern)

由于工厂模式在父工厂中定义了一个抽象的实例化方法,这也就导致了通过工厂模式只能创建一类的实例,这是工厂模式的限制;而抽象工厂类将解决了这个问题。

在这里插入图片描述

也就是提供多个不同的接口来实现不同大类的创建。

但这样将引起了一个问题,当我们去新增工厂能够创建的类的时候,又必须去修改抽象的父工厂,又回到了之前简单工厂的问题,即违反了开闭原则。

装饰器模式(Decorator Pattern)

动态的给对象添加一些额外的功能,就添加功能来说,装饰模式比生成子类更加灵活。

它的类图结构是这样的

在这里插入图片描述

实际的实现类 ConcreteComponent 和 抽象的装饰器类 都需要实现 Component 接口

抽象装饰器类又引用了装饰器接口的实现类来辅助实现接口

具体装饰器 ConcreteDecorator 继承自抽象装饰器类,可以添加多种功能。

来看一个具体的案例

public class DecoratorPattern {
    public static void main(String[] args) {
        PhonePro phonePro = new PhonePro(new Phone1());
        phonePro.call();
        phonePro.takePhoto(); // 拓展的新功能
    }
}
// 接口
interface Phone { void call();}
// 最初的实现类
class Phone1 implements Phone {
    @Override
    public void call() {
        System.out.println("通话中......");
    }
}
// 抽象装饰器类
abstract class PhoneDecorator implements Phone {
    private final Phone phone;
    PhoneDecorator(Phone phone) {
        this.phone = phone;
    }
    @Override
    public void call() {
        phone.call();
    }
}
// 装饰器类
class PhonePro extends PhoneDecorator {

    PhonePro(Phone phone) {
        super(phone);
    }
    public void takePhoto() {
        System.out.println("拍照中");
    }
}

在上面的案例中,我们使用了装饰器模式在原有手机功能的基础上拓展了拍照功能。

适配器模式(Adaptor Pattern)

将一个类的接口变化成客户端期待的另一种接口,从而是的原本因为接口不匹配而无法在一起工作的两个类能够在一起工作。

在这里插入图片描述

中文使用者和英文使用者交流的时候因为语言不通需要一个翻译官(适配器),翻译官将中文使用者的话翻译(translate)成英文来让英文使用者能够听懂(使得接口匹配),能够完成工作,写出代码就是这样的:

public class AdapterPattern {
    public static void main(String[] args) {
    // 正常使用
        Adapter adapter = new Adapter(new Speaker());
        System.out.println(adapter.translate());
    }
}
// 中文
class Speaker {
    public String speak() {
        return "你好";
    }
}
interface Translator {
    String translate();
}
// 翻译者
class Adapter implements Translator {
    private final Speaker speaker;

    public Adapter(Speaker speaker) {
        this.speaker = speaker;
    }

    @Override
    public String translate() {
        return speaker.speak() + "->" + "hello";
    }
}

观察者模式(Observer Pattern)

定义了对象之间的一种一对多的关系,是的每当一个对象状态发生改变的时候,其相关的依赖对象都得到通知并且自动被更新。

public class ObserverPattern {
    public static void main(String[] args) {
        Factory factory = new Factory();
        UserImpl user1 = new UserImpl("张三");
        UserImpl user2 = new UserImpl("李四");
        factory.getOrder(user1);
        factory.getOrder(user2);
        factory.notifyUser();
    }
}
interface Producer {
    /**
     * 处理订单
     */
    void getOrder(User user);

    /**
     * 提醒取货
     */
    void notifyUser();
}
// 工厂
class Factory implements Producer {
    private static final List<User> orders = new ArrayList<>();
    @Override
    public synchronized void getOrder(User user) {
        orders.add(user);
    }

    @Override
    public synchronized void notifyUser() {
        for (User user : orders) {
            user.get();
        }
    }
}
interface User{
    void get();
}
// 取货用户
class UserImpl implements User{
    String name;
    UserImpl(String name) {this.name = name;}
    @Override
    public void get() {
        System.out.println(this.name + "取货");
    }
}

上面的 factory 被称为主题对象,user 就是观察者,主题对象中获取观察者,并且通过调用观察者的方法来通知观察者。

外观模式(Facade Pattern)

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,外观模式提供一个更高层次的接口,是的子系统更加容易使用。

比如当你需要请假的时候,需要校医院出示证明、辅导员签字、学院审核;但是经过学校的整改以后,这些这些步骤可以交给学院的医生进行,医生协助你完整整个流程,将复杂的细节隐藏起来。这个医生就是那个更高层次的接口,负责外部(请假学生)与内部(学校)的通信。

在这里插入图片描述

public class FacadePattern {
    public static void main(String[] args) {
        CollegeDoctor collegeDoctors = new CollegeDoctor();
        System.out.println(collegeDoctors.leave());
    }
}
class Hospital {
    static boolean hospitalProve() {
        System.out.println("医院证明");
        return true;
    }
}
class Counselor {
    static boolean CounselorSignature() {
        System.out.println("辅导员签字");
        return true;
    }
}
class College {
    static boolean CollegeReview() {
        System.out.println("学院审核");
        return true;
    }
}
interface Facade {
    boolean leave();
}
class CollegeDoctor implements Facade {

    @Override
    public boolean leave() {
        boolean a = Hospital.hospitalProve();
        boolean b = Counselor.CounselorSignature();
        boolean c = College.CollegeReview();
        return a && b && c;
    }
}

状态模式(State Pattern)

允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

在这里插入图片描述

public class StatePattern {
    public static void main(String[] args) {
        Worker worker = new Worker();
        worker.work();
        worker.changeState(new Happy());
        worker.work();
    }
}
class State {
    void handle() {
        System.out.println("工作");
    };
}
class Happy extends State {
    @Override
    void handle() {
        System.out.println("高兴的工作");
    }
}
class Sad extends State {
    @Override
    void handle() {
        System.out.println("伤心的工作");
    }
}
class Mad extends State {
    @Override
    void handle() {
        System.out.println("愤怒的工作");
    }
}
class Calm extends State {
    @Override
    void handle() {
        System.out.println("平静的工作");
    }
}
class Worker {
    private State state = new State();
    public void changeState(State state) {
        this.state = state;
    }
    public void work() {
        state.handle();
    }
}

在上面的案例中,工人工作有四种状态,如果按照一般的方式,需要写四个 if-else 分支语句,上面通过定义 State 和它的实现字类,在其内部实现工作的具体流程。

策略模式(Strategy Pattern)

定义一组算法,将每个算法都封装起来,并且使得它们之间可以相互转化,策略模式算法独立于使用它的客户而变化,称之为政策(Policy)模式。

在这里插入图片描述

策略模式的类图和上面的状态模式几乎完全相同,但是与状态模式关注状态不同,策略模式关注的是某个行为的实现方式。

比如上面状态模式的案例中,由于状态不同,可能会导致一系列方法执行的不通,除了工作行为还可能影响休息、吃饭等行为;但是策略模式比起你此时的状态,更关心的是你某个行为的实现方式,比如你如何工作,如何休息。

举一个策略模式的案例,比如说商场的打折活动,根据不同的打折策略计算出来的价值也不同,此时就可以定义多种策略,通过在计算的时候传入不同的策略对象来实现最终结果的计算。

比如在线程池的构造方法中,有这样的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

其中的参数 handler 就是一种拒绝策略,

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

传入的是该接口的实现类,子类中实现了具体的拒绝策略。

代理模式(Proxy Pattern)

为其他对象提供一种这个对象的代理,以控制对这个对象的访问。

代理模式中其他对象无法直接访问这个对象,必须通过代理的方式来访问,这样就控制了访问的途径;比如我们日常生活中使用的网络代理就是这种模式。因为有一些服务器我们无法直接访问,所以可以通过代理服务器替我们转发请求,并且将返回内容再传输给我们的方式来实现访问。

在这里插入图片描述

public class ProxyPattern {
    public static void main(String[] args) {
        RealProjectProxy realProjectProxy = new RealProjectProxy();
        System.out.println(realProjectProxy.getImage());
    }

}
interface Subject {
    String getImage();
}
class RealSubject implements Subject {
    @Override
    public String getImage() {
        return "小猫图片";
    }
}
class RealProjectProxy implements Subject{
    private static final Subject subject = new RealSubject();
    
    private void connect() {
        System.out.println("建立连接");
    }
    
    private void log() {
        System.out.println("记录日志");
    }

    @Override
    public String getImage() {
        connect();
        String image = subject.getImage();
        log();
        return image;
    }
}

在上面的案例中,我们需要去 RealSubject 中去获取图片,此时通过代理类去代理访问,并且建立链接和记录日志。

代理模式和装饰器模式可能会有些类似,但它们的关注点是不通的,代理模式关注的是控制对于某个对象的访问,而装饰器模式则是更注重与对对象功能的拓展。

责任链模式(Chain Of Responsibility Pattern)

是一种请求处理的模式,它让多个处理器都有机会处理这个请求,知道某个请求处理成功为止,责任连模式把多个处理器串成串,然后让请求在链上传递。

比如在公司需要请假的话,如果是小假期可以让开发经理直接审批,但是如果我们想要请示更长的假期,可能就需要部门经理来审批;再长的话可能就需要涉及到总经理。

在这里插入图片描述

对于请求者不用关心其中的处理,只需要将请求交给处理的开端,就可以得到最终的结果。

在这里插入图片描述

public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        Handler1 handler1 = new Handler1();
        handler1.handle(10);
    }
}
abstract class Handler {
    protected Handler nextHandler;
    protected void setNextHandler(Handler handler) {
        this.nextHandler = handler;
    }
    public abstract void handle(int day);
}
class Handler1 extends Handler {
    Handler1() {
        setNextHandler(new Handler2());
    }
    @Override
    public void handle(int day) {
        if (day <= 3) {
            System.out.println("1 处理成功");
        } else {
            nextHandler.handle(day);
        }
    }
}
class Handler2 extends Handler {
    Handler2() {
        setNextHandler(new Handler3());
    }
    @Override
    public void handle(int day) {
        if (day <= 10) {
            System.out.println("2 处理成功");
        } else {
            nextHandler.handle(day);
        }
    }
}
class Handler3 extends Handler {
    @Override
    public void handle(int day) {
        if (day <= 10) {
            System.out.println("3 处理成功");
        } else {
            System.out.println("请求失败");
        }
    }
}

这里我们对上面的案例来进行一个实现,里面的 handle 方法接受一个请假天数作为参数,然后将请求放到责任链中去处理,最终得到处理结果。

模板方法模式(Template Method Pattern)

定义一个操作中的方法的框架,而将一些步骤延迟到子类中去实现,使得子类可以在不改变一个算法的结构既可以定义某些特定的步骤。

在这里插入图片描述

jdk 中的 AQS 抽象类(AbstractQueuedSynchronizer)及其子类锁的实现就使用到了模板方法模式,在 AQS 中并没有提供 tryAcquire 等方法,而是开放到子类中去实现,从而实现各式各样的锁

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

享元模式(FlyWeight Pattern)

运用共享技术有效地支持大量细类度的对象。

享元模式是为了解决公共资源的共享问题,当一个公共资源不需要考虑外部状态(如使用者是谁)的时候,通过单例模式就可以实现共享;但是如果公共资源需要针对外部状态做出改变的时候,单例模式就无法解决了,此时就可能需要创建大量的类,这样就造成了资源的浪费。

此时就可以使用到享元模式,享元模式的逻辑是首先有一个公共的享元类,享元类中提供一个方法来接收外部的参数(状态),然后根据不同的状态去执行逻辑,这样就避免了公共的实例对象被过多的创建。

享元模式的类图结构是这样的,首先有一个抽象的享元类,里面定义了享元的执行逻辑,通过继承这个类来实现各种的功能,享元类的创建委托给享元工厂完成,享元工厂负责返回、创建和管理具体的享元类。

在这里插入图片描述

享元模式被大量的用于池技术中,比如线程池、连接池等;以线程池距离,它维护了线程的创建和销毁,并且传入不通的任务来使得线程能够处理不同的外部请求。

public class FlyWeightPattern {
    public static void main(String[] args) {
        FlyWeightFactory flyWeightFactory = new FlyWeightFactory();
        SharedBike bike1 = flyWeightFactory.getBike("张三");
        SharedBike bike2 = flyWeightFactory.getBike("李四");
        flyWeightFactory.getBike("王五");
        bike1.back();
        SharedBike bike4 = flyWeightFactory.getBike("王五");
    }
}
interface Bike {
    void ride(String name);
}
class SharedBike implements Bike {
    /**
     * 车辆状态,0 空闲,1 使用中
     */
    public int state = 0;

    public int getState() {
        return state;
    }
    @Override
    public synchronized void ride(String name) {
        state = 1;
        System.out.println(name + "骑行中");
    }

    public synchronized void back() {
        state = 0;
    }

}
class FlyWeightFactory {
    public static final List<SharedBike> sharedBikePool = new ArrayList<>();
    static {
        sharedBikePool.add(new SharedBike());
        sharedBikePool.add(new SharedBike());
    }
    public SharedBike getBike(String name) {
        for (SharedBike bike : sharedBikePool) {
            if (bike.getState() == 0) {
                bike.ride(name);
                return bike;
            }
        }
        System.out.println("无空闲车辆");
        return null;
    }
}

上面展示了一个共享单车案例,用享元模式来实现了资源的共享。

命令模式(CommandPattern)

命令模式是一种行为设计模式,它可以将请求转换为一个包含与请求相关的所有信息的独立对象。这个转换让你能够根据不同的请求将方法参数化,延迟请求执行或者将其放入队列中,且能实现可撤销的操作。

对命令模式最常见的实现就是服务器的 MVC 架构,MVC 架构的三层为模型(model)、视图(View)和控制器(Controller),通过 MVC 架构能够将视图层和业务层分割开来,那为什么要将它们分割开呢?

先来看看如果要讲它们写在一起会有什么后果,比如我们设计一个按钮实现刷新操作,按钮是一个视图的展示,将刷新的代码写到这个 Button 中,但如果别的地方也用到了刷新,比如点按 F5,此时就需要将刚刚的刷新代码复制过去;而这种直接复制往往就是我们最忌讳的事情,因为它会引发一系列的难以维护的问题。

此时就应该将视图对象和业务逻辑分隔开,使得复制方法得以复用

在这里插入图片描述

最终抽象出这样的类图结构

在这里插入图片描述

这好像也是定义了多种策略,可能入上面的策略模式混淆,但策略模式关注的是对象中某个方法的执行算法,而命令模式则更加强调命令的复用和与对象的绑定。

原型模式(Prototype Pattern)

用原型实例指定要创建对象的种类,并通过拷贝这些原型的属性来创建新的对象。通过拷贝自身的属性来创建一个新对象的过程叫作原型模式;也被称为克隆模式。

原型模式的是为了解决对象复制的问题的,如果我们获取到了一个对象,想要复制一个和它完全相同的对象,我们可以通过循环复制其中的属性的方式来构建新的对象,但这必须要求这个类提供了全部属性的访问和修改权限,且有时我们获取的运行类型和编译类型不相同的时候,我们甚至都无法知道对象中有哪些属性,复制更是无稽之谈了。

此时就需要寻求原本类的帮助,使得被复制的类实现一个 clone 方法,返回一个复制后的类。Java 中就提供了这样一个 Clonable 接口,继承了这个接口的类需要实现 clone 方法来返回克隆对象。

public interface Cloneable {}

接口中没有定义任何的方法,因为公共父类 Object 中已经实现了 protected 修饰的 clone 方法:

protected native Object clone() throws CloneNotSupportedException;

在这里插入图片描述

public class PrototypePattern {
    public static void main(String[] args) throws CloneNotSupportedException {
        A a = new A();
        A clone = a.clone();
    }
}
class A implements Cloneable {
    int a = 10;

    @Override
    public A clone() throws CloneNotSupportedException {
        return (A)super.clone();
    }
}

备忘录模式(Memento Pattern)

备忘录模式是一种行为设计模式,它允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。

比如我们要实现一个文本编辑器,编辑器最重要的功能就是撤销模式;撤销模式的原理就是在用户修改的时候去保存上一个状态的快照,当用户撤销的时候,通过读取快照来使得文本恢复到之前的状态,而要获取快照,和上面的克隆相同,也需要去获取类的全部属性,此时也会遇到和克隆相同的问题:没有权限访问,此时同样需要委托需要做备忘录的类来实现备忘录功能。

首先需要定义一个快照类,里面存有所有的属性,然后这个快照类还存有一个额外内容,比如快照名称、时间戳等;然后建立一个快照栈结构来存储快照。

当存储快照的时候,在类内部构建一个快照对象然后将其存入到快照栈中,需要获取的时候从栈中弹出这个对象就能实现撤销恢复的操作。

在这里插入图片描述

public class MementoPattern {
    public static void main(String[] args) {
        Document document = new Document();
        document.change("1");
        document.change("12");
        document.change("123");
        document.print();
        document.resume();
        document.print();
    }
}
class Document {
    private String text;
    private final History history = new History();
    public void change(String text) {
    // 修改之前记录上个状态
        history.record(new BackUp(this.text));
        this.text = text;
    }
    public void resume() {
        text = history.getLastVersion().text;
    }
    public void print() {
        System.out.println(text);
    }
}
interface Memento{}
class BackUp implements Memento {
    public String text;
    public BackUp(String text) {
        this.text = text;
    }
}
class History {
    private final Stack<BackUp> backUpStack = new Stack<>();
    public void record(BackUp memento) {
        backUpStack.push(memento);
    }
    public BackUp getLastVersion() {
        return backUpStack.pop();
    }
}

上面的案例中构造了一个文本对象,并且在存储记录之前保存快照来实现撤销功能。

迭代器模式(Iterator Pattern)

迭代器模式是一种行为设计模式,能够在不暴露集合底层表现形式的情况下遍历集合中的所有元素。

在这里插入图片描述

迭代器模式大家一定都不陌生,Collection 接口就继承了 Iterable 接口,Iterable 接口中就定义了迭代器的获取和迭代等逻辑。

public interface Collection<E> extends Iterable<E>

我们可以通过获取迭代器的方式在不需要了解集合内容的情况下完成遍历。

public class IteratorPattern {
    public static void main(String[] args) {
        ArrayList<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        integers.add(3);
        Iterator<Integer> iterator = integers.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

增强 for 循环底层使用的也是这样的方式。

组合模式(Composite Pattern)

组合模式是一种结构性设计模式,可以使用它将对象组合成树形结构,并且能够像使用独立对象那样去使用它们。

这其实就类似于二叉树的后续遍历,叶子节点就是下面的 Leaf,而树枝节点就是 Composite,当我们需要统计叶子节点的总和的时候,其实就是层层递归的:

在这里插入图片描述

上面的案例中,展示了获取叶子节点的 val 之和的方式,树枝节点就是 Composite,它将所有的任务委派给了子元素,然后将子元素的结果整合作为本层结果返回给自己的上一层,最终在根节点得到最终的结果。

在这里插入图片描述

桥接模式(Bridge Pattern)

桥接模式是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用。桥接模式将抽象和实现进行解耦,使得两者可以独立的变化

比如一个苹果类,我们将其按照品质分为两个类(优质和普通),这就需要两个实现子类,此时在通过颜色将其分类(红色和黄色),子类的个数通过组合变成了四个,此时再按照产地进分类…… 子类的个数指数级别的增长。

此时就可以使用桥接模式,让苹果还是只有两个子类,优质和普通,再通过引用颜色类的方式构造不同的分支;它的类图结构是这样的:

在这里插入图片描述

中介者模式(Mediator Pattern)

中介者模式是一种行为设计模式,能让你减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。

在这里插入图片描述

访问者模式(Visitor Pattern)

访问者模式用于封装一些作用于某周数据结构中各元素的操作,它可以在不改变数据结构的前提下,定义作用于这些元素的新的操作。

比如此时我们有一个机器人,想要让它的更新功能,最简单的做法肯定不是去替换它的硬件,而是去修改硬件的指令集。此时就可以通过传入一个软件包的方式来实现 Robot 的更新,此时就需要有一个访问者去访问机器人的硬件并且给它们传入新的指令。

首先定义了一个 Visitor 接口,里面定义了两个方法 visitCpu 和 visitDisk 来访问 Robot 中的硬件属性,所以需要获取到这个属性;在硬件接口中定义一个 accept 方法,在这个方法中会调用 Visitor 接口中的 visit 方法来将自己传入(或者自己某个属性的指针传入),然后在 visit 方法中进行修改,最终实现功能。

在这里插入图片描述

public class VisitorPattern {
    public static void main(String[] args) {
        Robot robot = new Robot();
        robot.run();
        SoftwarePackage softwarePackage = new SoftwarePackage();
        robot.accept(softwarePackage);
        robot.run();
    }
}
interface HardWares {
    void accept(Visitor visitor);
}
interface Visitor{
    void visitCpu(Cpu cpu);
    void visitDisk(Disk disk);
}
class SoftwarePackage implements Visitor {

    @Override
    public void visitCpu(Cpu cpu) {
        cpu.command = "int a = 2;";
    }

    @Override
    public void visitDisk(Disk disk) {
        disk.command = "int a = 3;";
    }
}
class Robot {
    Cpu cpu = new Cpu("int a = 1;");
    Disk disk = new Disk("int a = 2;");
    public void run() {
        System.out.println(cpu.command + "\n" + disk.command);
    }
    public void accept(Visitor visitor) {
        cpu.accept(visitor);
        disk.accept(visitor);
    }
}
class Cpu implements HardWares{
    public String command;
    Cpu(String command) {
        this.command = command;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitCpu(this);
    }
}
class Disk implements HardWares{
    public String command;
    Disk(String command) {
        this.command = command;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visitDisk(this);
    }
}

解释器模式(Interpreter Pattern)

定义一个语言的文法,并且创建一个解释器来解释这个方法中的句子,这里的语言指的是使用规定格式和语法的代码。解释器模式是一种类行为模式。

解释器模式的类图结构是这样的,首先定义文法 Context,将其传入 Expression 解析器中执行解析,这个解析器根据是否终止分为 TerminalExpression 和 NoTerminalExpression,当内容进入第一个解析器的时候,它会不断调用其他对应的解析器,直到最终返回结果。

在这里插入图片描述

比如这里我们构造一个加法解析器,当遇到等号的时候输出结果,等号解析器就是终止解析器;首先定义一个 Context 类,里面含有指令和存储的中间值 value,提供两个方法去获取下一条指令;然后定义了解析器接口 Expression 抽象类,在抽象类中实现了获取下一个值和获取下一个符号的方法,并且声明了抽象方法 interpret 来执行实际的解析任务。

然后创建了加法执行器循环的处理语句,并且在遇到等号处理器的时候结束。

public class InterpreterPattern {
    public static void main(String[] args) {
        Context context = new Context("1+2+3=");
        AddExpression addExpression = new AddExpression();
        addExpression.interpret(context);
    }
}
class Context {
    String command;
    long value;
    int index = 0;
    Context(String command) {this.command = command;}
}
abstract class Expression {
    abstract void interpret(Context context);
    Integer findNextNum(Context context) {
        return (int) context.command.charAt(context.index++);
    }
    String findNextOpr(Context context) {
        return String.valueOf(context.command.charAt(context.index++));
    }
}
class AddExpression extends Expression {
    @Override
    void interpret(Context context) {
        Integer nextNum = findNextNum(context);
        String nextOpr = findNextOpr(context);
        context.value += nextNum;
        switch (nextOpr) {
            case "+":
                this.interpret(context);
                return;
            case "=": new EqualExpression().interpret(context);
        }
    }
}
class EqualExpression extends Expression {
    @Override
    void interpret(Context context) {
        System.out.println("res = " + context.value);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/648423.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

驱动开发执行应用层时报ELF: not found,syntax error: unexpected “(“错误

问题&#xff1a; 原因&#xff1a;在跨平台的时候注意我们使用的编译器&#xff0c;我是因为没有没有交叉编译导致的。 出问题之前使用的是gcc test_01_normal.c -o test_01_normal生成的文件&#xff0c;导致&#xff0c;执行时报ELF这种问题。 解决办法&#xff1a;arm-li…

将本地项目上传到 gitee 仓库

1、创建 gitee 仓库 到 gitee 官网&#xff0c;新建仓库 配置新建仓库 完成仓库的创建 项目上传到仓库 上传项目需要安装git git官方下载地址&#xff1a;git下载地址 安装完成&#xff0c;前往本地项目所在文件夹&#xff0c;右击选择 Git Bash Here 刚下载完成需要配置G…

粤嵌—2024/5/13—删除排序链表中的重复元素(✔)

代码实现&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* deleteDuplicates(struct ListNode *head) {if (head NULL || head->next NULL) {return head;}struct ListNode *…

【计算机毕业设计】基于SSM+Vue的新能源汽车在线租赁管理系统【源码+lw+部署文档】

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;新能源汽车在线租赁当然也不能排除在外。新能源汽车在线租赁是以实际运用为开发背景&#xff0c;运用软件工程开发方法&…

【Linux部署】【pig前端部署】Linux安装- docker/docker-compose/nginx (使用docker优雅部署nginx)

&#x1f338;&#x1f338; Linux安装- docker/docker-compose/nginx 优雅部署 &#x1f338;&#x1f338; 一、一键安装jdk yum install -y java-1.8.0-openjdk.x86_64验证 二、安装docker yum list docker-ce --showduplicates | sort -rsudo yum install -y yum-utils …

Visual Studio和Visual Studio Code分清了? 都是IDE,可不是框架。

Visual Studio和VSCode两者都是 Microsoft 制造的IDE&#xff08;集成开发环境&#xff09;。尽管它们的名字相似&#xff0c;但它们的功能却大不相同。 一、什么是Visual Studio&#xff08;VS&#xff09; Visual Studio&#xff08;简称VS&#xff09;是由微软公司开发的一…

用go语言实现一个有界协程池

写在文章开头 本篇文章算是对go语言系列的一个收尾&#xff0c;通过go语言实现一个实现一个简单的有界协程池。 Hi&#xff0c;我是 sharkChili &#xff0c;是个不断在硬核技术上作死的 java coder &#xff0c;是 CSDN的博客专家 &#xff0c;也是开源项目 Java Guide 的维护…

AIGC时代算法工程师的面试秘籍(2024.4.29-5.12第十三式) |【三年面试五年模拟】

写在前面 【三年面试五年模拟】旨在整理&挖掘AI算法工程师在实习/校招/社招时所需的干货知识点与面试方法&#xff0c;力求让读者在获得心仪offer的同时&#xff0c;增强技术基本面。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&#x1f4aa; 欢迎大家关注Rocky…

引入安全生产培训云平台,实现“人人讲安全、个个会应急”

引入安全生产培训云平台&#xff0c;旨在全面提升企业及员工的安全意识与应急处理能力&#xff0c;通过数字化手段实现“人人讲安全、个个会应急”的目标。这一平台的构建和应用&#xff0c;不仅促进了安全知识的普及&#xff0c;还极大提高了培训的效率与效果。以下是该平台几…

Backend - postgresSQL DB 存储过程(数据库存储过程)

目录 一、存储过程的特性 &#xff08;一&#xff09;作用 &#xff08;二&#xff09;特点 &#xff08;三&#xff09;编码结构的区别 二、定时执行存储过程 三、2种编码结构 &#xff08;一&#xff09;函数结构 1. SQL代码 2. 举例 &#xff08;1&#xff09;例1-循…

邦之信短信分析:验证码短信、营销短信与通知短信的差异化解析

在数字通讯时代&#xff0c;短信已成为我们日常生活中不可或缺的一部分。其中&#xff0c;验证码短信、营销短信和通知短信各自扮演着不同的角色。今天&#xff0c;飞鸽将带您深入了解这三种短信类型之间的核心差异。 1. 验证码短信 验证码短信广泛应用于各类电商网站和…

【UE5.1 角色练习】07-AOE技能

目录 效果 步骤 一、准备技能动画 二、准备粒子特效 三、技能蓝图 四、相机震动 前言 在上一篇&#xff08;【UE5.1 角色练习】06-角色发射火球-part2&#xff09;基础上继续实现角色释放AOE技能的功能。 效果 步骤 一、准备技能动画 1. 在项目设置中添加一个操作映…

如何恢复已删除/丢失的照片/视频?

“嗨&#xff0c;我把我所有的世界杯照片和视频都存储在我的数码相机存储卡上。但是&#xff0c;当我将存储卡与计算机连接时&#xff0c;它会要求我格式化存储卡。我格式化了存储卡&#xff0c;但我所有的世界杯照片和视频都不见了。这对我来说是一场大灾难。是否有可能恢复丢…

[图解]产品经理创新模式01物流变成信息流

1 00:00:01,570 --> 00:00:04,120 有了现状的业务序列图 2 00:00:04,960 --> 00:00:08,490 我们就来改进我们的业务序列图了 3 00:00:08,580 --> 00:00:11,010 把我们要做的系统放进去&#xff0c;改进它 4 00:00:13,470 --> 00:00:15,260 怎么改进&#xff1f;…

【MATLAB】信号的熵

近似熵、样本熵、模糊熵、排列熵|、功率谱熵、奇异谱熵、能量熵、包络熵 代码内容&#xff1a; 获取代码请关注MATLAB科研小白的个人公众号&#xff08;即文章下方二维码&#xff09;&#xff0c;并回复信号的熵本公众号致力于解决找代码难&#xff0c;写代码怵。各位有什么急需…

Pytorch DDP分布式细节分享

自动微分和autograde 自动微分 机器学习/深度学习关键部分之一&#xff1a;反向传播&#xff0c;通过计算微分更新参数值。 自动微分的精髓在于它发现了微分计算的本质&#xff1a;微分计算就是一系列有限的可微算子的组合。 自动微分以链式法则为基础&#xff0c;依据运算逻…

Tomcat源码解析(七):底层如何获取请求url、请求头、json数据?

Tomcat源码系列文章 Tomcat源码解析(一)&#xff1a;Tomcat整体架构 Tomcat源码解析(二)&#xff1a;Bootstrap和Catalina Tomcat源码解析(三)&#xff1a;LifeCycle生命周期管理 Tomcat源码解析(四)&#xff1a;StandardServer和StandardService Tomcat源码解析(五)&…

知攻善防应急响应靶机训练-Web3

前言 本次应急响应靶机采用的是知攻善防实验室的Web-3应急响应靶机 靶机下载地址为&#xff1a; https://pan.quark.cn/s/4b6dffd0c51a 相关账户密码 用户:administrator 密码:xj123456xj123456 解题过程 第一题-攻击者的两个IP地址 直接查看apache的log日志搜索.php 发现…

学习Uni-app开发小程序Day26

这一章学习的内容细节较多&#xff0c;主要是分为&#xff1a;首次加载减少网络消耗、获取图片的详细信息、图片的评分和避免重复评分、将图片下载到本地并且获取设备的授权 加载图片减少网络消耗 这里突出这个功能&#xff0c;是根据老师视频上的描述&#xff0c;个人觉得很…

Spark介绍

Spark简介 Spark,是一种通用的大数据计算框架,正如传统大数据技术Hadoop的MapReduce、Hive引擎,以及Storm流式实时计算引擎等. Spark是加州大学伯克利分校AMP实验室(Algorithms Machines and People Lab)开发的通用内存并行计算框架,用于构建大型的、低延迟的数据分析应用程序…