模式是在某种情境下,针对某问题的某种解决方案
设计模式的精髓在于封装变化,我们总是花更多的时间在软件维护和更新上面,所以应该致力于提高软件的可维护性和可扩展性
- 创建型:单例、原型、建造者、工厂方法、抽象工厂
- 结构型:装饰者、代理、适配器、组合、外观、享元、桥接
- 行为型:策略、状态、迭代器、观察者、责任链、模版方法、命令、备忘录、解释器、中介者、访问者
1、单例模式
- WindowManagerGlobal:内部维护着mViews、mRoots、mParams列表
2、原型模式
- java.lang.Object.clone()
当创建给定类的实例很昂贵或很复杂时,就可以使用原型模式,原型模式允许你通过复制现有实例来创建新的实例(在Java中,通常意味着使用clone或者反序列化)
3、建造者模式
Kotlin具名参数、Setter方法都无法解决参数依赖 & 参数校验的能力,而且如果对象是不可变的,那还无法调用Setter方法
AlertDialog.Builer builder = new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
.setTitle("title")
.setMessage("message")
.setPositiveButton("Button1",
new DialogInterface.OnclickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("click");
}
})
.create()
.show();
4、工厂方法模式
// List和Set继承自Collection接口,Collection接口继承于Iterable接口,
// 所以List和Set接口也需要继承并实现Iterable中的iterator()方法
// 实现类ArrayList和HashSet中的iterator方法就给我们具体构造并返回了一个迭代器对象
public Iterator<E> iterator() {
return new Itr();
}
工厂模式可以有效避免初始化造成的耦合问题
5、抽象工厂模式
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
上面代码中,Context使用了抽象工厂模式
抽象工厂模式通过组合的方式提供抽象接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的产品是什么。这样一来,客户就从具体的产品中被解耦
6、装饰者模式
Java IO
ContextWrapper
开放-封闭原则:观察者模式、装饰者模式
采用装饰者模式在实例化组件时,不只需要实例化组件,还要把组件包装进装饰者中,天晓得有多少个,这时我们可以考虑 工厂模式 和 生成器模式
7、代理模式
- Binder:Interface、Proxy、Stub
远程代理、虚拟代理、动态代理 代理模式为另一个对象提供替身或占位符以控制对这个对象的访问
8、适配器模式
// 创建字节流
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
// 创建字符流适配器,将字节流转换为字符流
InputStreamReader isr = new InputStreamReader(fis);
OutputStreamWriter osw = new OutputStreamWriter(fos);
比较典型的有ListView和RecyclerView。为什么ListView需要使用适配器呢?
ListView用于显示列表数据,但列表数据形式多种多样,为了处理和显示不同的数据,我们需要对应的适配器作为桥梁
这样ListView就可以只关心它的每个ItemView,而不用关心这个ItemView具体显示的是什么
ListView和数据源之间没有任何关系,这时候需要通过适配器提供getView方法给ListView使用,
每次ListView只需提供位置信息给getView函数,然后getView函数根据位置信息向数据源获取对应的数据,根据数据返回不同的View
适配器将一个类的接口转换成客户期望的另一个接口。适配器可以让原本接口不兼容的类可以合作无间
9、组合模式
组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理单个对象及对象组合
Android中View的结构是树形结构,每个ViewGroup包含一系列的View,而ViewGroup本身又是View。
这是Android中非常典型的组合模式
10、外观模式
- Context(四大组件启动)
外观模式提供了统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用(最少知识原则)
11、享元(蝇量)模式
如果让某个类的一个实例能用来提供许多“虚拟实例”,就使用蝇量模式
Java中的常量池,线程池等。主要是为了重用对象。
Android线程通信中每次我们调用Message.obtain()从消息池中取出可重复使用的消息,避免产生大量的Message对象
12、桥接模式
拆分或重组一个具有多重功能的庞杂类:(形状、颜色)
ActivityManagerService -> (ActivityStack -> ActivityRecord)
13、策略模式
// JDK使用场景
public interface Comparator<T> {
int compare(T o1, T o2);
}
// Android在属性动画中使用时间插值器的时候就用到了策略模式
// 在使用动画时,你可以选择线性插值器LinearInterpolator、加速减速插值器以及自定义的插值器
接口提供能力,如果能力有多种实现方式,应该考虑使用策略模式来抽离变化(多用组合,少用继承)
interface FlyBehaviour {
void fly();
}
interface Flyable {
void setFlyBehaviour(FlyBehaviour flyBehaviour);
FlyBehaviour getFlyBehaviour();
default void fly() {
getFlyBehaviour().fly();
}
}
class Duck { }
class WildDuck extends Duck implements Flyable {
/* 可以使用建造者模式来保证FlyBehaviour一定会被设置 */
FlyBehaviour mFlyBehaviour;
@Override
public void setFlyBehaviour(FlyBehaviour flyBehaviour) {
mFlyBehaviour = flyBehaviour;
}
@Override
public FlyBehaviour getFlyBehaviour() {
return mFlyBehaviour;
}
}
可以通过 建造者模式 来保证FlyBehaviour一定会被设置
14、状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
Android源码中很多地方都有用到状态模式,举一个例子,就是Android的WIFI管理模块
当WIFI开启时,自动扫描周围的接入点,然后以列表的形式展示;当wifi关闭时则清空
这里wifi管理模块就是根据不同的状态执行不同的行为。
15、迭代器模式
- 集合类
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
16、观察者模式
- LifecycleObserver
观察者模式是JDK使用最多的设计模式,它实现了一对多以及松耦合关系,符合好莱坞原则、符合开闭原则
17、责任链模式
-
事件分发:在 Android Framework 中的 View 和 ViewGroup 中,事件分发机制就是一个典型的责任链模式的应用。当一个触摸事件发生时,它首先被传递给顶层的 View(通常是 Activity),然后依次传递给 View 层级中的每一个 View,直到找到合适的 View 来处理这个事件。如果某个 View 没有处理这个事件,它会将事件传递给它的父容器,直到事件被处理或者传递到了根布局
-
异常处理:在 JDK 中,异常处理机制也是一个常见的责任链模式的应用。当一个方法抛出异常时,异常会被传递给调用栈中的上一级方法,如果上一级方法能够处理这个异常,它就会被捕获并处理掉,否则异常会继续向上级方法传递,直到被捕获或者到达顶层方法
-
拦截器:在 Android Framework 中的 Interceptor 或者 OkHttp 中的 Interceptor 也是责任链模式的应用。拦截器链中的每个拦截器都有机会处理请求或者响应,如果某个拦截器处理了请求或者响应,它就可以决定是否继续传递给下一个拦截器
18、模版方法模式
- View.onMeasure、onLayout、onDraw
- Activity.onSaveInstanceState(Bundle)、onRestoreInstanceState(Bundle)、onCreate
直到目前,我们的议题都在围绕封装转:我们已经封装了对象创建、方法调用、复杂接口。接下来我们要深入封装算法块,好让子类在任何时候都可以将自己挂接进运算里 模版方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版算法使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤
19、命令模式
- java.lang.Runnable
通过封装方法调用,我们可以把运算块包装成形。所以调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形的方法来完成它就可以。通过封装方法调用,也可以做一些聪明的事情,例如日志请求(数据库事务),或者重复使用这些封装来实现撤销
20、备忘录模式
- Activity-onSavedInstanceState
- Activity-onRetainNonConfigurationInstance
当你需要让对象返回之前的状态时,就使用备忘录模式
21、解释器模式
使用解释器模式为语言创建解释器
这个用到的地方也不少,其一就是Android的四大组件需要在AndroidManifest.xml中定义,
其实AndroidManifest.xml就定义了,等标签(语句)的属性以及其子标签,规定了具体的使用(语法),
通过PackageManagerService(解释器)进行解析
22、中介者模式
java.util.concurrent.Executor(将任务和执行节藕)
Binder机制也用到了中介者模式。我们知道系统启动时,各种系统服务会向ServiceManager提交注册,
当我们需要获取系统的Service时,比如ActivityManager、WindowManager等,
首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。
并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManager和Binder驱动就是中介者
使用中介者来集中相关对象之间复杂的沟通和控制方式
23、访问者模式
Android中运用访问者模式,主要是在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),
著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT
当你想要为一个对象的组合增加新的能力,且封装并不重要时,考虑使用访问者模式
MVC
杂记
代理模式是一种结构型设计模式,它允许在不改变原始对象的基础上为其添加新功能。代理通常与被代理对象实现相同的接口,并在需要的时候将方法调用传递给被代理对象。代理的主要优点是可以在不改变原始对象的情况下添加新功能,以及实现懒加载、安全控制等特性
委托模式是一种结构型设计模式,它通过将对象的部分行为或属性委托给另一个对象来实现。与代理模式不同,委托模式关注的是对象的行为委托,而不是代理实现新功能。在Kotlin中,可以使用by关键字轻松实现委托模式
策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装到具有公共接口的独立类中,使得它们可以相互替换。策略模式可以在运行时根据需要选择不同的算法,而不需要修改原始代码
UML类图
- 车的类图结构为<>,表示车是一个抽象类
- 小汽车和自行车 与 车是继承关系;更具体为实现关系,使用带空心箭头的虚线表示
- 小汽车与 SUV 之间也是继承关系,更具体为泛化关系,使用带空心箭头的实线表示
- 小汽车与发动机之间是组合关系,使用带实心箭头的实线表示
- 学生与班级之间是聚合关系,使用带空心箭头的实线表示
- 学生与身份证之间为双向关联关系,使用一根实线表示
- 学生上学需要用到自行车,与自行车是一种依赖关系,使用带箭头的虚线表示
UML 类表示法
具体类在类图中用矩形框表示,矩形框分为三层:第一层是类名字;第二层是类的成员变量;第三层是类的方法
成员变量以及方法前的访问修饰符用符号来表示:
- “+”表示 public
- “-”表示 private
- “#”表示 protected
- 不带符号表示 default
其中,属性和方法返回值的数据类型非必需,静态方法在方法名前加下划线用来区和实例方法的不同
抽象类在UML类图中同样用矩形框表示,但是抽象类的类名以及抽象方法的名字都用 斜体字 表示
接口在类图中也是用矩形框表示,但与类的不同的是,接口在类图中的第一层用 <<interface>> 表示
设计原则
我们在设计类的时候,首先要考虑的是单一职责,对于一个类应该只有一个引起变化的原因。比如我们设计一个汽车,设计一个类不能既管理引擎又管理刹车
扩展功能时我们要考虑的是开闭原则,对扩展开发,对修改关闭
在设计接口时,我们要考虑的是接口隔离和里氏替换
在实现具体功能时,我们要考虑的是依赖反转,抽象不能依赖具体,具体应该依赖抽象
依赖倒置原则 - 实践
- 工厂方法
- 控制反转:DI(依赖注入)、DL(依赖查找)
思考题
如何在System.out.println调用时自动添加附加信息?
public class TimestampedPrintStream extends PrintStream {
private PrintStream original;
public TimestampedPrintStream(PrintStream original) {
super(original);
this.original = original;
}
@Override
public void println(String x) {
String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
original.println(timestamp + " " + x);
}
public static void main(String[] args) {
// 创建一个代理对象来替换 System.out
System.setOut(new TimestampedPrintStream(System.out));
// 现在你可以直接调用 System.out.println,而时间戳信息会被自动添加
System.out.println("This is a message with timestamp");
}
}