面向对象设计的基本原则:
(1)OCP(开闭原则Open-Closed Principle)一个软件的实体应当对扩展开放,对修改关闭。
(2)DIP(依赖倒转原则Dependence Inversion Principle)要针对接口编程,不要针对实现过程。
(3)LoD(迪米特法则Law of Demeter)只与你直接的朋友通信,而避免和陌生人通信。
最少知识原则:一个软件实体应当尽可能少的与其他实体发生相互作用。
一、创建型模式(帮助创建对象,关注对象的创建过程)
1、单例模式
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:
①由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式解决。
②单例模式可以在系统设置全局的访问点,优化环境共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
(1)饿汉式(线程安全,调用效率高,不能延时加载)
①实现:
public class SingletonDemo1 {
//类初始化时立即加载这个对象,没有延时加载优势,加载类时线程安全。
private static SingletonDemo1 instance = new SingletonDemo1();
//私有构造函数
private SingletonDemo1(){}
//方法没有同步,调用效率高
public static SingletonDemo1 getInstance() {
return instance;
}
}(2)懒汉式(线程安全,调用效率不高,可以延时加载)
①实现:
public class SingletonDemo2 {
//类初始化时不初始化这个对象(延时加载,真正用的时候再创建)
private static SingletonDemo2 instance;
//私有化构造器
private SingletonDemo2(){}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance() {
if(instance== null) {
instance= new SingletonDemo2();
}
return instance;
}
}
(3)双重检测锁(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)
(4)静态内部类式(线程安全,调用效率高,可以延时加载)
①实现:
public class SingletonDemo4 {
private static class SingletonClassInstance {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
public static SingletonDemo4 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo4(){}
}②要点:
a.外部类没有static属性,则不会像饿汉式那样立即加载对象。
b.只有真正调用getInstance(),才会加载静态内部类。加载类时线程是安全的。instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
c.兼备了并发高效调用和延迟加载的优势。
(5)枚举式(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化漏洞)
①实现:
public enum SingletonDemo5 {
//这个枚举元素本身就是单例对象
INSTANCE;
// 单例可以有自己的操作
public void singletonOperation() {
// 功能处理
}
}
②优点:实现简单,枚举本身就是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化的漏洞。
③缺点:无法延迟加载。
如何选用?
①单例对象 占用资源少 不需要延时加载(枚举式好于饿汉式)
②单例对象 占用资源大 需要延时加载(静态内部类式好于懒汉式)2、工厂模式(实现了创建者和调用者的分离)
核心本质:实例化对象,用工厂方法代替new操作。将选择实现类、创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
(1)简单工厂模式(静态工厂模式):用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改已有代码)
①工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例。
②对于增加新产品无能为力,不修改代码的话,无法扩展。
(2)工厂方法模式:用来生产同一等级结构中的固定产品(支持增加任意产品)
①为了避免简单工厂模式的缺点,不完全满足OCP。
②工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个(对于一个项目或者一个独立模块而言)工厂类,而工厂方法模式有一组实现了相同接口的工厂类。
(3)抽象工厂模式:用来生产不同产品族的全部产品(对于增加新的产品,无能为力,支持增加产品族)
①抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式生产需要的对象时一种非常好的解决方式。
3、建造者模式
(1)场景
①要建造一个复杂的产品。比如神舟飞船、iPhone。这个复杂的产品的创建。有这样一个问题需要解决,装配这些子组件是不是有个步骤问题?
②实际开发中,所需要的对象构建时,也非常复杂,有很多步骤需要处理时。
(2)本质
①分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)从而可以构造出复杂的对象。这个模式适用于某个对象的构建过程复杂的情况下使用。
②由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
4、原型模式
(1)通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
(2)就是Java中的克隆技术,以某个对象为原型,复制出新的对象。显然,新的对象具备原型对象的特点。
(3)效率高,直接克隆,避免了重新执行构造过程步骤。
(4)克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出的对象的属性值完全和原型对象相同。并且克隆出的新对象的改变不会影响原型对象。然后,再修改克隆对象的值。
实现:Cloneable接口和clone方法。Prototype模式中实现起来最困难的地方就是内存复制操作,所幸在Java中提供了clone方法替我们做了绝大部分事情。
二、结构型模式
核心作用:是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。关注对象和类的组织。
1、适配器模式:将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
(1)模式中的角色
①目标接口(Target)客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
②需要适配的类(Adaptee)需要适配的类或适配者类。
③适配器(Adapter)通过包装一个需要适配的对象,把原接口转换成目标接口。
(2)工作中的场景
①经常用来做旧系统改造和升级。
②如果我们的系统开发之后再也不需要维护,那么很多模式都是没必要的,但是不幸的是,事实却是维护一个系统的代价往往是开发一个系统的数倍。
③学习中见过的场景:
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)
2、代理模式
(1)核心作用
①通过代理,控制对对象的访问。可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理(即AOP的微观实现)。
②AOP(Aspect Oriented Programming面向切面编程)的核心实现机制
(2)核心角色
①抽象角色:定义代理角色和真实角色的公共对外方法。
②真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。(关注真正的业务逻辑)。
③代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。(将统一的流程控制放到代理角色中处理)
(3)应用场景
①安全代理:屏蔽对真实角色的直接访问。
②远程代理:通过代理类处理远程方法调用(RMI)。
③延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
3、桥接模式
(1)装饰模式和桥接模式的区别:两个模式都是为了解决过多子类对象问题。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是为了增加新的功能。
4、装饰模式(包装器模式)
(1)装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化,以便增加新的具体构建类和具体装饰类。
(2)优点
①扩展对象功能,比继承灵活,不会导致类个数急剧增加。
②可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象。
③具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类。
(3)缺点
①产生很多小对象。大量小对象占据内存,一定程度上影响性能。
②装饰模式易于出错,调试排查比较麻烦。
(4)职责
①动态的为一个对象增加新的功能。
②装饰模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。
(5)实现
①Component抽象构件角色:真实对象和装饰对象有相同的接口。这样,客户端对象就能够以与真实对象相同的方式同装饰对象交互。io流中的InputStream、OutputStream、Reader、Writer。
②ConcreteComponent具体构件角色(真实对象):io流中的FileInputStream、FileOutputStream。
③Decorator装饰角色:持有一个抽象构件的引用。装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能。io流中的FilterInputStream、FilterOutputStream。
④ConcreteDecorator具体装饰角色:负责给构件对象增加新的责任。io流中的BufferedOutputStream、BufferedInputStream等。
5、组合模式
(1)使用场景:把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象。
(2)核心
①抽象构件Component角色:定义了叶子和容器构件的共同点。
②叶子构件Leaf角色:无子节点。
③容器构件Composite角色:有容器特征,可以包含子节点。
6、外观模式
(1)核心:为子系统提供统一的入口。封装子系统的复杂性,便于客户端调用。
7、享元模式
(1)场景
①内存属于稀缺资源,不要随便浪费,如果有很多个完全相同或相似的对象,我们可以通过享元模式,节省内存。
②享元模式由于其共享的特性,可以在任何“池”中操作,比如:线程池、数据库连接池。
③String类的设计也是享元模式。
(2)核心
①享元模式以共享的方式高效地支持大量细粒度对象的重用。
②享元对象能做到共享的关键是区分了内部状态和外部状态。(内部状态可以共享,不会随环境变化而改变;外部状态不可以共享,会随环境变化而改变)
(3)优点
①极大减少内存中对象的数量。
②相同或相似对象内存中只存一份,极大的节约资源,提高系统性能。
③外部状态相对独立,不影响内部状态。
(4)缺点
①模式较复杂,使程序逻辑复杂化。
②为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态,使运行时间变长。用时间换取了空间。
三、行为型模式(关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共有十一种模式)
1、责任链模式:将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。
(1)场景
①打牌时轮流出牌
②接力赛跑
③大学中奖学金审批
④公司中公文审批
2、命令模式:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作,也称为动作Action模式,事务Transaction模式。
(1)结构
①Command抽象命令类
②ConcreteCommand具体命令类
③Invoker调用者/请求者:请求的发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联。在程序运行时,将调用命令对象的execute(),间接调用接收者的相关操作。
④Receiver接收者:接收者执行与请求相关的操作,具体实现对请求的业务处理。未抽象前,实际执行操作内容的对象。
⑤Client客户类:在客户类中需要创建调用者对象,具体命令类对象,在创建具体命令对象时指定对应的接收者。发送者和接收者之间没有直接关系,都通过命令对象间接调用。
3、解释器模式
(1)是一种不常用的设计模式。
(2)用于描述如何构成一个简单的语言解释器,主要用于使用面向对象语言开发的编译器和解释器设计。
(3)当我们需要开发一种新的语言时,可以考虑使用解释器模式。
(4)尽量不要使用解释器模式,后期维护会有很大麻烦。在项目中,可以使用Jruby,Groovy,Java的js引擎来替代解释器的作用,弥补Java语言的不足。
4、迭代器模式
(1)场景
①提供一种可以遍历聚合对象的方式。又称为游标cursor模式。
②聚合对象:存储数据
③迭代器:遍历对象
5、中介者模式
(1)核心
①如果一个系统中对象之间的联系呈现为网状结构,对象之间存在大量多对多关系,将导致关系及其复杂,这些对象称为同事对象。
②可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将复杂的网络结构化解为星型结构。
(2)本质:解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。我们通过中介者对象统一管理这些交互关系。
6、备忘录模式
(1)核心:就是保存某个对象内部状d态的拷贝,这样以后就可以将该对象恢复到原先的状态。
(2)结构
①源发器类Originator
②备忘录类Memento
③负责人类CareTaker(负责保存好的备忘录对象。可以通过增加容器,设置多个备忘点。)
7、观察者模式
(1)核心
①观察者模式主要用于1:N的通知。当一个对象(目标对象Subject或Observable)的状态变化时,他需要及时告知一系列对象(观察者对象,Observer),令他们做出响应。
②通知观察者的方式
a.推:每次都会把通知以广播方式发送给所有观察者,所有观察者只能被动接收。
b.拉:观察者只要知道有情况即可。至于什么时候获取内容,获取什么内容,都可以自主决定。
8、状态模式
(1)核心:用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。
(2)结构
①Context环境类:环境类中维护一个State对象,它是定义了当前的状态。
②State抽象类
③ConcreteState具体状态类:每一个类封装了一个状态对应的行为。
9、策略模式:策略模式对应于解决某一个问题的一个算法族,允许用户从该算法族中任选一个算法解决某一个问题,同时可以方便的更换算法或者增加新的算法,并且由客户端决定调用哪个算法。
(1)本质:分离算法,选择实现。
10、模板方法模式:模板方法模式是编程中经常用得到的模式。它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。
(1)核心:处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用模板方法模式,将这个节点的代码实现转移给子类完成。即处理步骤父类中定义好,具体实现延迟到子类中定义。
(2)方法回调(钩子方法):在软件开发中,我们可以将call翻译为调用,子类不能调用父类,而通过父类调用子类。这些调用步骤已经在父类中写好了,完全由父类控制整个过程。
11、访问者模式
(1)模式动机:对于存储在一个集合中的对象,他们可能具有不同的类型(即使有一个公共的接口),对于该集合中的对象,可以接受一类称为访问者的对象来访问,不同的是访问者其访问方式也有所不同。
(2)定义:表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用这些元素的新操作。