设计模式六大原则
单一职责原则
单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
不要使用一个类分担多个职责,影响原有代码逻辑。 增加功能不应该去修改已有代码,而是考虑是否到应该创建一个新的职责类。
例:
// 反例 每次新增业务都需要去修改原有的类
public class DBConection{
public static void connetion(String dbName){
if("Mysql".equals(dbName)){
System.out.print("连接到 Mysql 数据库")
}
if("Oracle".equals(dbName)){
System.out.print("连接到 Oracle 数据库")
}
.......
}
}
// 正例
public interface DbConnection{
void connetion();
}
// MysqlConnection 只负责 Mysql 数据库的连接
public class MysqlConnection implements DbConnection{
@override
public void connetion(){
System.out.print("连接到 Mysql 数据库")
}
}
里氏替换原则
里氏替换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
定义;一个软件实体如果适用于一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明地使用子类的对象。子类对象能够替换父类对象,而程序逻辑不变。
简单的说就是父类能用的地方,子类对象可以替换并且逻辑不变。
里式替换原则重要性
- 约束继承泛滥
- 加强程序的健壮性
那么在 java 语法中我们如何才能做到子类替换父类而逻辑不变呢?
需要可总结为子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
-
重载 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
子类的方法重载父类的方法时,方法的前置条件【即方法的输入,入参】要比父类的输入参数更加宽松
用子类对象替换父类,之前将调用父类的方法在子类替换之后仍然调用父类的方法。
-
重写 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
子类的方法实现父类方法时(重写,实现抽象方法) 方法的后置条件(方法的输出、返回值)要比父类更严格或者相同。
在java的语法中使用子类替换父类,一定是不会调用到父类原本的方法的,那么重写是否违反了里式替换原则呢?
本人带着疑问在知乎上找到了一个大家比较认可的回答。

public class A {
public LinkedList method(HashMap map) {
System.out.print("调用父类方法");
return new LinkedList();
}
}
public class AB extends A {
public List method(Map map) {
System.out.println("调用子类方法");
return new LinkedList();
}
}
public class Test {
public static void main(String[] args) {
HashMap map = new HashMap<>(16);
A a = new A();
a.method(map);
}
}
// 使用子类替换父类之后得到的结果一致打印的都是调用父类方法
public class Test {
public static void main(String[] args) {
HashMap map = new HashMap<>(16);
AB ab = new AB();
ab.method(map);
}
}
依赖倒转原则
依赖倒转原则(Dependence Inversion Principle) 高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。
例:
集合框架的 Collection 接口
// 所有的集合类都实现了 Collection 接口抽象
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
....
}
表面看起来说是要抽象以及针对接口编程。那么为什么叫依赖倒置原则呢?我们还是用职责单一原则中数据库连接的类来举例。
- 反例

- 正例

依赖倒转原则的优点
减少类之间的耦合、提高系统稳定性,提高代码可读性和可维护性,降低修改程序所造成的风险。
接口隔离原则
接口隔离原则(Interface Segregation Principle) 类间的依赖关系应该建立在最小的接口上
将接口职责细分,防止过多的空实现。
但也需要注意防止接口细粒度过小造成接口泛滥。
例:
public interface DataHandler {
// 转换为Xml
XmlObject convertToXml();
// 转换为json
JsonObject convertToJson();
// 初始化 Excel
Excel initExcel();
}
// 如果我此时只需要 实现 convertToXml convertToJson 的方法,实现DataHandler时必须实现initExcel 空方法
public interface DataHandler{
// 转换为Xml
XmlObject convertToXml();
// 转换为json
JsonObject convertToJson();
}
public interface ExcelHandler{
// 转换为json
Excel initExcel();
}
迪米特法则
迪米特法则(Law of Demeter) 一个类对自己依赖的类知道的越少越好
尽量减少两个对象之间的交互,如果两个对象之间不必直接通信。那么两个对象就不应该发生任何的直接作用。可以通过第三者转发这个个调用。简单来说就是引用一个第三者来降低系统的耦合。直接与其他类通信必然会造成和其他有耦合关系,例如: 组合 继承。 在多个类具有相互直接关系的类之间,增加一个中间类(Mediator) 调用者不用与实现类产生耦合关系,只需要关心调用的方法即可。
那么什么是直接调用?
未出现在成员变量、方法输入、输出中的类。与之直接交互称为直接调用。
迪米特法则的优点
降低类与类之间的耦合
开闭原则
开闭原则(Open-Closed Principle, OCP):应当对扩展开放,对修改关闭。
简介:
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试,整个流程对开发周期影响很大,这个时候就需要开闭原则来解决这种问题。开闭原则指出一个软件实体例如类、模块、函数应该对拓展开放,对修改关闭。用抽象构建框架提高软件系统的稳定性、可复用性以及可维护性。
开闭原则的重要性
-
开闭原则可以提高系统稳定性
只需要测试新增的功能,无需重新测试原有逻辑。
-
开闭原则可以提高可复用性
依据开闭原则无法修改旧的代码,开发需要依照职责单一原则、接口隔离原则。利用接口或抽象类进行业务细化。底层抽象业务可在其他复杂业务中进行复用。
-
开闭原则可以提高系统可维护性
当有修改需求时只需新增细化之后的业务模块的抽象实现即可无需修改整块的顶层业务模块。
复合模式 (附加)
尽量使用组合与聚合代替继承
设计模式
创建型设计模式
简单工程模式 Simple Factory
简单工厂模式又称静态工程方法,由一个工厂对象决定创建出哪一种产品类的实例,简单工厂模式不属于23种GOF设计模式之一。
public class AnimalFactory{
public static Animal getAnimal(String name){
if("tiger".equals(name){
return new Tiger();
}
if("dolphin".equals(name)){
return new Dolphin();
}
.....
return null;
}
}
// 简单工厂生产
Animal tiger = AnimalFactory.getAnimal("tiger");
工厂模式 Factory Model
一个工厂类负责生产一种标准的产品 符合设计模式单一职责原则和依赖倒转原则并且依赖于抽象
// 首先我们需要一个玩具工厂接口制定生产标准
public interface ToyFactory{
// 生产玩具
public static Toy product();
}
// IcePrincess Audi 都派生自 Toy 接口
public class LegoFactory implements ToyFactory{
@override
public static Toy product(){
return new IcePrincess();
}
}
public class CarFactory implements ToyFactory{
@override
public static Toy product(){
return new Audi();
}
}
// 选择一个实际我们需要的工厂,开始生产玩具
pbucli class demo{
public static void main(String[] args){
Car car = new CarFactory.product();
IcePrincess icePrincess = new LegoFactory.product();
}
}
抽象工厂模式 Abstract Factory
抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
例:

优势:一个产品族的工程对象即可创建出符合统一标准的所有产品。不用重复创建多个工程生产多个产品最后进行组装。
劣势:违反开闭原则,当产品族创建新的产品时所有继承抽象工厂的工程类都必须进行代码重构。
// 惠普电脑生产工厂
public class HPComputerFactory extends AbstractComputerFactory{
public static HPComputer createComputer(){
InterCpu cpu = this.crteateCpu();
SamsungMemory memory = this.createMemory();
return new HPComputer(cpu, memory)
}
public static Cpu crteateCpu(){
return InterCpuFactory.createCpu();
}
public static Merroy createMemory(){
return SamsungFactory.createMemory();
}
}
备注:工厂模式用于封装复杂的对象创建逻辑,只需要对使用者暴露创建接口即可。
建造者模式 Builder
适用于属性特别多的对象,采用链式编程简化对象的创建
// 简单的建造者模式
class User {
private String name;
private String password;
private String nickName;
private int age;
private User(String name, String password, String nickName, int age) {
this.name = name;
this.password = password;
this.nickName = nickName;
this.age = age;
}
// 链式调用设置各个属性值,返回 this,即 UserBuilder
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder password(String password) {
this.password = password;
return this;
}
public UserBuilder nickName(String nickName) {
this.nickName = nickName;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
}
// 也可以使用 Lombok 简化代码
@Builder
class User {
private String name;
private String password;
private String nickName;
private int age;
}
核心是:先把所有的属性都设置给 Builder,然后 build() 方法的时候,将这些属性复制给实际产生的对象。
原型模式 Prototype
基于原有对象产生新的对象实例
-
Object.copy() 浅拷贝
需要被copy的对象实现Cloneable接口,对象内部的对象拷贝的是同一引用。 -
序列化与反序列化 深拷贝
如果采用了JDK自带的序列化,需要实现 Serializable接口。然后进行反序列化 缺点:效率低
单例模式 Singleton
- 饱汉式
// 饱汉式缺点:当单例对象没有实例化的时候,Class 加载的时候就已经为此对象分配了内存空间
public class Sigleton{
// 私有的构造方法,防止外部通过构造方法实例化
private Sigleton {}
private Sigleton sigleton = new Sigleton();
public static Sigleton getInstance(){
return sigton;
}
}
- 饿汉式(线程安全)
// 双重检查 防止大量线程在synchronized代码块进行等待,造成锁升级。效率低
public class Sigleton{
// 私有构造方法
private Sigleton {}
// volatitle 保证对象在多个线程的可见性
private static volatitle Sigleton sigleton = null;
public static Sigleton getInstance(){
if(sigleton == null){
synchronized(Sigleton.class){
if(sigleton; == null){
sigleton = new Singleton();
}
}
}
return new Sigleton();
}
// 嵌套类 利用嵌套类的属性 第一次被引用的时候才被加载
public class Sigleton{
// 私有构造方法
private Sigleton {}
private static class SigletonHolder{
private final static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
结构型设计模式
适配器 Adapter
将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
类比: (两脚充电器 )—-> 转换头 --> (三脚充电器)
目标类:定义客户所需要的接口或抽象类 三脚充电器
适配类:作为转换器 对目标类和适配者类进行适配 转换头
适配者类:作为被适配对象,包装好了主要执行的业务逻辑 两脚充电器
- 类适配
// 适配者类
public interface TwoPinPlug{
void charge();
}
// 目标类
public class ThreePinPlug{
public void charge(){
....
}
}
// 适配类
public class PinPlugAdapter extends ThreePinPlug implements TwoPinPlug{
public void charge(){
....
}
}
- 对象适配
// 适配者类
public class TwoPinPlug{
public void charge(){
// 充电的主要业务逻辑
.....
}
}
// 目标类
public interface ThreePinPlug {
public void charge();
}
// 适配类
public class PinPlugAdapter implements ThreePinPlug{
TwoPinPlug twoPingPlug = null;
public PinPlugAdapter(TwoPinPlug twoPingPlug){
this.twoPingPlug = twoPingPlug;
}
TwoPinPlug twoPinPlug = new TwoPinPlug();
public void charge(){
twoPinPlug.charge();
}
}
// 用转换头装配好的充电器进行充电
ThreePinPlug adapter = new PinPlugAdapter();
adapter.charger();
优势:将目标类与适配类解耦,增加了类的复用性,将业务逻辑封装在适配类中,同一个适配者类可在不同环境中使用。
缺点:java是单继承,所以适配器的适配者只是是一个。
桥接模式 Bridge
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
例:
举一个形象的例子。当我们在画画的时候。需要三种粗细六种颜色的蜡笔。那么我们需要准备18支蜡笔。而同样的情况
如果我们选择了油彩笔和六种颜色的颜料就可以随意组合出你需要的所有画笔。如果粗细和颜色有变化只要新增即可
但使用蜡笔我们则需要准备更多的不同粗细和颜色的蜡笔。这就是桥接模式最生动的演示,当我们在软件开发时,某一
个类存在两个独立变 的维度时,通过桥接模式,可以将这两个维度分离出来,使两者可以单独扩展变化,让系统更符合
“单一职责”原则。
// 笔
public abstract class Pen{
// 写
public abstract void draw();
}
// 颜料
public interface Color{
}
组合模式 Composite
装饰器模式 Decorator
门面模式 Facade
代理模式 Proxy
当目标对象的功能不足以满足客户端的要求,系统为该对象创建一个代理对象,而代理对象可以增强原目标对象的功能
- 静态代理
// 代理类与被代理类实现同一个接口,代理类持有被代理类引用
public interface Light {
// 照明
public void lighting();
}
// 普通电灯
public class Lamp implements Light {
@Override
public void lighting(){
...........
}
}
public class LampProxy implements Light{
// 代理类持有被代理类的引用
private Lamp lamp;
public class LampProxy(Lamp lamp){
this.lamp = lamp
}
@Override
public void lighting(){
// 发亮
lamp.lighting();
// 加大瓦数
incrPowere();
// 变换色彩
changeColor();
}
}
Lamp lamp = new LampProxy(lamp)
lamp.lighting();
- 动态代理 (JDK)
动态代理实现了解耦,通过使用动态代理,程序就为被代理对象增加了额外功能。
-
实现 InvocationHandler接口 -
Proxy.newProxyInstance() 创建动态代理对象
publi class LampHandler implements InvocationHandler {
// 被代理对象
private Object target;
public void setTarget(Object target){
this.target = target;
}
// 执行动态代理对象的所有方法,都会被替换成执行如下方法
public Object invoke(Object proxy, Method method, Object[] args){
// do something ........
Object result method.invoke(target, args)
// do something ........
return result;
}
}
public class MyProxyFactory{
public static Object getProxy(Object target){
LampHandler lampHandler = new LampHandler();
// 为LampHandler设置Target值
handler.setTarget(target);
// 创建并返回一个动态代理
return Proxy.newProxyInstance(target.getclass.getClassLoader(), target.getClass.getInterfaces(), handeler)
}
}
- 动态代理(CGLib)
TODO
行为型设计模式
模板方法模式(Template Method)
责任链模式(Chain of Responsibility)
迭代器模式(Iterator)
观察者模式(Observer)
策略模式(Strategy)