设计模式和思想
设计模式是在软件开发中常用的解决问题的方法和经验总结。它们提供了一套被广泛认可的解决方案, 用于解决在软件设计和开发过程中常见的问题。 设计模式通过定义了一些通用的架构模式、结构模式和行为模式, 帮助开发人员更好地组织和管理代码, 并提高代码的可维护性、可扩展性和重用性. 另外可以看看 我做系统架构的一些原则 (opens new window) 很有思考深度
# common
# UML
case更多内容可见: link (opens new window)

- 车的类图结构为
<<abstract>>, 表示车是一个抽象类; - 它有两个继承类: 小汽车和自行车;它们之间的关系为实现关系, 使用带空心箭头的虚线表示;
- 小汽车为与 SUV 之间也是继承关系, 它们之间的关系为泛化关系, 使用带空心箭头的实线表示;
- 小汽车与发动机之间是组合关系, 使用带实心箭头的实线表示;
- 学生与班级之间是聚合关系, 使用带空心箭头的实线表示;
- 学生与身份证之间为关联关系, 使用一根实线表示;
- 学生上学需要用到自行车, 与自行车是一种依赖关系, 使用带箭头的虚线表示;
- 可见性
可见性表示该属性对于类外的元素而言是否可见, 包括公有(public)、私有(private)和受保护(protected)三种, 在类图中分别用符号+、- 和 # 表示
- 聚合与组合
在 UML(统一建模语言)中, "聚合"(Aggregation)和"组合"(Composition)是用来表示类之间的关联关系的两个术语
- 聚合表示类之间的一种关联关系, 其中一个类(整体)包含另一个类(部分)。聚合关系是一种弱关系, 意味着整体对象和部分对象可以存在独立于彼此的生命周期。聚合关系通常使用一个带空心菱形的线表示, 该菱形指向整体对象
- 组合表示类之间的一种关联关系, 其中一个类(整体)包含另一个类(部分)。组合关系是一种强关系, 意味着整体对象和部分对象的生命周期是紧密相连的, 部分对象不能独立存在。如果整体对象被销毁, 部分对象也将被销毁。组合关系通常使用一个带实心菱形的线表示, 该菱形指向整体对象
简而言之, 聚合关系表示"整体-部分"的关系, 而组合关系表示"整体-部分"的强关系, 部分对象与整体对象有更紧密的依赖关系
- 关联关系
关联关系是用一条直线表示的;它描述不同类的对象之间的结构关系;它是一种静态关系, 通常与运行状态无关,一般由常识等因素决定的;它一般用来定义对象之间静态的、天然的结构;
- 依赖关系
依赖关系是用一套带箭头的虚线表示的;他描述一个对象在运行期间会用到另一个对象的关系;
# 时序图
为了展示对象之间的交互细节,后续对设计模式解析的章节,都会用到时序图;
时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序
时序图包括的建模元素主要有:对象(Actor)、生命线(Lifeline)、控制焦点(Focus of control)、消息(Message)等等
# 设计模式的六大原则
更多内容可见: https://www.kancloud.cn/digest/xing-designpattern/143718 (opens new window)
- 单一职责原则
- 开闭原则
对于扩展是开放的, 但是对于修改是封闭的
- 里氏替换原则
里氏替换原则是继承复用的基石。只有当衍生类可以替换掉基类, 软件单位的功能不会受到影响时, 基类才能真正的被复用, 而衍生类也才能够在基类的基础上增加新的行为
- 依赖倒置原则
在 java 中的表现是: 模块间的依赖通过抽象发生, 实体类之间不发生直接的依赖关系, 其依赖关系是通过接口或抽象类产生
- 接口隔离原则
接口隔离的目的是系统解开耦合, 从而容易重构, 更改和重新部署
- 迪米特原则
# SPI
SPI 是 Java 平台提供的一种机制, 允许应用程序在运行时动态加载和发现实现某个接口的服务提供者 通常我们会使用 google 的 AutoService (opens new window) 来简化整个过程 SPI 过程.
你可以在 mvnrepository (opens new window) 查看最新的 AutoService 版本
Google Autoservice 的原理- 定义服务接口: 首先, 您需要定义一个服务接口, 该接口定义了所需的功能或行为。接口通常在一个单独的库或模块中定义, 并作为提供者和使用者之间的契约
- 实现服务提供者: 接下来, 您需要实现服务接口的一个或多个具体实现。每个实现都需要提供一个无参数的构造函数, 并且通常在独立的模块中实现
- 创建 SPI 配置文件: 为了将服务提供者注册到应用程序中, 您需要创建一个 SPI 配置文件。该配置文件是一个文本文件, 位于 META-INF/services 目录下, 文件名与服务接口的全限定名相同。在配置文件中, 每一行包含一个服务提供者的完全限定名
- 使用 Google Autoservice 注解: 为了简化服务提供者的注册过程, 您可以使用 Google Autoservice 库中提供的
@AutoService注解。将此注解应用于服务提供者的实现类上, 它将自动生成 SPI 配置文件, 并将服务提供者自动注册到应用程序中 - 加载和使用服务提供者: 在应用程序中, 您可以使用 Java 的 ServiceLoader 类来加载和使用服务提供者。ServiceLoader 类允许您动态加载服务提供者的实现, 并在运行时获取它们的实例
通过使用 Google Autoservice 库, 您可以简化 SPI 的使用过程, 自动注册和发现服务提供者, 从而实现更灵活和可扩展的应用程序架构
# AOP
提示
AOP 它提倡的是针对同一类问题的统一处理. 那么 AOP 这种编程思想有什么用呢, 一般来说, 主要用于不想侵入原有代码的场景中, 例如 SDK 需要无侵入的在宿主中插入一些代码, 做日志埋点、性能监控、动态权限控制、甚至是代码调试等等
Spring DI(依赖注入) IOC(控制反转)
有一些工具和库帮助我们使用 AOP:
- AspectJ: 一个 JavaTM 语言的面向切面编程的无缝扩展(适用 Android)
- Jake 大神的 Hugo,https://github.com/JakeWharton/hugo (opens new window)
- Javassist for Android: 用于字节码操作的知名 java 类库 Javassist 的 Android 平台移植版
- DexMaker: Dalvik 虚拟机上, 在编译期或者运行时生成代码的 Java API
- ASMDEX: 一个类似 ASM 的字节码操作库, 运行在 Android 平台, 操作 Dex 字节码
# 结构型设计模式
# MVC MVP MVVM
提示
MVC(Model-View-Controller), MVP(Model-View-Presenter)和 MVVM(Model-View-ViewModel)是三种常见的架构模式。它们被广泛应用于各种编程语言和框架中, 以帮助开发人员组织和管理代码, 并实现良好的可维护性和可扩展性
他们间的区别可以参考:
# MVC
MVC 是一种经典的设计模式, 用于将应用程序的逻辑、数据和用户界面相互分离。它包括以下三个核心组件:
- 视图(View): It is the UI(User Interface) layer that holds components that are visible on the screen
- 控制器(Controller): This component establishes the relationship between the View and the Model
- 模型(Model): This component stores the application data.
在传统的 MVC 模式中, View 和 Controller 是紧密耦合的, 它们之间存在双向的交互。然而, 在 Android 中, 通常使用了一种变种的 MVC 模式, 称为 Passive View。在 Passive View 中, View 不直接与 Model 进行交互, 而是通过 Controller 来间接操作和更新 Model
一、mvc 的结构

二、MVC 的调用关系
用户的对 View 操作以后, View 捕获到这个操作, 会把处理的权利交移给 Controller(Pass calls);Controller 接着会执行相关的业务逻辑, 这些业务逻辑可能需要对 Model 进行相应的操作;当 Model 变更了以后, 会通过观察者模式(Observer Pattern)通知 View;View 通过观察者模式收到 Model 变更的消息以后, 会向 Model 请求最新的数据, 然后重新更新界面
# 优缺点
一、优点
1、把业务逻辑全部分离到 Controller 中, 模块化程度高
当业务逻辑变更的时候, 不需要变更 View 和 Model, 只需要 Controller 换成另外一个 Controller 就行了(Swappable Controller)
2、观察者模式可以做到多视图同时更新
二、缺点
1、Controller 测试困难
因为视图同步操作是由 View 自己执行, 而 View 只能在有 UI 的环境下运行。在没有 UI 环境下对 Controller 进行单元测试的时候, Controller 业务逻辑的正确性是无法验证的: Controller 更新 Model 的时候, 无法对 View 的更新操作进行断言
2、View 无法组件化
View 是强依赖特定的 Model 的, 如果需要把这个 View 抽出来作为一个另外一个应用程序可复用的组件就困难了。因为不同程序的的 Domain Model 是不一样的
# MVP
MVP 由以下三个组件组成
- Model(模型): 用于存储数据的层。它负责处理领域逻辑(现实世界的业务规则)并与数据库和网络层进行通信
- View(视图): 用户界面(UI)层。它提供数据的可视化, 并跟踪用户的操作, 以便通知 Presenter
- Presenter(展示者): 从模型中获取数据并应用 UI 逻辑以决定要显示的内容。它管理视图的状态, 并根据来自视图的用户输入通知采取行动
和 MVC 模式一样, 用户对 View 的操作都会从 View 交移给 Presenter。Presenter 同样的会执行相应的业务逻辑, 并且对 Model 进行相应的操作;而这时候 Model 也是通过观察者模式把自己变更的消息传递出去, 但是是传给 Presenter 而不是 View。Presenter 获取到 Model 变更的消息以后, 通过 View 提供的接口更新界面. 如下图:

二、特点
- 各部分之间的通信, 都是双向的
- View 与 Model 不发生联系, 都通过 Presenter 传递
- View 非常薄, 不部署任何业务逻辑, 称为"被动视图"(Passive View), 即没有任何主动性, 而 Presenter 非常厚, 所有逻辑都部署在那里
# 优缺点
一、优点
1、便于测试
Presenter 对 View 是通过接口进行, 在对 Presenter 进行不依赖 UI 环境的单元测试的时候。可以通过 Mock 一个 View 对象, 这个对象只需要实现了 View 的接口即可。然后依赖注入到 Presenter 中, 单元测试的时候就可以完整的测试 Presenter 业务逻辑的正确性。这里根据上面的例子给出了 Presenter 的单元测试样例
2、View 可以进行组件化
在 MVP 当中, View 不依赖 Model。这样就可以让 View 从特定的业务场景中脱离出来, 可以说 View 可以做到对业务逻辑完全无知。它只需要提供一系列接口提供给上层操作。这样就可以做高度可复用的 View 组件
二、缺点
1、Presenter 中除了业务逻辑以外, 还有大量的 View->Model, Model->View 的手动同步逻辑, 造成 Presenter 比较笨重, 维护起来会比较困难
# MVVM
TODO
# 桥接模式
桥接模式(Bridge Pattern): 将抽象部分与它的实现部分分离, 使它们都可以独立地变化。它是一种对象结构型模式, 又称为柄体(Handle and Body)模式或接口(Interface)模式 任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。
桥接模式包含如下角色:
- Abstraction: 抽象类
- RefinedAbstraction: 扩充抽象类
- Implementor: 实现类接口
- ConcreteImplementor: 具体实现类

# 适配器模式
适配器模式包含如下角色:
- Target: 目标抽象类
- Adapter: 适配器类
- Adaptee: 适配者类
- Client: 客户类
对象适配器 
类适配器 
# 装饰模式
装饰模式(Decorator Pattern) : 动态地给一个对象增加一些额外的职责(Responsibility), 就增加对象功能来说, 装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper), 与适配器模式的别名相同, 但它们适用于不同的场合。
装饰模式包含如下角色:
- Component: 抽象构件
- ConcreteComponent: 具体构件
- Decorator: 抽象装饰类
- ConcreteDecorator: 具体装饰类

示例参考: 一下鸿洋老师的解决方案了, 大家可以看他的文章: 优雅的为 RecyclerView 添加 HeaderView 和 FooterView (opens new window)
# 代理模式
分类
- 静态代理: 运行之前代理类的 class 编译文件已经存在
- 动态代理: 通过反射动态的生成代理者对象。(在执行阶段才知道代理谁)
模式结构 _代理模式包含如下角色: _
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色

# 静态代理
一个静态代理示例
public class Lawyer implements ILaysuit{
private ILaysuit mLawsuit; //持有一个具体被代理者的引用
public Lawyer(ILaysuit mLawsuit) {
this.mLawsuit = mLawsuit;
}
@Override
public void submit() {
mLawsuit.submit();
}
@Override
public void burden() {
mLawsuit.burden();
}
@Override
public void defend() {
mLawsuit.defend();
}
@Override
public void finish() {
mLawsuit.finish();
}
}
public class Xiaomin implements ILaysuit{}
//使用
public class Client {
public static void main(String[] args) {
ILaysuit mLawyer = new Lawyer(new Xiaomin());
mLawyer.submit();
mLawyer.burden();
mLawyer.defend();
mLawyer.finish();
}
}
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
35
36
37
38
# 动态代理 (opens new window)
- 动态代理是一种较为高级的代理模式, 它的典型应用就是 Spring AOP
- 如果按照这种方法使用代理模式, 那么真实主题角色必须是事先已经存在的, 并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色, 这将导致系统中的类个数急剧增加, 因此需要想办法减少系统中类的个数, 此外, 如何在事先不知道真实主题角色的情况下使用代理主题角色, 这都是动态代理需要解决的问题
- 使用上, 通过 Proxy 的 static 方法 newProxyInstance, 传入 InvocationHandler(包含了真实主题对象), 请看下面的例子
public class Client {
public static void main(String[] args) {
// 构造小明
final ILaysuit xiaomin = new Xiaomin();
// 构造一个动态代理
InvocationHandler proxy = new DynamicProxy(xiaomin);
/**
- 构造代理律师 xiaomin.getClass().getInterfaces() 同 new
- Class[]{ILaysuit.class}
*/
ILaysuit lawyer = (ILaysuit) Proxy.newProxyInstance(xiaomin.getClass().getClassLoader(),xiaomin.getClass().getInterfaces(), proxy);
lawyer.submit("老板拖欠工资");
lawyer.burden("这是合同书和去年的银行流水号");
lawyer.defend("证据确凿, 无可厚非");
lawyer.finish("诉讼成功, 老板结算拖欠工资");
}
}
//
public class DynamicProxy implements InvocationHandler {
private Object obj; //被代理对象的引用
public DynamicProxy(Object obj) {
this.obj = obj;
}
/**
- proxy: 被代理对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/**
- 通过 invoke 方法调用具体的被代理方法, 也就是真实方法
*/
Object result = method.invoke(obj, args);
return result;
}
}
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
35
36
37
38
39
40
41
42
43
# 创建型设计模式
# 单例模式
一、DCL(double check lock)
/**
* 双重检查锁模式: 对线程安全的懒惰模式的改进: 方法上的synchronized在每次调用时都要加锁, 性能太低.
*/
final class DoubleCheckedLockingSingleton {
/** 实例对象, 这里还没有添加volatile关键字 */
private static volatile DoubleCheckedLockingSingleton INSTANCE = null;
/** 禁用构造方法 */
private DoubleCheckedLockingSingleton() { }
/**
* 获取对象: 将方法上的synchronized移至内部
* @return instance 本类的实例
*/
public static DoubleCheckedLockingSingleton getInstance() {
// 先判断实例是否存在
if (INSTANCE == null) {
// 加锁创建实例
synchronized (DoubleCheckedLockingSingleton.class) {
if (INSTANCE == null) {
// 2
// 1.创建内存空间
// 2.在内存空间中初始化对象 Singleton
// 3.将内存地址赋值给 INSTANCE 对象(执行了此步骤, INSTANCE 就不等于 null 了)
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
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
说明:
volatile 作用
- 解决内存可见性问题
- 防止指令重排序
试想一下, 如果不加 volatile, 那么线程 1 在执行到上述代码的第 ② 处时就可能会执行指令重排序, 将原本是 1、2、3 的执行顺序, 重排为 1、3、2。但是特殊情况下, 线程 1 在执行完第 3 步之后, 如果来了线程 2 执行到上述代码的第 ① 处, 判断 instance 对象已经不为 null, 但此时线程 1 还未将对象实例化完, 那么线程 2 将会得到一个被实例化"一半"的对象, 从而导致程序执行出错, 这就是为什么要给私有变量添加 volatile 的原因了
二、内部类
/**
* 静态内部类模式, 也称作Singleton Holder(单持有者)模式: 线程安全, 懒惰模式的一种, 用到时再加载
*/
final class StaticInnerSingleton {
/** 禁用构造方法 */
private StaticInnerSingleton() { }
/**
* 通过静态内部类获取单例对象, 没有加锁, 线程安全, 并发性能高
* @return SingletonHolder.instance 内部类的实例
*/
public static StaticInnerSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
/** 静态内部类创建单例对象 */
private static class SingletonHolder {
private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
比较推荐这种方式, 没有加锁, 线程安全, 用到时再加载, 并发行能高
# 建造者模式
# 工厂方法模式
工厂方法模式包含如下角色:
- Product: 抽象产品
- ConcreteProduct: 具体产品
- Factory: 抽象工厂
- ConcreteFactory: 具体工厂

public class FactoryImpl extends Factory{
@Override
public <T extends Car> T createCar(Class<T> clz) {
Car car = null;
try {
// car = (Car) Class.forName(clz.getName()).newInstance();
car = (Car) clz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) car;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 行为设计模式
# 观察者模式
观察者模式包含如下角色:
- Subject: 目标
- ConcreteSubject: 具体目标
- Observer: 观察者
- ConcreteObserver: 具体观察者

# 迭代器模式 (opens new window)
# 备忘录模式
备忘录模式又叫做快照模式(Snapshot Pattern)或 Token 模式, 是对象的行为模式
备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下, 将一个对象的状态捕捉(Capture)住, 并外部化, 存储起来, 从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用
# 模式对比
# 代理模式和装饰模式区别
- 从语意上讲, 代理模式是为控制对被代理对象的访问, 而装饰模式是为了增加被装饰对象的功能
- 代理类所能代理的类完全由代理类确定, 装饰类装饰的对象需要根据实际使用时客户端的组合来确定
- 被代理对象由代理对象创建, 客户端甚至不需要知道被代理类的存在;被装饰对象由客户端创建并传给装饰对象
- 装饰模式, 是面向对象编程领域中, 一种动态地往一个类中添加新的行为的设计模式。就功能而言, **装饰模式相比生成子类更为灵活, 这样可以给某个对象而不是整个类添加一些功能