面向对象的六大原则
1.单一职责原则——SRP(Single Responsibility Principle)
一个类中应该是一组相关性很高的函数、数据的封装
2.开闭原则——OCP(Open Close Principle)
软件中的对象(类、模块和函数等)应该对于扩展是开放的,但是对于修改是封闭的
3.里氏替换原则——LSP(Lisdow Substitution Principle)
如果对没一个类型为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所有的对象O1替换为O2时,程序P的行为没有发生变化,那么类型S就是类型T的子类型
4.依赖倒置原则——DIP(Dependence Inversion Principle)
指代了一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了
5.接口隔离原则——ISP(InterfaceSegregation Principles)
客户端不应该依赖它不需要的接口
6.迪米特原则——LOD(Law of Demeter)
一个对象应该对其他对象有最少的了解
创建型模式:5个
单例模式、Builder、原型模式、工厂方法、抽象工厂
行为型模式: 11个
策略模式、状态模式、观察者模式、中介者模式、访问者模式、迭代器模式、模板方法、备忘录模式、命令模式、解释器模式、职责链模式
结构型模式:7个
组合模式、代理模式、装饰模式、外观模式、享元模式、享元模式、桥接模式、适配器模式
1.单例模式
定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例
使用场景:
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该且只有一个,例如访问IO和数据库等资源
单例模式的主要关键点:
构造函数不对外开放,一般为private
通过一个静态方法或者枚举返回单例类对象
确保单例类的对象有且只有一个,尤其在多线程环境下
确保单例类对象在反序列化时不会重新构建对象
单例模式常用的实现方式
饿汉单例——在声明静态对象时就已经初始化
1
2
3
4
5public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
}懒汉单例——声明静态对象,在用户第一次调用getInstance时进行初始化
1
2
3
4
5
6
7
8
9
10
11
12public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}Double CheckLock(DCL)实现单例——既能在需要时才初始化,又能保证线程安全,并且单例对象初始化后调用getInstance不进行同步锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}静态内部类单例模式——建议用于解决DCL在某些情况下出现失效的问题
1
2
3
4
5
6
7
8
9
10
11public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}枚举单例
1
2
3
4
5
6
7public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do sth");
}
}使用容器实现单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显
- 由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式
- 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问
缺点
- 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现
- 单例对象如果持有Context,很容易内存泄露,传递时最好用Application Context
2.Builder模式
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
使用场景:
相同的方法,不同的执行顺序,产生不同的事件结果时
多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时
产品类非常复杂,或者产品类中调用顺序不同产生了不同的作用,这个时候使用建造者模式非常适合
当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值
优点
- 良好的封装性,使用建造者模式可以使客户端不必知道产品内部组成的细节
- 建造者独立,容易扩展
缺点
- 会产生多余的Builder对象以及Director对象,消耗内存
简单实现
1 | public class Phone { |
3.原型模式
定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
使用场景:
- 类初始化需要消耗 非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗
- 通过new产生一个对象需要非常频繁的数据准备或者访问权限,这时可以使用原型模式
- 一个对象需要提供给其他对象访问,而且各个调用者都可能需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
优点
原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可以更好地体现其有点
缺点
- 直接在内存中拷贝,构造函数不会执行,在实际应用开发中需要注意这个潜在的问题
- 使用浅拷贝操作副本时可能会影响原始对象
简单实现
1 | public class WordDocument implements Cloneable { |
4.工厂方法模式
定义:定义一个用于创建对象的接口,让子类决定实例化哪个类
使用场景:
在任何需要生成复杂对象的地方,都可以使用工厂方法模式。复杂对象适合使用工厂模式,用new就可以完成创建的对象无需使用工厂模式
简单实现:
1 | public abstract class BaseFactory { |
5.抽象工厂模式
定义:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类
使用场景:
一个对象族有相同的约束时可以使用抽象工厂模式
优点
分离接口与实现,客户端使用抽象工厂来创建需要的对象,而客户端根本不知道具体的实现是谁,客户端只是面向产品的接口编程,使其从具体的产品实现中解耦,同时基于接口与实现的分离,使抽象工厂方法模式在切换产品类时更加灵活、容易
缺点
- 对类文件的爆炸性增加
- 不太容易扩展新的产品类,因为每当我们增加一个产品类就需要修改抽象工厂,那么所有的具体工厂类都会被修改
简单实现
1 | //抽象产品A接口 |
6.策略模式
定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化
使用场景:
- 针对同一类型问题的多重处理方式,仅仅是具体行为有差别时
- 需要安全地封装多种同一类型的操作时
- 出现同一抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时
优点
- 结构清晰明了、使用简单直观
- 耦合度 相对而言较低,扩展方便
- 操作封装也更为彻底,数据更为安全
缺点
随着策略的增加,子类也会变得繁多
简单实现
1 | //创建一个接口 |
7.状态模式
定义:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类
使用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为
- 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有庞大的多分支语句(if-else或switch-case),且这些分支依赖于该对象的状态
优点
State模式将所有与一个特定的状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将烦琐的状态结构判断转换成结构清晰的状态类族,在避免代码膨胀的同时也保证了可扩展性和可维护性
缺点
状态模式的使用必然会增加系统类和对象的个数
简单实现
1 | //创建一个接口 |
8.责任链模式
定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止
使用场景
- 多个对象可以处理同一请求,但具体由哪个对象处理则在行动时动态决定
- 在请求处理者不明确的情况下向多个对象中的一个 提交一个请求
- 需要动态指定一组对象处理请求
优点
可以对请求者和处理者关系解耦,提高代码的灵活性
缺点
对链中请求处理者的遍历,如果处理者太多那么遍历必定会影响性能,特别是在一些递归调用中
简单实现
1 | //创建抽象的请求者 |
9.解释器模式
定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来结算语言中的句子
使用场景
- 如果某个简单的语言需要解释执行而且可以将该语言中的语句表示为一个抽象语法树时可以考虑使用
- 在某些特定领域出现不断重复的问题时,可以将该领域的问题转化为一种语法规则如下的语句,然后构建解释器来解释该语句
优点
具有灵活的扩展性,当我们相对文法规则进行扩展延伸时,只需要增加相应的非终结符解释器,并在构建抽象语法树时,使用到新增的解释器对象进行具体的解释即可
缺点
- 对于每一条文法都可以对应至少一个解释器,会生产大量的类,导致维护困难
- 对于过于复杂的文法,构建其抽象语法树会显得异常繁琐,甚至可能会出现需要构建多棵抽象语法树的情况,因此,对于复杂的文法并不推荐使用解释器模式
简单实现
1 | //创建一个接口表达式 |
10.命令模式
定义:将一个请求封装为一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作
使用场景
- 某一对象有一系列的事物操作
- 具有抽象行为的动作,支持多种类型的操作
优点
更弱的耦合性、更灵活的控制性以及更好的扩展性
缺点
容易有类的膨胀,大量衍生类的创建,简单的行为命令反而复杂
简单实现
1 | //创建一个命令接口 |
11.观察者模式
定义:定义对象见一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并更新
使用场景:
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系
- 事件多级触发场景
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制
优点
- 观察者和被观察者之间是抽象耦合,应对业务变化
- 增强系统灵活性、可扩展性,建立一套触发机制
缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
简单实现
1 | //观察者 |
12.备忘录模式
定义:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可以将该对象恢复到原先保存的状态
使用场景
- 需要保存一个对象在某一个时刻的状态或部分状态
- 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态
优点
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
简单实现
1 |
|
13.迭代器模式
定义:提供一种方法顺序访问一个容器对象中的各个元素,而又不需要暴露该对象的内部表示
使用场景
遍历一个容器对象时
优点
- 它支持以不同的方式遍历一个聚合对象。
- 迭代器简化了聚合类。
- 在同一个聚合上可以有多个遍历。
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点
由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
简单实现
1 | //迭代器接口 |
14.模板方法模式
定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构可重定义该算法的某些特定步骤
使用场景
- 多个子类有公有方法,并且逻辑基本相同时
- 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能由各个子类实现
- 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类,然后通过钩子函数约束其行为
优点
- 封装不变部分,扩展可变部分
- 提取公共部分代码,便于维护
缺点
- 模板方法会带来代码阅读的难度,会让用户觉得难以理解
简单实现
1 | //创建一个抽象类,它的模板方法被设置为 final。 |
15.访问者模式
定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
使用场景
- 对象结构比较稳定,但经常需要在此对象结构上定义新的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望再增加新操作时修改这些类
优点
- 各角色职责分离,符合单一职责原则
- 具有优秀的扩展性
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
- 灵活性
缺点
- 具体原生对访问者公布细节,违反了迪米特原则
- 具体元素变更时导致修改成本大
- 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象
简单实现
1 | //定义一个表示元素的接口 |
16.中介者模式
定义:中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用。从而使它们可以松散耦合。当某些对象之间的作用发生改变时,不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。中介者模式将多对多的相互作用转化为一对多的相互作用。中介者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理
使用场景
多个类相互耦合,形成了网状结构。
优点
- 如果类的依赖关系如网状般错综复杂,适当使用中介者模式可以对这种依赖关系进行解耦使逻辑结构清晰
缺点
- 如果类之间的依赖关系不复杂,使用中介者反而会使得原本的逻辑结构变得复杂
简单实现
1 | //创建User类 |
17.代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问
使用场景
当无法或不想直接访问某个对象或者访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口
优点
- 职责清晰。
- 高扩展性。
- 智能化。
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
简单实现
1 | //创建一个接口。 |
18.组合模式
定义:将对象组合成树形结构以表示是‘’部分-整体“的层次结构,使得用户对单个对象和组合对象的使用具有一致性
使用场景
- 表示对象的部分-整体层次结构时
- 从一个整体中能独立出部分模块或功能的场景
优点
- 高层模块调用简单。
- 节点自由增加。
简单实现
1 | //创建 Employee 类,该类带有 Employee 对象的列表 |
19.适配器模式
定义:适配器模式把一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作
使用场景
- 系统需要使用现有的类,而此类接口不符合系统的需要,即接口不兼容
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作
- 需要一个统一的输出接口,而输入端的类型不可预知
优点
更好的复用性
系统需要使用现有的类,而此类的接口不符合系统的需要。使用适配器模式就可以让这些功能得到更好的复用
更好的扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能
缺点
- 过多使用适配器会让系统非常凌乱,不容易整体把握
- 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
简单实现
1 | //为媒体播放器和更高级的媒体播放器创建接口。 |
20.装饰模式
定义:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活
使用场景
需要透明且动态地扩展类的功能时
优点
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点
- 多层装饰比较复杂。
简单使用
1 | //创建一个接口 |
21.享元模式
定义:使用共享对象可有效地支持大量的细粒度的对象
使用场景
- 系统中存在大量的相似对象
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
- 需要缓冲池的场景
优点
- 大大减少对象的创建,降低系统的内存,使效率提高。
缺点
- 使得系统更加复杂,为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化
- 将享元对象的状态外部化,而读取外部状态使时间稍微边长
简单使用
1 | //创建一个接口。 |
22.外观模式
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行,外观模式提供一个高层次的接口,使得子系统更易于使用
使用场景
- 为复杂的模块或子系统提供外界访问的模块。
- 子系统相对独立。
- 预防低水平人员带来的风险。
优点
- 对客户程序隐藏子系统细节,因而减少了客户对于子系统的耦合,能够拥抱变化
- 外观类对子系统的接口封装,使得系统更易于使用
缺点
- 外观类接口膨胀。由于子系统的接口都有外观类统一对外暴露,使得外观类的API接口较多,在一定程度上增加了用户使用成本
- 外观类没有遵循开闭原则,当业务出现变更时,可能需要直接修改外观类
简单实现
1 | /** * 电脑接口 */ |
23.桥接模式
定义:将抽象部分与实现部分分离,使它们都可以独立地进行变化
使用场景
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
优点
- 抽象和实现的分离。
- 优秀的扩展能力。
- 实现细节对客户透明。
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
简单实现
1 | //创建桥接实现接口。 |