logo头像
ICQL

设计模式

相关源码

https://gitee.com/icql/icql-java/tree/master/designpattern
https://github.com/icql/icql-java/tree/master/designpattern

代码质量

1、可维护性(maintainability)

如果bug容易修复,修改、添加功能能够轻松完成,那我们就可以主观地认为代码对我们来说易维护。相反,则较难维护

2、可读性(readability)

符合编码规范,注释详尽,代码高内聚低耦合

3、可扩展性(extensibility)

在不修改或少量修改原有代码的情况下,通过扩展的方式添加新功能。符合”开闭原则”:对扩展开放,对修改关闭

4、灵活性(flexibility)

代码适用性高,可复用

5、简洁性(simplicity)

KISS 原则:”Keep It Simple,Stupid”

6、可复用性(reusability)

减少重复代码的编写,面向对象的继承、多态存在的目的之一,就是为了提高代码的可复用性

7、可测试性(testability)

比较难写单元测试,说明可测试性不好,代码应边界分离便于测试




面向对象

面向对象编程,面向过程编程,函数式编程

面向对象分析

OOA(Object Oriented Analysis):做什么

面向对象设计

OOD(Object Oriented Design):怎么做

面向对象编程

OOP(Object Oriented Programming):将分析和设计翻译成代码

四大特性(封装、抽象、继承、多态)

封装(Encapsulation)

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫方法)来访问内部信息或者数据。

对提供者来说,保护数据隐藏信息;对调用方来说,不需要了解内部的逻辑,只需要访问公开的接口,提高易用性

抽象(Abstraction)

让调用着只要关心提供了哪些功能,而不需要知道实现细节

java提供了接口和抽象类语法特性,方法本身也是一种抽象,调用者只需要通过方法名注释文档了解其功能就可以直接使用

基于接口而非实现编程,开闭原则,代码解耦

继承(Inheritance)

表示is-a的关系,单继承或多继承,但避免继承层次过深复杂,因此尽量少用或不用

多态(Polymorphism)

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。

java实现多态有2种:继承、接口

面向过程和面向对象的区别

面向对象编程是一种编程范式或编程风格,它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石

面向过程编程也是一种编程范式或编程风格,它以过程(可以为理解方法、函数、操作)作为组织代码的基本单元,以数据(可以理解为成员变量、属性)与方法相分离为最主要的特点

面向对象开发中常见的面向过程风格:滥用 getter、setter 方法;Constants 类、Utils 类等静态方法的设计问题;基于贫血模型的开发模式

接口和抽象类

表示is-a关系,使用抽象类,是对成员变量和方法的抽象,解决代码复用问题

表示has-a关系,使用接口,表示具有某一组行为特性,用于隔离接口和具体实现,解耦代码,提高扩展性

基于接口而非实现编程

多用组合少用继承。若继承层次过深或者关系不稳定优先使用组合,从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface Flyable {
void fly()
}

public class FlyAbility implements Flyable {
@Override
public void fly() { //... }
}

// 省略 Tweetable/TweetAbility/EggLayable/EggLayAbility
public class Ostrich implements Tweetable, EggLayable {// 鸵鸟
private TweetAbility tweetAbility = new TweetAbility(); // 组合
private EggLayAbility eggLayAbility = new EggLayAbility(); // 组合
//... 省略其他属性和方法...
@Override
public void tweet() {
tweetAbility.tweet(); // 委托
}
@Override
public void layEgg() {
eggLayAbility.layEgg(); // 委托
}
}

没有权限修改一个外部类时,可以使用继承重写原来的方法

贫血模型和充血模型

基于贫血模型的 MVC 分层架构:M 表示 Model(模型,数据层),V 表示 View(视图,展示层),C 表示 Controller(控制,逻辑层)

基于充血模型的 DDD 分层架构

类之间的关系(6种)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//1)泛化(Generalization)可以简单理解为继承关系
public class A { ... }
public class B extends A { ... }

//2)实现(Realization)一般是指接口和实现类之间的关系
public interface A {...}
public class B implements A { ... }

//3)聚合(Aggregation)是一种包含关系,A 类对象包含 B 类对象,B 类对象的生命周期可以不依赖 A 类对象的生命周期,
//也就是说可以单独销毁 A 类对象而不影响 B 对象,比如课程与学生之间的关系
public class A {
private B b;
public A(B b) {
this.b = b;
}
}

//4)组合(Composition)也是一种包含关系。A 类对象包含 B 类对象,B 类对象的生命周期跟依赖 A 类对象的生命周期,
//B 类对象不可单独存在,比如鸟与翅膀之间的关系
public class A {
private B b;
public A() {
this.b = new B();
}
}

//5)关联(Association)是一种非常弱的关系,包含聚合、组合两种关系。具体到代码层面,
//如果 B 类对象是 A 类的成员变量,那 B 类和 A 类就是关联关系


//6)依赖(Dependency)是一种比关联关系更加弱的关系,包含关联关系。
//不管是 B 类对象是 A 类对象的成员变量,还是 A 类的方法使用 B 类对象作为参数或者返回值、局部变量,
//只要 B 类对象和 A 类对象有任何使用关系,我们都称它们有依赖关系。

设计原则

SOLID原则

单一职责原则(Single Responsibility Principle,SRP)

不要设计大而全的类,要合理地设计粒度相对较小、功能单一的模块/类,取舍要有度

开闭原则(Open Closed Principle,OCP)

对扩展开放、对修改关闭。也就是尽可能的做到,添加一个新的功能应该是,在已有代码基础上扩展代码而非修改原有代码逻辑

里式替换原则(Liskov Substitution Principle,LSP)

子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏

接口隔离原则(Interface Segregation Principle,ISP)

客户端不应该强迫依赖它不需要的接口

依赖倒置原则(Dependency Inversion Principle,DIP)

高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

控制反转(Inversion Of Control,IOC):设计思想,对程序执行流程的控制由原来的编码人员 “反转” 给了框架

依赖注入(Dependency Injection,DI):编码技巧,不通过 new 在类内部创建依赖的对象,将依赖的对象在类的外部创建好之后,通过构造函数,方法参数等方式 传递/注入 给类来使用

KISS原则

Keep It Simple and Stupid,尽量保持简单

YAGNI原则

You Ain’t Gonna Need It,你不会需要它。不要去编写当前用不到的代码,不要做过度设计

DRY原则

Don’t Repeat Yourself,不要写重复的代码

LOD原则

Law of Demeter,迪米特法则(最小知识原则),高内聚,低耦合,不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口




编码质量

重构

5W2H分析法,二战美国兵器修理部首创

1
2
3
4
5
6
7
(1)WHAT——是什么?
(2)WHY——为什么要做?
(3)WHO——谁?
(4)WHEN——何时?
(5)WHERE——何处?
(6)HOW ——怎么做?
(7)HOW MUCH——多少?

大规模高层次重构,小规模低层次重构

可测试性

编码规范

高内聚低耦合

程序出错返回值,NULL,空对象,错误码,异常




设计模式

总览图

创建型

主要解决对象的创建问题

1)单例模式

定义:一个类只允许创建一个实例
使用场景:处理资源访问冲突;数据在系统种只应该保存一份(如配置信息)
实现方式:主要考虑以下3个方面:构造函数private,创建对象线程安全,是否延迟加载

饥饿式,懒汉式,双重检测,静态内部类,枚举

单例存在的问题:
  • 单例对OOP特性支持不友好:封装/继承/多态/抽象
  • 单例会隐藏类之间的依赖关系:类似于工具类一样使用时属于硬编码,不便查找
  • 单例对代码的扩展性不友好
  • 单例对代码的可测试性不友好:由于类似硬编码,不方便mock
  • 单例不支持有参数的构造函数
单例的替代方式:工程模式,ioc容器
单例的作用范围
  • 进程内唯一(大多数情况)
  • 线程内唯一(用类似ThreadLocal的Map即可实现)
  • 多进程内唯一(分布式),用一个集中式的共享区域存储,如db数据库等

2)多例模式(特殊的单例)

  • 一个类允许有固定的多个实例
  • 区别于工厂模式,多例是同一个类型的多个实例,而工厂模式是多个不同子类的实例
  • 实现方式,枚举 或 使用map结构

3)工厂模式

  • 用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象
简单工厂
  • 工厂类直接创建对象,static方法,最简单
工厂方法
  • 将创建对象的行为下沉到各个类型自己的工厂类中,这些类型工厂类实现一个工厂接口,和工厂接口一起再使用一个简单工厂模式对外暴漏
抽象工厂
  • 不常用,相当于原来的实例可以通过两个维度去划分,在工厂方法的基础上,每个维度创建实例都在工厂方法中有对应的方法

4)建造者模式

  • 用来创建一种类型的复杂对象,通过设置不同的可选参数,”定制化”地创建不同的对象
  • 对象一旦创建就不允许修改

5)原型模式

  • 对象的创建成本较大,直接根据已有对象复制一个新的对象的方式
  • 深拷贝(Deep Copy)和浅拷贝(Shallow Copy)

结构型

主要总结了一些类或对象组合在一起的经典结构

1)代理模式

  • 在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能
  • 对客户端不透明
应用场景
  • 业务系统的非功能性需求开发:如打印日志,记录耗时,事务控制,幂等,限流,鉴权等等
  • rpc框架(远程请求包装成代理),缓存
实现
  • 代理类和被代理类实现同一个接口,代理类组合一个被代理类对象
  • 代理类继承被代理类
分类
  • 静态代理:指手动编码每个被代理类的代理类
  • 动态代理:利用jdk动态代理(实现同一接口)或 cglib(继承)实现的动态生成代理类
    https://zhuanlan.zhihu.com/p/29188162

2)桥接模式

不常用,将抽象部分与它的实现部分分离,使它们都可以独立地变化

一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展

3)装饰器模式

  • 以对客户端透明的方式,动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活
  • 应用例子:购买煎饼,客户可自由选择加鸡蛋,加肉松,加火腿等等;java-io类中的经典实现

4)适配器模式

  • 这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作
  • 实现方式:类适配器(继承),对象适配器(组合)
  • 应用场景:适配接口,接口兼容,java-log的经典应用(slf4j)

5)门面模式

  • 门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用
  • 简而言之,就是聚合一些细粒度的接口或方法组装成一个面向用户用例的接口,提高易用性,解决一致性,以及一些性能问题

6)组合模式

  • 不常用,将一组对象组织(Compose)成树形结构,以表示一种部分 - 整体的层次结构
  • 应用场景,主要用于树形结构的数据

7)享元模式

  • 运用共享技术有效地支持大量细粒度对象的复用,最简单的办法就是用一个hashmap维护对象作为享元池
  • 享元对象能做到共享的关键是区分了内部状态和外部状态
  • 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享
  • 外部状态是随环境改变而改变的、不可以共享的状态
  • 享元模式和对象池的区别:对象池中的对象在同一时间只能被一个线程使用,目的是为了节省时间,而享元模式则可以多个,目的是为了减少空间占用
  • java类库中的 Integer(IntegerCache) 和 String(字符串常量池) 的应用

行为型

1)观察者模式

  • 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅模式
  • Guava EventBus简单框架
  • 观察者模式和生产消费者模型的区别
    • 观察者:一对多,订阅者不存在竞争关系
    • 生产者消费者:多对多,一般消费者之间存在竞争关系,只会有一个消费者可以收到消息

2)模板模式

  • 模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤
  • 作用一:复用
    • java-io中 InputStream,OutputStream,Reader,Writer等,InputStream 的 read 方法
    • Java AbstractList,addAll(int index, Collection<? extends E> c) 方法 -> add(int index, E element)
  • 作用二:扩展
    • HttpServlet中的service()方法是一个模板方法,它实现了整个 HTTP 请求的执行流程,doGet()、doPost() 是模板中可以由子类来定制的部分
  • 回调机制:同步回调(和模板方法模式作用相似),异步回调

3)策略模式

  • 定义一组算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端
  • 使用策略模式一般借助工厂模式封装策略
  • 取消if else分支

4)职责链模式

  • 将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止
  • 经典应用:Servlet Filter,Spring Interceptor

5)状态模式

  • 状态机(有限状态机)的常用表示法有 分支判断,查表,状态模式

6)迭代器模式

  • 迭代器是用来遍历容器,编程语言一般都已经实现好了
  • 正常情况下,迭代器遍历中为什么不能添加和删除元素?(jdk中在iterator内部类中维护了一个字段lastRet用以解决此问题)
    • 当添加/删除的元素在迭代器游标的前面就会出现问题
    • 当添加/删除的元素在迭代器游标的后面不会出现问题

7)访问者模式

  • 允许一个或者多个操作应用到一组对象上,解耦操作和对象本身

8)备忘录模式

  • 备忘录模式,也叫快照(Snapshot)模式,存储副本以便后期恢复

9)命令模式

  • 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
  • 和策略模式的实现形式很相似,区别:
    • 策略模式是通过不同的算法做同一件事情:例如排序,而命令模式则是通过不同的命令做不同的事情,常含有接收者,如菜单中的复制,移动,压缩

10)解释器模式

  • 解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法

11)中介模式

  • 中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互



实战

面向对象,接口鉴权

最小可行产品(Minimum Viable Product,简称MVP)是一种避免开发出客户并不真正需要的产品的开发策略。
该策略的基本想法是,快速地构建出符合产品预期功能的最小功能集合。

通过迭代逐步优化

面向对象分析:分析需求得出可设计的需求方案描述

面向对象设计:将需求方案描述转化为具体的类设计,贫血/充血,划分功能模块,类职责,定义类属性方法,类之间的交互等

面向对象编码:实现设计的具体编码

面向对象,虚拟钱包

分层思想,业务强相关的钱包(支付,转账,充值等),下沉无关业务的虚拟钱包(只和余额增减,三方支付相关)

面向对象,设计原则,业务系统开发,如何做需求分析和设计,积分系统

面向对象,设计原则,非业务通用系统开发,如何做需求分析和设计,性能计数器

编码质量,id生成器

工厂模式:最小DI容器

微信打赏

赞赏是不耍流氓的鼓励