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

news2024/9/24 21:24:39

本篇文章是对于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);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能座舱-车载声学技术训练营

语音交互赋能车载智能终端&#xff0c;成为智能座舱生态构建的核心功能 曾几何时&#xff0c;至少十年前&#xff0c;车内语音交互&#xff0c;大家都认为是“智障”阶段&#xff0c;基本上除了难用作为评价找不到其他的形容词做修饰。 但是随着技术的不断发展&#xff0c;特别…

2024年哪款充电宝好?热门罗马仕、西圣、倍思充电宝全方面实测

在这个人人都离不开手机的时代&#xff0c;出门坐车需要用到手机刷卡&#xff0c;吃饭点外卖需要用到手机&#xff0c;做旅游攻略需要用到手机等等&#xff0c;如果是出门一天的话不带充电宝&#xff0c;手机基本是不能够撑回到家的&#xff0c;这时候就需要一个充电宝辅助我们…

整理前端新出的操作工具好用又好玩(Custom Formatter,Oxlint,Nuxt DevTools,component-party)

1.使用Custom Formatter 使vue3中的reactive object 在Chrome在console中更易理解的方式展现 启用步骤&#xff1a; 1.打开控制台&#xff0c;然后打开console设置 2.前往proferences中的Console&#xff0c;勾选Enable custom formatters选项 3.刷新页面 2.使用css Overv…

【NumPy】权威指南:使用NumPy的percentile函数进行百分位数计算

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

2024最新(PC+WEB+IOS+Android)即时通讯系统客户端仿默往IM源码下载

2024最新(PCWEBIOSAndroid)即时通讯系统客户端仿默往IM源码下载(总大小约2.4G&#xff09; 系统功能配置灵活、海量并发、稳定可靠、数据安全&#xff0c;2小时快速部署、数据安全、单聊群聊、系统通知等通信功能&#xff0c;支持App、PC、Web等多端快速接入。 群功能&#xf…

【小米手环7】表盘修改/制作指南

2024年了还有人用小米手环7么&#xff1f; 5月10号得到我的小米手环7nfc&#xff0c;随之开启了我对表盘制作的探索之旅。~历时18天&#xff0c;终于让我成功修改了官方表盘——荧光电子&#xff01;表盘&#xff1a;羽球人的荧光电子已上传至表盘自定义工具app。 接下来&…

将x减到0的最小操作(滑动窗口)

算法原理&#xff1a; 第一眼看到这个题的时候&#xff0c;我真的想不到它到底是怎么和滑动窗口联系起来的&#xff0c;在我的脑海里只有一个简单的双指针。直到我看过了题解&#xff0c;才明白原来是这么回事&#xff1a;先看题目是求最小操作数&#xff0c;此时你一定不要先…

二叉树顺序结构实现【堆的实现】【详细图解】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 目录 1、二叉树的顺序结构2、堆的概念3、堆的实现3.1 堆实现的前提3.1.1 向上调整3.1.2 向下调…

网络安全行为可控定义以及表现内容简述

在数字化快速发展的今天&#xff0c;网络安全已成为国家和企业不可或缺的防线。据统计&#xff0c;网络攻击事件频发&#xff0c;给全球经济带来了巨大损失。因此&#xff0c;确保网络安全行为可控显得尤为重要。今天我们来聊聊网络安全行为可控定义以及表现内容。 网络安全行为…

音视频开发9 FFmpeg 解复用框架说明,重要API说明

一&#xff0c;播放器框架 二 常用音视频术语 容器&#xff0f;文件&#xff08;Conainer/File&#xff09;&#xff1a; 即特定格式的多媒体文件&#xff0c; 比如mp4、flv、mkv等。 媒体流&#xff08;Stream&#xff09;&#xff1a; 表示时间轴上的一段连续数据&#xff0…

维护课堂纪律的重要性

在课堂上&#xff0c;老师们是否经常遇到这样的情况&#xff1a;孩子们交头接耳&#xff0c;小动作不断&#xff0c;甚至有人偷偷玩手机&#xff1f;这些行为长让老师感到头疼&#xff0c;但作为老师&#xff0c;是否思考过&#xff0c;维护课堂纪律的重要性究竟何在&#xff1…

生信网络学院|05月31日《SOLIDWORKS Manage 产品周期管理》

课程主题&#xff1a;SOLIDWORKS Manage 产品周期管理 课程时间&#xff1a;2024年05月31日 14:00-14:30 主讲人&#xff1a;付舰 生信科技 PLM实施顾问 1、SOLIDWORKS Manage介绍 2、周期流程管理 3、产品项目管理 4、项目会议管理 5、项目问题管理 安装腾讯会议客户端…

java配置文件解析yml/xml/properties文件

XML 以mybatis.xml:获取所有Environment中的数据库并连接session为例 import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException;import javax.xml.parsers.DocumentBuilder; impo…

第十二周 5.21面向对象的三大特性(封装、继承、多态)(二)

三、多态 1.理解: (1)多态:父类型的引用存储不同子类型的对象 父类类名 引用名 new 子类类名(); 引用 对象 父类型 子类型 …

视频太大怎么压缩变小 视频太大了怎么压缩

视频作为一种多媒体形式&#xff0c;在当今社会的重要性日益凸显&#xff0c;其应用范围广泛&#xff0c;影响力深远。 但是视频文件的大小也在不断增加&#xff0c;这给存储和传输带来了很大的困扰。那么&#xff0c;当视频文件过大时&#xff0c;我们该如何将其压缩变小呢&am…

lubuntu20.04安装和使用ROS Noetic Ninjemys

Noetic Ninjemys 最后一个ROS官方支持的第一代Noetic Ninjemys 为何选择Lubuntu 熟悉我博客的朋友知道&#xff0c;我的这些分享都没有官方经费支持&#xff0c;都是在自己和志同道合朋友们共同努力下&#xff0c;走到今天。 设备陈旧&#xff0c;只能选择对系统资源需求最少…

Common Lisp笔记

在计划学习函数式编程的时候&#xff0c;我一开始打算学习的是 F#。因为我朋友就是在 DTU 上的学&#xff0c;F# 就是 DTU&#xff08;丹麦理工&#xff09;开发的。但是由于 F# 和微软的 .NET 绑定&#xff0c;而在 macOS 上&#xff0c;目前版本的 .NET 的是有些问题的&#…

【NumPy】NumPy线性代数模块详解:掌握numpy.linalg的核心功能

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

软件测试人员如何规划自己的职业发展路线

在这个飞速发展的时代中&#xff0c;我们每个人都渴望在各自的领域里找到属于自己的一片天空&#xff0c;而对于那些致力于软件测试的朋友们而言&#xff0c;规划好自己的职业发展路线显得尤为重要。 一、明确目标 首先&#xff0c;我们需要确立一个明确的职业发展目标。对于软…

C# yolov8 TensorRT Demo

C# yolov8 TensorRT Demo 目录 效果 说明 项目 代码 下载 效果 说明 环境 NVIDIA GeForce RTX 4060 Laptop GPU cuda12.1cudnn 8.8.1TensorRT-8.6.1.6 版本和我不一致的需要重新编译TensorRtExtern.dll&#xff0c;TensorRtExtern源码地址&#xff1a;https://githu…