1. 概览
1.1 谁发明了设计模式?
设计模式的概念最早是由 克里斯托佛·亚历山大
在其著作 《建筑模式语言》
中首次提出的。 本书介绍了城市设计的 “语言”,提供了253个描述城镇、邻里、住宅、花园、房间及西部构造的模式, 而此类 “语言” 的基本单元就是模式。后来,埃里希·伽玛
、 约翰·弗利赛德斯
、 拉尔夫·约翰逊
和 理查德·赫尔姆
这四位作者接受了模式的概念。 1994 年, 他们出版了 《设计模式: 可复用面向对象软件的基础》
一书, 将设计模式的概念应用到程序开发领域中。
1.2 设计模式能给我们什么样的帮助?
- 优化平时开发中的ifelse语句,让代码更加整洁
- 站在更高的角度去看待编程开发,学会更多的面向对象的思维,尤其是;接口、抽象类、多态等使用
- 升职、加薪,良好的代码是效能提升的基础,成为本组编码最靓的精神小伙
1.3 设计模式需要依据的设计原则
设计模式遵循六大原则:
-
单一职责(
一个类和方法只做一件事
)public class UserInfo { private long userId; private String username; private String email; private String telephone; private long createTime; private long lastLoginTime; private String avatarUrl; private String provinceOfAddress; // 省 private String cityOfAddress; // 市 private String regionOfAddress; // 区 private String detailedAddress; // 详细地址 // ...省略其他属性和方法...} 复制代码
判断类的职责是否足够单一的几个依据?
1、类中的代码行数、函数或者属性过多;
2、类依赖的其他类过多,或者依赖类的其他类过多;
3、私有方法过多;比较难给类起一个合适的名字;
-
开闭原则(
抽象架构,扩展实现
)添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
注意:
1、开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。
2、同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。
-
里氏替换(
多态,子类可扩展父类
)public class Transporter { private HttpClient httpClient; public Transporter(HttpClient httpClient) { this.httpClient = httpClient; } public Response sendRequest(Request request) { // ...use httpClient to send request } } public class SecurityTransporter extends Transporter { private String appId; private String appToken; public SecurityTransporter(HttpClient httpClient, String appId, String appToken) { super(httpClient); this.appId = appId; this.appToken = appToken; } @Override public Response sendRequest(Request request) { if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) { request.addPayload("app-id", appId); request.addPayload("app-token", appToken); } return super.sendRequest(request); } } public class Demo { public void demoFunction(Transporter transporter) { Reuqest request = new Request(); //...省略设置request中数据值的代码... Response response = transporter.sendRequest(request); //...省略其他逻辑... } } // 里式替换原则 Demo demo = new Demo(); demo.demofunction(new SecurityTransporter(/*省略参数*/);); 复制代码
里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。
-
依赖倒置(
细节依赖抽象,下层依赖上层
)高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。
Tomcat 是运行 Java Web 应用程序的容器。Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。
-
接口隔离(
建立单一接口
)//*****类库接口******// public interface UserService { boolean register(String cellphone, String password); boolean login(String cellphone, String password); UserInfo getUserInfoById(long id); UserInfo getUserInfoByCellphone(String cellphone); } public class UserServiceImpl implements UserService { //... } //*****函数接口******// public class Statistics { private Long max; private Long min; private Long average; private Long sum; private Long percentile99; private Long percentile999; //...省略constructor/getter/setter等方法... } public Statistics count(Collection<Long> dataSet) { Statistics statistics = new Statistics(); //...省略计算逻辑... return statistics; } //*****oop中的接口******// public interface Updater { void update(); } public class RedisConfig implemets Updater { //...省略其他属性和方法... @Override public void update() { //... } } public class KafkaConfig implements Updater { //...省略其他属性和方法... @Override public void update() { //... } } 复制代码
1、“接口”可以理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。
2、“接口”可以理解为单个 API 接口或函数。
3、“接口”也可以理解为面向对象编程语言中的接口语法。
-
迪米特原则(
最少知道,降低耦合
)public class NetworkTransporter { // 省略属性和其他方法... public Byte[] send(HtmlRequest htmlRequest) { //... } } public class HtmlDownloader { private NetworkTransporter transporter;//通过构造函数或IOC注入 public Html downloadHtml(String url) { Byte[] rawHtml = transporter.send(new HtmlRequest(url)); return new Html(rawHtml); } } public class Document { private Html html; private String url; public Document(String url) { this.url = url; HtmlDownloader downloader = new HtmlDownloader(); this.html = downloader.downloadHtml(url); } //... } 复制代码
不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。
2.设计模式概览
2.1 创建型
这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复用性。
序号 | 类型 | 图稿 | 业务场景 | 实现要点 |
---|---|---|---|---|
1 | 工厂方法 | ![]() |
多种类型商品不同接口,统一发奖服务搭建场景 | 定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。 |
2 | 抽象工厂 | ![]() |
替换Redis双集群升级,代理类抽象场景 | 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 |
3 | 建造者 | ![]() |
各项装修物料组合套餐选配场景 | 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 |
4 | 原型 | ![]() |
上机考试多套试,每人题目和答案乱序排列场景 | 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 |
5 | 单例 | ![]() |
7种单例模式案例,Effective Java 作者推荐枚举单例模式 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 |
2.2 结构型
这类模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
序号 | 类型 | 图稿 | 业务场景 | 实现要点 |
---|---|---|---|---|
1 | 适配器 | ![]() |
从多个MQ消息体中,抽取指定字段值场景 | 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 |
2 | 桥接 | ![]() |
多支付渠道(微信、支付宝)与多支付模式(刷脸、指纹)场景 | 将抽象部分与实现部分分离,使它们都可以独立的变化。 |
3 | 组合 | ![]() |
营销差异化人群发券,决策树引擎搭建场景 | 将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 |
4 | 装饰 | ![]() |
SSO单点登录功能扩展,增加拦截用户访问方法范围场景 | 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。 |
5 | 外观 | ![]() |
基于SpringBoot开发门面模式中间件,统一控制接口白名单场景 | 为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 |
6 | 享元 | ![]() |
基于Redis秒杀,提供活动与库存信息查询场景 | 运用共享技术有效地支持大量细粒度的对象。 |
7 | 代理 | ![]() |
模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景 | 为其他对象提供一种代理以控制对这个对象的访问。 |
2.3 行为型
这类模式负责对象间的高效沟通和职责委派。
序号 | 类型 | 图稿 | 业务场景 | 实现要点 |
---|---|---|---|---|
1 | 责任链 | ![]() |
模拟618电商大促期间,项目上线流程多级负责人审批场景 | 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 |
2 | 命令 | ![]() |
模拟高档餐厅八大菜系,小二点单厨师烹饪场景 | 将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。 |
3 | 迭代器 | ![]() |
模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景 | 提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。 |
4 | 中介者 | ![]() |
按照Mybatis原理手写ORM框架,给JDBC方式操作数据库增加中介者场景 | 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 |
5 | 备忘录 | ![]() |
模拟互联网系统上线过程中,配置文件回滚场景 | 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。 |
6 | 观察者 | ![]() |
模拟类似小客车指标摇号过程,监听消息通知用户中签场景 | 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 |
7 | 状态 | ![]() |
模拟系统营销活动,状态流程审核发布上线场景 | 允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。 |
8 | 策略 | ![]() |
模拟多种营销类型优惠券,折扣金额计算策略场景 | 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。 |
9 | 模板方法 | ![]() |
模拟爬虫各类电商商品,生成营销推广海报场景 | 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 |
10 | 访问者 | ![]() |
模拟家长与校长,对学生和老师的不同视角信息的访问场景 | 主要将数据结构与数据操作分离。 |
参考文献
《设计模式之美》
《重学设计模式》