设计模式-Java版本

发布时间:2023年12月29日

前言

okey,最近在写个小框架,那么不免会用到设计模式相关的内容,当然还有个原因就是要准备软考。所以的话我们今天的话把我们的设计模式重新过一遍,当然这里注意的是,由于每个人的理解是不同的话,因此对于涉及模式来说,我们的案例代码可能也是不太相同的。所以如果你觉的我这里的某些描述有点问题,请在评论区我们一同探讨。那么在这里的话,也是会有一些奇怪的比喻,重在理解。

设计原则

设计原则是可以理解是我们的一种代码编写的建议规范。我们接下来的设计模式基本上都是同这几个设计原则相关的,换句话说,我们的设计模式可以帮助我们的代码在一定程度上符合规范我们的代码满足设计原则。那么为什么我们要学习使用设计模式呢?

这个问题其实就好像我们为什么在学习高等数学的时候,或者初等(高中)数学的时候,为什么我们要学习二级结论,要学习技巧的道理是一样的。如果有学习过考研数学的话,我想你对于这些话印象会很深刻。很多固定的题目,如果在第一步你没有想清楚(思考出大致的解题链路)你将很痛苦(当然现在不仅仅是思路的问题,还有计算能力的问题)对于我们的代码同样如此

那么在这里我们一共有六个基本的设计原则
六大基本设计原则

单一职责原则

单一职责原则是一个基础的设计原则。其核心是保证,我们写的每一类只做一个件事情。避免上帝类的出现,导致代码臃肿不利于对扩展开放。

以我们人类社会为例子:单一职责告诉我们每个人有属于,每个人的角色

开闭原则

基于接口,来进行声明,配合单一职责原则,实现对代码的扩展开放。在需要新功能的时候,只需要实现新的接口,然后通过机构模式来将其进行替换。

开闭原则告诉我们,从事计算机软件开发时,我们可以在同一行业内疯狂跳槽

里氏替换原则

继承必须具备超类的所有特性。(对类进行分类,实现不同的功能)

举个例子:龙生龙,凤生凤,老鼠的儿子会打洞。所有的子类继承父类,都需要确保具备父类的所有的功能,之后再新增新的功能。

迪米特法则

降低类之间的耦合,不同角色,做不同的事。只是向上开发的时候,提供接口,而提供实现。下级只需要为上级提供接口,而不能提供具体实现,具体实现可以由同级实现,让上级调用。

接口隔离原则

单一职责是对类来说,避免上帝的出现,接口隔离原则则是对接口来说,来避免上帝接口的出现。这个和里氏替换原则是息息相关的

依赖倒置原则

高层不能依赖底层。顶级接口实现,可以依赖底层接口实现,但是底层不能直接依赖高层,同级可依赖。

口诀
单一职责:尽可能自己做自己的事
开闭原则:多加少改,后面再配合工厂模式,策略模式等等。
里氏替换原则:我们需要继承和发扬中国传统文化(子类必须具备超类的所有实现)
迪米特法则:尽可能提供接口服务,保持一定的神秘感(我只关心你能干什么,你要什么,你适合什么环境,什么时候用你,怎么用你)
接口隔离原则:接口多拆拆,避免上帝出现,同时提高复用性
依赖倒置原则:上级要听上级的话,上级派遣下级,下级无权调用上级

设计模式

说完了基本的设计原则,我们来看到我们的设计模式,设计模式我们这里可以分为三大类。

  1. 构建类型
  2. 结构类型
  3. 行为类型

这个很好理解。因为最早的设计模式是土木老哥那边搞出来的。然后用在我们这边非常的牛逼。所以可以这样想象。盖一栋大楼,我们需要哪些部件,这些部件要如何组装耦合在一起完成功能。然后呢,这些部件要怎么生产,具备哪些功能。

当然话说回来,由于我们的实际代码其实是很复杂的,所以的话,我们其实可能会出现你中有我我中有你的情况,注意编码即可。

构建类型

工厂模式

okey, 那么接下来我们一个一个来玩玩。这里我们举一个生成汽车的例子:

在这里插入图片描述

在这里插入图片描述

在这里我们首先定义了我们的汽车接口,然后我们可以生产两种汽车。

public interface ICar {

    void run();
}

public class SuperCar implements ICar {
    @Override
    public void run() {
        System.out.println("我是超跑");
    }
}

public class Truck implements ICar {
    @Override
    public void run() {
        System.out.println("我是卡车");
    }
}

之后的话,我们创建了一个工厂,这个工厂可以帮助我们生成汽车:

public class CarFactory {

    public static ICar buildCar(Class<? extends ICar> car) throws InstantiationException, IllegalAccessException {
        return car.newInstance();
    }
}

这里的话,我们直接通过反射就可以创建一个对象了。于是在使用的时候就是这样:

public class Test {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ICar superCar = CarFactory.buildCar(SuperCar.class);
        superCar.run();
    }
}

我们只需要传入到对应的类名即可。当然这里你会发现我们每次都是通过反射创建了一个新的对象。那么这个时候实际上我们可以再优化一下的,还记得我们的SpringIOC容器嘛。实际上我们也可以来个简单一点的IOC容器:

public class CarFactory {

    private static Map<Class<? extends ICar>,ICar> IOC = new HashMap<>();
    static {
        IOC.put(SuperCar.class,new SuperCar());
        IOC.put(Truck.class,new Truck());
    }

    public static ICar buildCar(Class<? extends ICar> car) throws InstantiationException, IllegalAccessException {
        return car.newInstance();
    }

    public static ICar getBean(Class<? extends ICar> car){
        return IOC.get(car);
    }
}

我们这里只是略加改造,加了一个HashMap来维护我们的对象即可。只是与Spring不同的是,我们这里采用的是自己手动添加了对象,而Spring是通过xml配置文件或者注解扫包等其他方式来完成了对象的注入。

学过Java反射的我们都知道,当我们得到对象的时候,尤其是类的时候,我们是可以做非常多的操作的,例如我们的方法增强等等。这里的话我们放出一张Spring的Bean周期你就明白了:
在这里插入图片描述
在这里面BeanDefinition里面存放了一个JavaBean的一些包,类等等信息,通过这个可以得到初始化的对象。然后这里面有个Aware接口,可以得到一些信息(方便你自己在这边实例化前后做一些操作)之后是PostProcessor。这边就是我们对方法进行增强了,也就是AOP。因为我们这里要执行一些东西,主要还是靠接口嘛。 当然里面实际上处理到的细节还是很多的。这里的话我们不做过多探讨,以后机会话分享一些阅读源码的博客。当然按照我的风格,基本上少不了看完这个东西之后手写一个lite版本的。先前我们其实写过了一个简单的Spring和MVC,包括一个任务执行框架。

抽象工厂

我们刚刚是聊到了工厂模式,其实我们抽象工厂和我们的工厂是一样的。区别在哪呢,那就是生成的对象不同。实际上,我们刚刚的工厂模式只是一个统称,实际上我们刚刚的例子只是生成的一台汽车。那么实际上,我们还可以生成汽车工厂。

于是我们刚刚只是生成汽车的工厂叫做:简单工厂
现在在生产建造汽车的工厂的工厂叫做:工厂方法
后来随着企业的状态,不在满足生成汽车,于是我们又开始生产飞机了,这个时候我们有一个东西可以生产一个可以生产工厂的工厂,这个时候这个玩意叫做抽象工厂

画个图你就明白了:
在这里插入图片描述
说白了就是一层一层抽象。我们这里有个玩笑话:遇事不决,先抽象一层,其实就是这个意思。你知道这里可能需要这个东西来完成,但是我不知道这个东西具体怎么实现,那没事,先写个接口,不着急定义方法,再写个工厂或者其他的管理器等等,这个东西可以生产或者管理那个接口。之后我们在讨论具体的实现。

这个的话,就比较简单了,说白了就是一层套一层。从简单工厂开始写,再写到顶级的抽象工厂即可。

建造者模式

建造者模式,其实就一句话:化繁为简 还是拿到我们刚刚的例子,我们有一台车。
车有轮子,有发动机,有架子(假设就这写东西的话,那么我们可以这样干):

首先这个小项目长这个样:
在这里插入图片描述
然后关系长这样:
在这里插入图片描述

代码如下:

public interface CarEngine {

    void engine();
}

public interface CarFrame {

    void frame();
}

public interface CarWheel {

    void wheel();
}

public class CarbonFibreFrame implements CarFrame {
    @Override
    public void frame() {
        System.out.println("碳纤维车架良好");
    }
}

public class MiQIWheel implements CarWheel {
    @Override
    public void wheel() {
        System.out.println("米其林轮胎温度正常");
    }
}

public class V8Engine implements CarEngine {
    @Override
    public void engine() {
        System.out.println("V8引擎启动");
    }
}

之后是我们的小汽车:

public class GreatCar {

    CarFrame carFrame;
    CarEngine carEngine;
    CarWheel carWheel;

    public GreatCar(CarFrame carFrame, CarEngine carEngine, CarWheel carWheel) {
        this.carFrame = carFrame;
        this.carEngine = carEngine;
        this.carWheel = carWheel;
    }

    public GreatCar() {
    }

    public void start(){
        carEngine.engine();
        carWheel.wheel();
        carFrame.frame();
    }
}

然后的话:

public class Test {

    public static void main(String[] args) {
        GreatCar greatCar = new GreatCar(new CarbonFibreFrame(), new V8Engine(), new MiQIWheel());
        greatCar.start();
    }
}

一辆小汽车完成构建。其实这个模式,再我们写Controller的时候不自觉就会用到,比如一个Controller里面有很多个Service。

当然,同样的,实际上,你还可以再搞个HashMap,维护这些组件,直接add加入这个组件就可以了。然后遍历处理一下,或者其他的。我自己在实现那个任务小框架的那个任务清单的实现的时候考虑的就是这个玩法。之后,你再配合工厂模式,去搞一下。

原型模式

什么是原型模式呢:用户创建重复的对象,同时还要保证性能的一张设计模式。
这个的话,我们说实话,没啥东西,就是怎么快速创建对象的一些操作,比如我们在创建的对象的时候,我们可以把数据先预处理加载过来创建。在设计类对象的时候多注意模块化设计,然后在创建多个对象的时候,我们直接用Java的clone,然后重写(如果有需要的话)clone 的方法,给它额外添加数据。

举个例子,我们创建试卷:

这里的话,我们核心就是看到这里:

public class QuestionBank implements Cloneable{

    private String candidate; // 考生
    private String number;    // 考号

    private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<>();
    private ArrayList<AnswerQuestion> answerQuestionList = new ArrayList<>();

    public QuestionBank append(ChoiceQuestion choiceQuestion) {
        choiceQuestionList.add(choiceQuestion);
        return this;
    }

    public QuestionBank append(AnswerQuestion answerQuestion) {
        answerQuestionList.add(answerQuestion);
        return this;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        QuestionBank questionBank = (QuestionBank) super.clone();
        
        //对数据进行额外处理(在这里是打乱题目序号)
        questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();
        questionBank.answerQuestionList = (ArrayList<AnswerQuestion>) answerQuestionList.clone();

        // 题目乱序
        Collections.shuffle(questionBank.choiceQuestionList);
        Collections.shuffle(questionBank.answerQuestionList);

        // 答案乱序
        ArrayList<ChoiceQuestion> choiceQuestionList = questionBank.choiceQuestionList;
        for (ChoiceQuestion question : choiceQuestionList) {
            Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());
            question.setOption(random.getOption());
            question.setKey(random.getKey());
        }
        return questionBank;
    }

    public void setCandidate(String candidate) {
        this.candidate = candidate;
    }

    public void setNumber(String number) {
        this.number = number;
    }

}

注意看到他里面重新实现的clone方法即可。也即是说它在重新创建对象的时候做了一个加强

然后直接clone对象:

public class QuestionBankController {

    private QuestionBank questionBank = new QuestionBank();

    public QuestionBankController() {
        questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", new HashMap<String, String>() {{
			//省略创建试卷的过程
    }

    public String createPaper(String candidate, String number) throws CloneNotSupportedException {
        QuestionBank questionBankClone = (QuestionBank) questionBank.clone();
        questionBankClone.setCandidate(candidate);
        questionBankClone.setNumber(number);
        return questionBankClone.toString();
    }

}

单例模式

这个的话是老朋友了,想想我们经常使用的框架Spring就知道,我们经常把我们的这种功能性代码作为一个Bean放到我们的IOC容器当中。那么什么东西适合单例呢:

  1. 全局唯一变量
  2. 无状态对象

那么这个代码的话,我就不给了,比较熟悉了。

当然这块的话我们的单例模式又分为懒汉模式,饿汉模式。然后在并发环境下,还要记得上锁(在懒汉模式下)

结构型

okey,接下来到了我们的结构型模式了。

适配器模式

这个我们直接举个非常常见的例子。那就是Mybatis配置多数据源,或者说适应不同的配置的时候。我们就需要使用到我们的适配器模式。

注意的我们的适配器模式和我们的代理模式有点像,但是目的不同:
适配器模式的目标是实现接口的转换,而代理模式的目标是对对象的访问进行控制

那么这个的话我们直接举个非常简单的例子就过去了。我们举个用老安卓充电线冲type-c的例子。这里的话我们搞个转换器(其实就是适配器)就可以完成充电了。

public interface Typec {
    
    //type-C接口
    void charge();
}

public class AndroidType {

    //安卓充电线
    public void chargeForOlderAndroid() {
        System.out.println("老安卓充电线");
    }

}

public class AdapterForTypeC implements Typec {
	//转换器(适配器)
    private AndroidType androidType;

    public AdapterForTypeC(AndroidType androidType) {
        this.androidType = androidType;
    }

    @Override
    public void charge() {
        androidType.chargeForOlderAndroid();
    }
}

然后装上就能happy了。

public class Test {
    public static void main(String[] args) {
        AdapterForTypeC adapterForTypeC = new AdapterForTypeC(new AndroidType());
        adapterForTypeC.charge();
    }
}

桥接模式

这个模式的话,和我们搞个写的建造者的代码其实很像,其实就类似的。只不过,这边重组合使用。
在桥接模式中,抽象(接口)和实现的关系是通过桥接接口来建立的。抽象类包含了对实现类的引用,这样就可以调用实现类的方法。抽象类和实现类可以在运行时动态地绑定和替换。

假设我们有一个 Shape 接口,它定义了一个 draw 方法。我们还有两个实现类 Circle 和 Rectangle 分别表示圆形和矩形,并且它们都有一个颜色属性。我们希望能够在不同的颜色下绘制不同形状的图形。

public interface Color {
    String getColor();
}

然后我们定义两个实现类 Red 和 Green 来实现 Color 接口:

public class Red implements Color {
    @Override
    public String getColor() {
        return "Red";
    }
}

public class Green implements Color {
    @Override
    public String getColor() {
        return "Green";
    }
}

接下来,我们定义 Shape 接口,并引入 Color 接口作为桥接接口:

public interface Shape {
    void draw();
    void setColor(Color color);
}
public class Circle implements Shape {
    private Color color;

    public Circle(Color color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("Drawing Circle with " + color.getColor() + " color");
    }

    @Override
    public void setColor(Color color) {
        this.color = color;
    }
}

public class Rectangle implements Shape {
    private Color color;

    public Rectangle(Color color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("Drawing Rectangle with " + color.getColor() + " color");
    }

    @Override
    public void setColor(Color color) {
        this.color = color;
    }
}

最后测试

public static void main(String[] args) {
    Color red = new Red();
    Color green = new Green();

    Shape circle = new Circle(red);
    Shape rectangle = new Rectangle(green);

    circle.draw();
    rectangle.draw();

    circle.setColor(green);
    rectangle.setColor(red);

    circle.draw();
    rectangle.draw();
}

其实这个你会发现,我们在写SpringBoot程序的时候,经常这样干。

组合模式

组合模式,用一颗树的形式来表述出我们的整个结构。
允许你将对象组合成树状结构来表示“整体-部分”的层次关系。组合模式使得用户对单个对象和组合对象的使用具有一致性。尤其是我们在优化我们的if-else判断的时候

举个例子:

if 一个大的情况:
	if 小的情况
		if ...
		else
	else
else if ...
	if ....

这种依托if-else的情况下。
我们就可以用一个组件优化一下:

if 一个大的情况:
	大的情况的处理器(情况)
else if ...

然后,这个处理器里面你再处理处理。这里面可能会用到其他的设计模式。
这块的话我们举个权限验证的例子吧(虽然我很想举个我自己写的那个小框架的例子,但是比较抽象(因为我没有给到对应的背景,读者阅读会比较困难))

okey, 现在我们来看到我们一个可能的权限验证的情况:

public boolean hasPermission(User user, String permission) {
    if (user.getRoles() != null) {
        for (Role role : user.getRoles()) {
            if (role.getPermissions() != null) {
                for (Permission p : role.getPermissions()) {
                    if (p.getName().equals(permission)) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

我们的需求是这样的:
首先的话,我们又这个Permession,然后有role。然后有User。之后的话,我们用户里面有规则。规则里面有权限。
然后为了统一,我们都有一个统一接口:

public interface Component {
    boolean hasPermission(String permission);
}

然后有一个定义权限的类:

public class Permission implements Component {
    private String name;

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

    @Override
    public boolean hasPermission(String permission) {
        return this.name.equals(permission);
    }
}

之后是我们的 规则:

public class Role implements Component {
    private List<Component> components;

    public Role() {
        this.components = new ArrayList<>();
    }

    public void addComponent(Component component) {
        components.add(component);
    }

    public void removeComponent(Component component) {
        components.remove(component);
    }

    @Override
    public boolean hasPermission(String permission) {
        for (Component component : components) {
            if (component.hasPermission(permission)) {
                return true;
            }
        }
        return false;
    }
}

规则里面也要看权限。(就是看,这个规则有没有对应到这个权限,我们在查看用户有没有这个权限的时候,是看到用户的这些规则里面有没有这个权限,有的话就是true)

然后是我们的User类:

public class User implements Component {
    private List<Component> components;

    public User() {
        this.components = new ArrayList<>();
    }

    public void addComponent(Component component) {
        components.add(component);
    }

    public void removeComponent(Component component) {
        components.remove(component);
    }

    @Override
    public boolean hasPermission(String permission) {
        for (Component component : components) {
            if (component.hasPermission(permission)) {
                return true;
            }
        }
        return false;
    }
}

最后是我们的测试代码:

public class Test {

    public static void main(String[] args) {
        Permission permission1 = new Permission("read");
        Permission permission2 = new Permission("write");
        Permission permission3 = new Permission("execute");

        Role role1 = new Role();
        role1.addComponent(permission1);

        Role role2 = new Role();
        role2.addComponent(permission2);
        role2.addComponent(permission3);

        User user = new User();
        user.addComponent(role1);
        user.addComponent(role2);

        System.out.println(user.hasPermission("read"));     // 输出:true
        System.out.println(user.hasPermission("write"));    // 输出:true
        System.out.println(user.hasPermission("execute"));  // 输出:true
        System.out.println(user.hasPermission("delete"));   // 输出:false
    }
}

在这里插入图片描述

装饰器模式

这个装饰器模式,其实玩过Python的小兄弟应该很熟悉了。我们有个@语法糖,可以做装饰器。它们的思想是类似的:装饰器模式也是通过包装对象来实现功能的扩展。在装饰器模式中,通常会定义一个装饰器类,该类接受一个对象作为参数,在运行时动态地为对象添加额外的行为。通过装饰器模式,可以在不改变原始对象的情况下,通过包装对象来扩展其功能。

那么这个时候我们的装饰器模式和代理模式有什么区别:
装饰器模式(Decorator Pattern)的目的是在不改变原有对象的基础上,动态地扩展其功能。它允许通过将对象包装在装饰器中,来为对象添加额外的行为或责任。
代理模式(Proxy Pattern)的目的是控制对对象的访问。它提供了一个代理对象,用于在客户端和实际对象之间进行间接访问,从而可以在访问对象时添加额外的逻辑。

// 定义一个接口
public interface Component {
    void operation();
}


// 具体的组件实现类,我们要装饰的对象
public class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("执行具体组件的操作");
    }
}

// 装饰器抽象类
public abstract class Decorator implements Component {
    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void operation() {
        component.operation();
    }
}


// 具体的装饰器类
public class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        additionalOperation();
    }

    public void additionalOperation() {
        System.out.println("执行额外的操作A");
    }
}

// 具体的装饰器类
public class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public void operation() {
        super.operation();
        additionalOperation();
    }

    public void additionalOperation() {
        System.out.println("执行额外的操作B");
    }
}


最后是我们的测试:

public class Test {
    public static void main(String[] args) {
        // 创建具体组件对象
        Component component = new ConcreteComponent();

        // 使用装饰器包装组件对象
        Component decoratedComponent = new ConcreteDecoratorA(new ConcreteDecoratorB(component));

        // 执行操作
        decoratedComponent.operation();
    }
}

代理模式

代理模式的话,我们分为动态代理,和静态代理。这里的话我们主要是学习思想,所以我们这里就来玩玩静态代理。那么这个静态代理和我们的装饰器模式区别在哪呢。其实区别没有那么大:

在代理模式中,代理类充当的是客户端和真实对象之间的中介,控制客户端对真实对象的访问。代理类可以在访问真实对象前后执行额外的操作,并且可以选择延迟加载真实对象,从而优化性能。代理模式的目的是保护真实对象并提供更高级别的访问控制。

在装饰器模式中,装饰器类包装了被装饰对象,通过动态地为被包装对象添加新的行为或修改现有行为,从而扩展其功能。装饰器模式的目的是增强原始对象的功能,而不是控制对它的访问。

虽然说两者都是通过外层的类来进行控制的。但是你可理解为主动权不一样。

这里我就不多说了,可以看到这篇文章,而且确实也是很常见的。

Java Dome(AOP模式回顾小Dome)

我们可以用代理来实现AOP,切面。

外观模式

一句话:隐藏系统的复杂性,并向客户端提供了一个客户端可以访问的系统接口。这个活我觉得大部分的人都很喜欢干。比如我,我有个习惯,就是每次在开发一个东西的时候,要用到什么第三方,或者什么新的 东西,然后想要实现某些功能的时候它的API调用比较反人类(反我的编码习惯)的时候,我就喜欢封装一下,然后给一个接口,我只关心它要什么,输出什么,什么时候用。举个例子,玩Redis的时候,对redistemplate的api直接做一个封装,要么自己封装,要么嫖现成的,避免关注到太多细节,不利于编码和学习。扯到这个,就不得不提到思维导读在学习当中的重要性了。

这个我们常常使用的SpringBoot+MVC的时候,我们用的注解啥的,就是一个非常直观的例子。

这个具体案例就不给了,太常见了。

享元模式

享元模式(Flyweight Pattern)是一种软件设计模式,它旨在优化大量细粒度对象的内存使用和性能。该模式通过共享对象来减少内存占用,同时提高系统的性能。

在享元模式中,将对象分为可共享的内部状态和不可共享的外部状态。内部状态可以被多个对象共享,而外部状态则取决于特定的对象环境,并且不能被共享。

享元模式的核心思想是利用共享来避免创建大量相似对象,从而减少内存占用。当需要创建一个对象时,首先检查对象池中是否已经存在相同内部状态的对象,如果存在,则直接返回共享的对象;如果不存在,则创建一个新的对象,并将其加入到对象池中以供后续使用。

通过使用享元模式,可以有效地减少系统中对象的数量,降低内存占用,并提高系统的性能。但需要注意的是,在使用享元模式时,要确保共享对象的状态是不可变的,否则可能会导致对象池中的对象状态被修改,从而引发错误。

人话,该公用就公用节约资源,该多例就多例

能够共享的对象,我们就放在一起进行维护。比如缓存这种(一段时间内是不变的)我们直接走缓存,大家共享呗,如果不是,或者情况不同,比如每个用户对应的信息订单这种,我们肯定没法共享。
·

行为模式

okey,到了我们这边的行为模式咯。我们实际的代码都是这些模式好几个混着用的,还是那句话其实是没有必要分那么清楚,真的会用,你写代码的时候,除非是遇到了某些比较吻合我们这些设计模式里面的情况的情况下,你可能会自觉的使用外,其实很多时候,你尊从我们的六个设计原则,很自然就能够写出比较合理的代码。因为实际工程上还要避免过度设计的问题。

责任链模式

说白了就是流水线。假设有一个订单处理系统,订单需要经过多个环节进行处理,包括验证、支付、库存检查和配送等环节。每个环节都有自己的处理规则和责任。

public abstract class Handler {
    protected Handler nextHandler;

    public void setNextHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    public abstract void handleRequest(Order order);
}

之后是我们具体的Handler


public class ValidateHandler extends Handler {
    public void handleRequest(Order order) {
        if (order.isValid()) {
            System.out.println("订单验证通过");
        } else {
            if (nextHandler != null) {
                nextHandler.handleRequest(order);
            } else {
                System.out.println("订单验证未通过");
            }
        }
    }
}

public class PaymentHandler extends Handler {
    public void handleRequest(Order order) {
        if (order.hasPaid()) {
            System.out.println("订单支付成功");
        } else {
            if (nextHandler != null) {
                nextHandler.handleRequest(order);
            } else {
                System.out.println("订单支付失败");
            }
        }
    }
}

public class StockCheckHandler extends Handler {
    public void handleRequest(Order order) {
        if (order.hasEnoughStock()) {
            System.out.println("库存检查通过");
        } else {
            if (nextHandler != null) {
                nextHandler.handleRequest(order);
            } else {
                System.out.println("库存不足");
            }
        }
    }
}

public class ShippingHandler extends Handler {
    public void handleRequest(Order order) {
        if (order.canBeShipped()) {
            System.out.println("订单已发货");
        } else {
            if (nextHandler != null) {
                nextHandler.handleRequest(order);
            } else {
                System.out.println("订单无法发货");
            }
        }
    }
}

然后是测试代码:

Handler validateHandler = new ValidateHandler();
Handler paymentHandler = new PaymentHandler();
Handler stockCheckHandler = new StockCheckHandler();
Handler shippingHandler = new ShippingHandler();

// 设置处理器的下一个处理器
validateHandler.setNextHandler(paymentHandler);
paymentHandler.setNextHandler(stockCheckHandler);
stockCheckHandler.setNextHandler(shippingHandler);

其实很多框架,比如Spring的Bean周期,Vue的生命周期,其实宏观上面看,就是一个责任链路。

命令模式

将请求封装成一个对象,从而使得可以使用不同的请求来参数化其他对象,并且能够将请求排队或记录日志、撤销等操作。这种模式让请求发送者和接收者彼此独立,它们不需要知道彼此的存在,只需要通过Command对象来进行交互。

举个简单的例子,就比如我们的遥控器,我们按下遥控器的按钮,此时发送了命令。然后电视接收到了命令,完成操作。

所以这个场景(模式)的话我们是包括了这几个部分的:

  1. 命令(抽象)
  2. 具体命令
  3. 发送者
  4. 接收者(定义了执行方法)
  5. 客户端,根据命令设置接收者(执行方法)

这个例子的话,其实我的上一篇博文,netty的一个整合,其实对不不同的消息类型的处理其实就是一个命令模式使用。

现在我们还是举个电视机的例子吧:

首先我们有个TV:

public class TV {
    public void turnOn() {
        System.out.println("电视已开机");
    }
    
    public void turnOff() {
        System.out.println("电视已关机");
    }
    
    public void volumeUp() {
        System.out.println("音量增加");
    }
    
    public void volumeDown() {
        System.out.println("音量减小");
    }
    
    public void changeChannel() {
        System.out.println("频道切换");
    }
}

然后我们有个抽象的指令:

public interface Command {
    void execute();
}

之后我们有好几个具体的指令,这里我就只贴出一个了:

public class TurnOnCommand implements Command {
    private TV tv;
    
    public TurnOnCommand(TV tv) {
        this.tv = tv;
    }
    
    @Override
    public void execute() {
        tv.turnOn();
    }
}

之后是我们的一个调用者:


public class RemoteControl {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
}

之后的话我们直接使用:

public class Client {
    public static void main(String[] args) {
        TV tv = new TV();
        
        Command turnOnCommand = new TurnOnCommand(tv);
        Command turnOffCommand = new TurnOffCommand(tv);
        Command volumeUpCommand = new VolumeUpCommand(tv);
        Command volumeDownCommand = new VolumeDownCommand(tv);
        Command changeChannelCommand = new ChangeChannelCommand(tv);
        
        RemoteControl remoteControl = new RemoteControl();
        
        // 设置命令并执行
        remoteControl.setCommand(turnOnCommand);
        remoteControl.pressButton(); // 执行开机命令
        
        remoteControl.setCommand(volumeUpCommand);
        remoteControl.pressButton(); // 执行增加音量命令
        
    }
}

这个时候你发现其实和我们的代理(装饰器)模式有那么点像,只是呢,首先没有对方法增强(也可以增强)然后只是把某一个抽离出来。使得在业务上面看起来更加明朗。

迭代器模式

顺着走,其实我们这边搞个举到了一个责任链的模式,其实有点像,只是区别是,当前状态用到了上一个状态(有点DP的意思哈),然后硬编码的形式走你。其实我们也可以走迭代器,对于简单的一个情况下。那么我们优化一下,就变成了这个:

首先我们先抽象出一层,搞一个HandlerIterator最外层的。

java
public class HandlerIterator implements Iterator<Handler> {
    private List<Handler> handlers;
    private int current;

    public HandlerIterator(List<Handler> handlers) {
        this.handlers = handlers;
        this.current = 0;
    }

    @Override
    public boolean hasNext() {
        return current < handlers.size();
    }

    @Override
    public Handler next() {
        Handler handler = handlers.get(current);
        current++;
        return handler;
    }
}

然后迭代处理就可以了

public class Client {
    public static void main(String[] args) {
        List<Handler> handlers = new ArrayList<>();
        handlers.add(new ValidateHandler());
        handlers.add(new PaymentHandler());
        handlers.add(new StockCheckHandler());
        handlers.add(new ShippingHandler());

        HandlerIterator iterator = new HandlerIterator(handlers);

        Order order = new Order();
        // 处理订单
        while (iterator.hasNext()) {
            Handler handler = iterator.next();
            handler.handleRequest(order);
        }
    }
}

中介模式

中介模式和我们的代理模式其实看上去很像,但是区别很明显的一个就是,我们的代理模式是一种结构型模式,主要在于对象的一个控制。中介模式是一种行为模式,是对一类对象进行处理。 一个对一个对象,进行控制和增强,一个是对一类对象进行处理。

比如我们的ORM框架,他就是个中介。多个数据库,多种数据库,连接到我们的ORM框架,然后ORM框架为我们的应用提供服务。

在这里插入图片描述
这个我感觉没啥好说的。

备忘录模式

这个我觉得没啥好说的

  1. 记得数据备份是好习惯
  2. 数据预加载也是好习惯
  3. 记得Config备份回滚
  4. 版本控制不限于git,代码配置数据也注意

观察者模式

用于在对象之间建立一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖它的对象都能够得到通知并自动更新。

  1. 主题(Subject):也称为被观察者或可观察对象,主题维护了一组观察者对象,并提供了添加、删除和通知观察者的方法。主题可以是具体的类或接口。
  2. 观察者(Observer):观察者定义了接收和处理主题通知的方法。观察者通过订阅主题来注册自己,并在主题状态发生变化时得到通知。
  3. 具体主题(Concrete Subject)和具体观察者(Concrete Observer):具体主题是主题的具体实现类,具体观察者是观察者的具体实现类。它们实现了主题和观察者的相应接口,并根据业务需求定义具体的行为。

举个简答的例子就是,我们那个订阅的一个例子,有哪些用户订阅了什么对象之类的。

举个例子我们现在有个专栏:


public class Column {
    private String name;
    private List<Observer> subscribers;

    public Column(String name) {
        this.name = name;
        this.subscribers = new ArrayList<>();
    }

    public void subscribe(Observer subscriber) {
        subscribers.add(subscriber);
    }

    public void unsubscribe(Observer subscriber) {
        subscribers.remove(subscriber);
    }

    public void notifySubscribers(String article) {
        for (Observer subscriber : subscribers) {
            subscriber.update(article);
        }
    }
}

然后我们有个观察者(其实就是我们的用户)

java
public interface Observer {
    void update(String article);
}
public class User implements Observer {
    private String name;

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

    @Override
    public void update(String article) {
        System.out.println("Hi " + name + "! A new article '" + article + "' has been published in the subscribed column.");
    }
}

之后我们的测试代码,我们可以看到哪些用户订阅了,然后给通知等等。

public class Test {
    public static void main(String[] args) {
        // 创建专栏
        Column techColumn = new Column("Technology");

        // 创建用户
        User user1 = new User("Alice");
        User user2 = new User("Bob");

        // 用户订阅专栏
        techColumn.subscribe(user1);
        techColumn.subscribe(user2);

        // 发布新文章
        techColumn.notifySubscribers("Introduction to AI");

        // 用户取消订阅
        techColumn.unsubscribe(user2);

        // 再次发布新文章
        techColumn.notifySubscribers("Deep Learning Applications");
    }
}

状态模式

它允许对象在内部状态改变时改变其行为。该模式将对象的状态封装成独立的类,并将状态的转换逻辑封装在状态类中。这样,当对象的状态发生改变时,它可以自动切换到适当的状态类,从而改变其行为。

其实很多时候我们直接用枚举类也是ok的,当然如果你的状态有特殊的操作也是okey的。

public interface State {
    void handle();
}
java
public class NormalState implements State {
    @Override
    public void handle() {
        System.out.println("Normal state: Performing normal operations...");
    }
}

public class WarningState implements State {
    @Override
    public void handle() {
        System.out.println("Warning state: Performing warning operations...");
    }
}

public class ErrorState implements State {
    @Override
    public void handle() {
        System.out.println("Error state: Performing error operations...");
    }
}
public class Context {
    private State currentState;

    public Context() {
        // 初始状态为正常状态
        currentState = new NormalState();
    }

    public void setState(State state) {
        currentState = state;
    }

    public void request() {
        currentState.handle();
    }
}

策略模式

它允许在运行时选择算法的行为。该模式将不同的算法封装成独立的策略类,并且这些策略类实现了相同的接口,以便在运行时可以互相替换使用。

在代码上其实和我们的装饰器模式很像的。只不过,装饰器模式在于创建新的对象,而我们的策略模式相当于替换不同的策略组件。

public interface Strategy {
    void execute();
}

然后我们这里定义不同的策略

public class ConcreteStrategyA implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy A...");
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy B...");
    }
}

public class ConcreteStrategyC implements Strategy {
    @Override
    public void execute() {
        System.out.println("Executing strategy C...");
    }
}

然后愉快玩耍

public class Context {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.execute();
    }
}

当然实际上,你还可以配合枚举类,然后搞个HashMap,把这些对象都放在一个容器当中。

然后你也发现了,这个状态模式和策略模式很像,是的,其实只是表示的状态不同,一个侧重状态的描述,一个侧重不同的状态采用不同的算法方案。

模板模式

顾名思义,其实就是模板它定义了一个算法的骨架,将一些步骤的实现延迟到子类中。这样可以在不改变算法结构的情况下,通过子类对某些步骤进行重定义,从而实现算法的定制化。比如我那个任务框架里面的那个任务清单的模板(当然我那个模板本质上还是一个代理,方法都是动态加上去的)

public abstract class AbstractTemplate {
    public void templateMethod() {
        // 步骤1
        step1();

        // 步骤2
        step2();

        // 步骤3
        step3();
    }

    protected abstract void step1();
    protected abstract void step2();

    protected void step3() {
        System.out.println("Default implementation of step3");
    }
}

然后实现类就这样玩:

public class ConcreteTemplate extends AbstractTemplate {
    @Override
    protected void step1() {
        System.out.println("Step1 implementation in ConcreteTemplate");
    }

    @Override
    protected void step2() {
        System.out.println("Step2 implementation in ConcreteTemplate");
    }

    @Override
    protected void step3() {
        System.out.println("Step3 implementation in ConcreteTemplate");
    }

访问者模式

访问者模式的核心思想是将数据结构和操作解耦,使得操作可以独立变化。它通过在数据结构中定义一个公共的接受访问者的方法,让访问者对象对数据结构中的每个元素进行访问,并执行相应的操作。
直接参考MVC设计思想。

总结

在这里我们有六大设计法则:

  1. 单一职责
  2. 开闭原则
  3. 里氏替换原则
  4. 迪米特法则
  5. 接口隔离原则
  6. 依赖倒置原则

之后我们有23个基本设计模式,我们对设计模式进行简单分类又分为三大类型。

  1. 创建型
  2. 结构性
  3. 行为型

具体到每一个分类是:

一 创建型

  1. 简单工厂
  2. 工厂方法
  3. 抽象工厂
  4. 建造者模式
  5. 原型模式
  6. 单例模式

二 结构型

  1. 适配器
  2. 桥接模式
  3. 组合模式
  4. 装饰器
  5. 代理模式
  6. 外观模式
  7. 享元模式

三 行为型

  1. 责任链模式
  2. 命令模式
  3. 迭代模式
  4. 中介模式
  5. 备忘录模式
  6. 观察者模式
  7. 状态模式
  8. 策略模式
  9. 模板模式
    10.访问者模式

以上就是全部内容了~

文章来源:https://blog.csdn.net/FUTEROX/article/details/135287722
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。