开发一个包子铺,用得着这么多设计模式?

大家知道我最近结合AI+DDD开发了一个包子铺系统,谁说写业务代码就一定是CURD?再简单的项目也可以用设计模式,这就来盘点一下我在包子铺项目中用到的设计模式

事先声明:我并没有为了炫技而引入一些复杂的设计,是因为项目中确实有必要用到。

关于包子铺系统的源码 ,如果你感兴趣,可以戳我查看源码获取方式

设计模式的类型

先回顾一下八股文,设计模式一共23种,分为三大类:

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式到底有没有用?我们来结合案例说一说。

工厂模式 Factory

设计模式中与工厂相关的其实有三种:简单工厂 Simple Factory工厂方法 Factory Method抽象工厂 Abstract Factory

这三种模式的复杂度依次递增,如果你实在分不清,就这么记:

  • 简单工厂就是每个对象都有自己的工厂
  • 工厂方法就是要生产一类产品,且这类产品存在比较大共性
  • 抽象工厂就是要创建很多类产品,并且这些产品构成了一个产品族

图我就不画了,老实说抽象工厂是我认为在设计模式中为数不多用处不大的模式,因为这里面有过度设计之嫌,应用在实际业务中有些太臃肿了。

在包子铺项目的应用

包子铺项目中绝大多数用的是简单工厂,只有交易订单的创建用到了工厂方法,因为包子铺有三类订单:直营渠道订单、外卖渠道订单、线下门店订单,刚好形成了一类"产品",这就比较适合用工厂方法:

工厂模式有什么用

要知道对象创建的过程也是包含逻辑的,工厂的意义就是你只需要提供原材料,工厂负责生产出商品。

举个例子,在创建订单的时候,下单时间、用户ID、用户购买的商品类型和数量,这些都是明确知道的,也就是原材料,而订单的总金额、订单是否涉及配送则需要计算得出。把对象的完整创建过程封装在工厂内,以保证对象创建的内聚性。

建造者模式 Builder

这个模式有用过Lombok的应该不陌生,Builder模式的好处是把复杂对象的创建分解成多个子步骤,这种情况下就很适合Builder。

Builder的另一个好处是代码编写上可以用链式调用,语法简洁清晰。

为什么有工厂模式还要用Builder

确实这二者都是构造型模式,选其一就可以了。

严格来说,包子铺只用到了Factory,至于Builder只是用到了Lombok提供的语法糖。通常领域模型是充血模型 Rich Model,因此不会提供Setter方法。从用户请求进来转化成模型我会用Factory模式,但是从物理数据模型转成领域模型更多还是会用Builder。

java 复制代码
// 用Lombok的两个注解
@Getter
@Builer
public class SomeEntity {
    private ID id;
    private Money transAmount;
    //...
}

单例模式 Singleton

单例模式的作用是保证对象的全局唯一性 ,主要用在 领域服务 Domain Service应用服务App Service 和一些全局配置上。,

默认情况下,Spring框架的@Controller@Service@Repository@Component几个注解声明的Bean都是单例的。这里就不再做过多的说明。

装饰器 Decorator

Decorator这个词也有粉刷匠的意思,试想一下一个粉刷匠,用各色的油漆在你家的房子上装点,刷了一层又一层,房子还是那个房子(功能不变),但是外观变漂亮了。

在设计模式中,装饰器主要用于在一个组件上叠加新的能力,同时保证组件的基本能力不变。

我们可以在不影响原有组件能力的基础上为其增加新能力,这个模式真的是太棒了!

在包子铺项目的应用

每个Entity都有一个ID,为了统一,所有的ID都需要实现Identifier,但ID生成其实是存在共性的,如果是你怎么处理这些共性?

很多同学大概会把共性"抽出来",写一个类似IDUtil的工具类,这种是面向过程 的思路,而我使用了面向对象的解法。

ID其实有很多种类型:

  • 基于日期的流水型数据,适合用DateBasedID
  • 数据量不大的配置型数据,适合用AutoIncreasedID

我这里用的是装饰器模式的变体,OrderID、ProductID、ConfigID都是装饰器,通过包装DateBasedID或AutoIncreasedID就可以轻松扩展其能力,并且代码的层次结构得以清晰地展现。

观察者 Observer

观察者模式常用于不同组件、模块间的解耦,A模块本需要调用B模块,但B模块的职责并没有那么重要,A不希望强依赖B,那么A就会通过发布一个领域事件,由B来订阅消费,这时候B就成了一个观察者。

包子铺中用到的观察者模式:

这一套机制可以很好地把事件的发布和订阅解耦,并且预留了足够的扩展性,事件分发默认基于数据库来实现,当然将来也可以增加其他的实现机制(例如基于MQ来实现)

关于领域事件的用法,很多人其实搞不明白,滥用事件的后果就是事件满天飞,代码跳来跳去影响可读性;但有些时候又是必须要用领域事件的。怎么掌握好这个度?这里埋一个坑,后面有机会我会讲讲这个话题。

外观 Facade

外观模式应该是所有模式里最好理解的了,如果你设计了一个系统,并通过对外暴露一些接口来提供服务。这就是外观模式。

外观模式的思想基础是六大设计原则中的最少知识原则 Least Knowledge Principle,就好像火车或飞机购票一样,其背后是由一套非常复杂的系统在运作,但顾客不需要关心那些细节,顾客只需要与售票窗口的售票员交流就可以买到票。

在包子铺中,所有对外的接口都已Facade的形式提供,就连命名上也是xxxFacade,这样看起来会一目了然。

模板方法 Template Method

包子铺中有许多定时任务,为了更好地管理这些任务,我给这些任务的执行定义了一套标准,大致如下:

java 复制代码
public abstract class AbstractTask implements Runnable {
    private TaskID taskId;
    
    public void run() {
        Task task = findTaskWithLock(taskId);
        Assert.notNull(task);
        if (task.isDone()) {
            return;
        }
        changeTaskToRunning(task);
        try {
            runTask(task);
            changeTaskToSuccess(task);
        } catch ... {
            changeTaskToFailed(task);
        } finally {
            
        }
    }
    
    // 提供给子类的扩展点
    protected abstract void runTask(Task task);
}

AbstractTask给所有的定时任务提供了一套运行模板,包括怎么做状态管理,记录错误信息,是否要重试等。 而每个具体的任务,就只需要扩展runTask()即可。

适配器 Adapter

用过MacBook的朋友都知道,最新版MacBook都只支持Type-C接口了,这时如果你恰好有一个U盘需要连接到电脑是不行的,那么你就需要一个USB转Type-C的转换器,这个转换器就是一个适配器 Adapter

适配器模式就是要将一种接口转换成另一种接口的模式,让原本不兼容的接口可以适配。

在包子铺中用户下单成功需要调用短信通道发送短信,但是应用层不希望感知具体的短信通道细节,因为那会使业务与具体的短信通道耦合性太高,这时就需要引入适配器:

常见的适配器有两种,上述例子给出的是基于接口的适配器模式,还有一种是基于类的适配器,感兴趣可以自行查资料研究一下。

策略模式 Strategy

依然是上面的短信发送例子,我们对接了多个短信通道,但每个通道并不都是那么稳定,因此我们希望通过动态调配比例来实现通道双活。

只需要简单增加一个类就实现了策略模式:

策略模式的作用在于定义了一个算法的多个实现(这里指的就是多个短信通道实现),并且算法之间可以互相替换,客户端可以根据不同的策略调整算法。

后记

其实包子铺用到的设计模式远不止如此,由于篇幅原因就先总结到这里。设计模式是非常神奇的东西,写代码也远不止是CRUD,希望对看到这篇文章的你有所启发。

相关推荐
charlie1145141912 小时前
从C++编程入手设计模式——外观模式
c++·设计模式·外观模式
昕冉5 小时前
UML图之学习绘制样例
设计模式·uml
qqxhb5 小时前
零基础设计模式——行为型模式 - 访问者模式
java·设计模式·go·访问者模式
码农颜5 小时前
java 设计模式_行为型_16访问者模式
java·设计模式·访问者模式
T___1 天前
从入门到放弃?带你重新认识 Headless UI
前端·设计模式
葬送的代码人生1 天前
AI Coding→像素飞机大冒险:一个让你又爱又恨的小游戏
javascript·设计模式·ai编程
勤奋的知更鸟1 天前
Java编程之外观模式
java·开发语言·设计模式·外观模式
渣渣_Maxz2 天前
使用 antlr 打造 Android 动态逻辑判断能力
android·设计模式
码农颜2 天前
java 设计模式_行为型_19命令模式
java·设计模式·命令模式