1. 面向对象设计的 6 大原则
单一职责原则
一个类中应该都是相关性很高的函数和数据
开闭原则
oop的体现,对象应该是对拓展开放,对修改封闭(尽量少的修改已经写好的代码)
里氏替换原则
oop的体现,所有引用基类的地方必须能透明的使用其子类的对象(子类可以扩展父类的功能,但不能改变父类原有的功能)
依赖倒置原则
即面向接口编程:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系(其依赖关系是通过接口或抽象类产生的)
接口隔离原则
类间的依赖关系应该建立在最小的接口上,目的是方便系统解耦,从而容易重构和更新
迪米特法则
一个类应该对自己需要耦合或调用的类知道得最少,调用者或者依赖者只需要知道它需要的方法即可
2. 创建型模式
Factory Method(工厂方法模式)多态性的体现
定义一个创建对象的接口,让子类决定实例化哪个类
实例化一些参数较多或者构造过程复杂的实例时,如果每一个用到的地方都写一次会很麻烦,修改起来也费劲,如果把每个对象的构造过程单独抽象出来,用一个Creater类来构造一个Product就会省去很多麻烦的 或者目前还不明确将来需要实例化哪些具体类时,可以使用工厂方法模式
简单工厂:去掉IFactory接口,直接使用一个ProductFactory类来创建所有的对象,常用反射来实现
public class ToyCreator{
private static final String TAG = "ToyCreator";
public static <T extends IToy> IToy createToy(Class<T> clazz) {
if (clazz == null){
throw new IllegalArgumentException("argument must not be null");
}
try {
IToy toy = clazz.newInstance();
Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ");
toy.play();
return toy;
} catch (Exception e) {
throw new UnknownError(e.getMessage());
}
}
}
缺点:类的数量已经大大增加
Abstract Factory(抽象工厂模式)多态性的体现
为创建一组相关或者是相互依赖的对象提供一个接口一类有关的(例如组合起来形成一个汽车,其中的发动机、轮子等都是相关的对象) ,而不需要制定它们的具体类(每一个生产汽车的工厂都是一个独立的工厂,例如奔驰汽车厂和宝马汽车厂)
工厂方法模式中工厂类生产出来的产品都是具体的同类型产品,而抽象工厂是产生一类相关的产品的工厂
仔细研究发现,其实工厂方法模式是包含在抽象工厂模式中的,抽象工厂的任务是定义一个负责创建**一组产品**的接口,这个接口内的每个方法都负责创建一个具体产品
当需要创建产品家族和想让制造的相关产品集合起来时,使用抽象工厂模式;当仅仅想把客户代码从需要实例化的具体类中解耦,或者如果目前还不明确将来需要实例化哪些具体类时,可以使用工厂方法模式
Builder(建造者模式)
将复杂对象的构建与展示分离,使得同样的构建过程可以创建不同的表示(在不知道具体细节的情况下,精细地控制对象的构造流程)
Singleton(单例模式)
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
Prototype(原型模式)
有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,可以节省对象构造的时间(复制比构造性能更好)
public class ConcretePrototype implements Cloneable{
private String string;
private ArrayList<String> stringList;
//略...
public ConcretePrototype clone() {
try {
ConcretePrototype copy = (ConcretePrototype) super.clone();
copy.setString(this.getString());
copy.setStringList((ArrayList<String>) getStringList().clone());
return copy;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
Object 类的 clone 方法只会拷贝对象中的 8 种基本数据类型 ,byte 、char、short、int、long、float、double、boolean,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝,如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝(String类型由于其在字符串常量池中,拷不拷贝都一样)
使用原型模式复制对象不会调用类的构造方法。它直接在内存中复制数据,不会调用到类的构造方法。甚至连访问权限都对原型模式无效。
3. 结构型模式
Adapter(适配器模式)
将原来不兼容的两个类融合在一起
使用过程中有"两种"方式:类适配器和对象适配器
类适配器
实现 ITarget 接口以及继承 Adaptee 类来实现接口转换,通过 Adapter 实现一个 operation1() 函数将 Adaptee 的 operation2() 转换为 ITarget 需要的操作
对象适配器
Adapter依赖Adaptee的对象,比类适配器更加的灵活,如果需要适配的方法不多,用对象适配器更合适,如果大部分方法都要适配,还是类适配器合适
例子:ListView的BaseAdapter就是典型的"需要统一的输出接口,而输入端的类型不可预知"
Bridge(桥接模式)
为被分离了的抽象部分和实现部分搭桥,将抽象部分与实现部分分离,使他们都可以独立地进行变化,他们两个部分是独立的,没有实现自同一个接口
是一个单方向的关系,只能够抽象部分去使用实现部分的对象,而不能反过来
桥接模式的目的是为了将抽象部分与实现部分解耦,可以将一个 N * N 的系统进行拆分,减少类的数量。
其实在使用带抽象的继承关系中不知不觉我们就会使用到桥接模式
Android中的例子,框中四个类使用的是典型的桥接模式
Composite(组合模式)
它将一组相似的对象看作一个对象处理,并根据一个**树状结构**来组合对象,然后提供一个统一的方法去访问相应的对象
能把相同的操作应用在组合和个别对象上,即在大多数情况下,我们可以忽略对象组合和个别对象之间的差别,例如文件夹这种可以层叠的结构
透明的组合模式
安全的组合模式
组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让高层模块忽略了层次的差异,方便对整个层次结构进行控制
Decorator(装饰者模式)/Wrapper(包装模式)
使用一种对客户端透明的方式来动态的扩展对象的功能,同时也是继承关系的一种替代方案之一,但比继承更加灵活
Context 就是典型的装饰者模式
装饰者模式的目的是透明地为客户端对象扩展功能,是继承关系的一种替代方案,而代理模式则是给一个对象提供一个代理对象,并由代理对象来控制对原有对象的引用。装饰者模式应该为所装饰的对象增强功能;代理模式对代理的对象施加控制,但不对对象本身的功能进行增强。
Proxy(代理模式)
为另一个对象提供一个代理以控制对这个对象的访问
当无法或者不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端的透明性,委托对象与代理对象需要实现同样的接口
静态代理模式的代码由程序员自己或通过一些自动化工具生成固定的代码再对其进行编译,也就是说我们的代码在运行前代理类的 class 编译文件就已经存在; 动态代理则与静态代理相反,在 Java 或者 Android 中通过反射机制动态地生成代理者的对象,代理谁我们将会在执行阶段决定,在 Java 中,也提供了相关的动态代理接口 InvocationHandler 类
静态代理
动态代理
使用 InvocationHandler 实现动态代理,并且可以根据 method 的名字实现了保护代理(https://blog.csdn.net/self_study/article/details/55050627)
Android中AMS的例子
代理模式是为了管理和控制对象,比如远程代理和虚拟代理等,而装饰者模式是为了扩展对象
Facade(外观模式) 精髓就是封装
使得整个子系统只有一个统一的高层的接口,降低用户的使用成本,也对用户屏蔽了实现细节,是我们封装 API 的常用手段
没有统一的UML图,就是一个门面类,控制整个子系统
Flyweight(享元模式)
使用共享对象可有效地支持大量细粒度的对象
尽可能减少内存使用量,适合用于可能存在大量重复对象的场景,缓存可共享的对象,来达到对象共享和避免创建过多对象的效果
例如文本系统中的常用图形,java的字符串常量池,都是这个思想
享元模式通常情况下获取的是不可变的实例,而从对象池模式中获取的对象通常情况下是可变的,需要去控制对象的状态和行为
Object Pool(对象池模式)
java中为了减小系统 GC 的压力,优化性能
需要使用到大量的同一类对象,这些对象的初始化会消耗大量的系统资源,而且它们只需要使用很短的时间,这种操作会对系统的性能有一定影响
例如java中的比如 ThreadPool 和 Android 中的 MessagePool 等
缺陷:使用对象池模式时,每次进行回收操作前都需要将该对象的相关成员变量重置,如果不重置将会导致下一次重复使用该对象时候出现预料不到的错误,同时,如果回收对象的成员变量很大,不重置还可能会出现内存 OOM 和信息泄露等问题。另外,对象池模式绝大多数情况下都是在多线程中访问的,做好同步工作极其重要。
4. 行为型模式
Chain of responsibility(责任链模式)
存在一个链式结构,多个节点首尾相连,每个节点都可以被拆开再连接
使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止
例如ViewGroup 和 View 中 touch 事件的分发,子 View 的 onTouchEvent 返回 true 代码消费该事件并不再传递,false 代表不消费并且传递到父 ViewGroup 去处理,这些树形结构的子 View 就是责任链上一个个处理对象
Command(命令模式)
将一个请求封装成一个命令对象(如开灯、关机等命令),从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作
日志、回滚撤销、批量执行功能等都是其应用场景:每次执行命令的时候都打印出相应的关键日志,或者每次执行后都将这个命令保存进列表中并且每个命令实现一个 undo 方法,以便可以进行回滚,也可以构造一个 MacroCommand 宏命令类用来按次序先后执行几条相关联命令
Receiver:接收者角色该类负责具体实施或执行一个请求,说的通俗一点就是,执行具体逻辑的角色
Invoker :请求者角色该类的职责就是调用命令对象执行具体的请求,相关的方法我们称为行动方法
public class Client {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(command);
invoker.action();
}
}
实际应用:如向导页,在最终一个页面,执行所有命令,例如数据库事务等
Interpreter(解释器模式)
比较少使用,它要求给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子,如sql语句的解析
Iterator(迭代器模式/游标模式)
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示
Mediator(中介者模式)
包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用,从而使耦合松散,而且可以独立地改变它们之间的交互
将这些网状结构的类变成星型依赖,所以类都只依赖于中介者,不直接依赖于其他类
转换为下图
Mediator:抽象中介者角色,包含了所有的同事对象,定义了同事对象到中介者对象的接口,可以通过抽象类或者接口的方式实现; Colleague:抽象同事类角色,定义了同事对象的接口,它**只知道中介者**而不知道其他的同事对象; ConcreteMediator:实现了父类定义的方法,它从具体的同事对象接受消息,向具体同事对象发出命令; ConcreteColleague:每个具体同事类都知道本身在小范围内的行为,而不知道它在大范围内的目的。
Memento(备忘录模式)
保存对象当前状态,并且在之后可以再次恢复到此状态
在不破坏封装的前提下,捕捉一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态
可以将 Memento 类设置成为 Originator 的静态内部类,Memento 的内部实现细节就可以只对 Originator 暴露,实现了"黑箱"的封装性
Observer(观察者模式) 解耦
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新
关联行为是可拆分的,而不是"组合"关系
特别注意:如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃
事件监听机制可以理解为观察者模式的实际应用
Strategy(策略模式)
同一个操作,有不同的方案可以采用,要根据不同的情况进行操作,可以替代if...else...
定义了一系列的方案(算法),并将每一个算法封装起来,使他们可以相互替换,让算法独立于使用它的客户而独立变化
Context context = new Context();
context.setStragety(new ConcreteStragetyA());
context.algorithm();
context.setStragety(new ConcreteStragetyB());
context.algorithm();
示例:Animator类中的setInterpolator() 就是典型的策略模式,不同的Interpolator类有不同的效果
State(状态模式)
状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的
应用场景:代码中包含大量与状态有关的条件语句,可以替代if...else;一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为
Context context = new Context();
context.setState(new ConcreteStateA());
context.doSomething();
context.setState(new ConcreteStateB());
context.doSomething();
有限状态自动机 (FSM:Finite State Machine),是表示有限多个状态以及在这些状态之间转移和动作的数学模型。状态存储关于过去的信息,它反映从系统开始到现在时刻输入的变化;转移指示状态变更,用必须满足来确保转移发生的条件来描述它;动作是在给定时刻要进行的活动描述(https://blog.csdn.net/yangwen123/article/details/10591451)
Template method(模板方法模式)流程封装
我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序,但是,某些步骤的具体实现是未知的(某些步骤的实现是会随着环境的变化而改变的),这时候我们就可以创建一个算法的模板,将算法定义成**一组**步骤,其中的任何步骤都可以是抽象的,由子类负责实现,这可以确保算法的**结构保持不变**,同时由子类提供部分实现
templateMethod() 模版方法,定义算法的步骤 abstractMethod() 基本方法的声明(由子类实现) hookMethod() 其中辅助函数
模板方法模式用四个字概括就是:流程封装。把某个固定的流程封装到一个 final 函数中,并且让子类能够定制这个流程中的某些或者所有步骤,这就要求父类提取公用的代码,提升代码的复用率,同时也带来了更好的可封装性。
Visitor(访问者模式)
将数据操作与数据结构分离
表示一个作用于对象内部结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作
使用频率并不是很高,通常你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是真的需要使用它
通过定义一个外在的Visitor来访问ObjectStructor对象结构中的Element,这样可以变化访问者来访问对象结构中的元素,但又不会影响元素内部结构
示例