前言
设计模式原则
开闭原则 | 对扩展开放,对修改关闭 |
单一职责原则 | 类职责应尽可能单一 |
里氏替代原则 | 只要父类能出现的地方,子类就可以出现 |
依赖倒置原则 | 细节应该依赖抽象 |
接口隔离原则 | 每个接口中不存在子类用不到却必须实现的方法 |
迪米特法则 / 最少知识原则 | 一个对象对其依赖应尽可能少的了解 |
合成复用原则 | 优先使用组合 / 聚合,其次考虑继承 |
概述
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性
除了适配器模式分为类结构型模式和对象结构型模式两种,其他的全部属于对象结构型模式
适配器模式 | 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 |
装饰模式 | 动态地给对象增加一些职责,即增加其额外的功能 |
外观模式 | 为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问 |
代理模式 | 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性 |
桥接模式 | 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度 |
组合模式 | 将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性 |
享元模式 | 运用共享技术来有效地支持大量细粒度对象的复用 |
适配器模式
定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
UML图
主要角色
-
目标接口:当前系统业务所期待的接口,它可以是抽象类或接口
-
适配者类:它是被访问和适配的现存组件库中的组件接口
-
适配器:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者
举个栗子
目标接口
public interface Powerable {
int outputDC5V();
}
适配者
public class ACLine {
public int outputAC220V() {
return 220;
}
}
适配器
public class PowerAdapter extends ACLine implements Powerable {
@Override
public int outputDC5V() {
// 适配
return this.adapt(super.outputAC220V());
}
/**
* @param input 不适配输入
* @return 可适配的输出
*/
private int adapt(int input) {
// 一系列的转换
return 5;
}
}
Client
public abstract class Client {
public static void main(String[] args) {
int requirePower;
// ideal:requiredPower < = DC 5V
Powerable power = new PowerBank();
requirePower = power.outputDC5V();
// unsafe: requiredPower 不适配于 AC 220V
ACLine specificPower = new ACLine();
requirePower = specificPower.outputAC220V();
// safe: requiredPower <= DC 5V
power = new PowerAdapter();
requirePower = power.outputDC5V();
}
}
装饰模式
定义
动态地给对象增加一些职责,即增加其额外的功能
UML图
主要角色
-
抽象构件:定义一个抽象接口以规范准备接收附加责任的对象
-
具体构件:实现抽象构件,通过装饰角色为其添加一些职责
-
抽象装饰:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能
-
具体装饰:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
外观模式
定义
为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问
UML图
主要角色
-
外观:为多个子系统对外提供一个共同的接口
-
子系统:实现系统的部分功能,客户可以通过外观角色访问它
代理模式
定义
为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性
UML图
主要角色
-
抽象主题:通过接口或抽象类声明真实主题和代理对象实现的业务方法
-
真实主题:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
-
代理者:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
和适配器模式、装饰模式、外观模式的对比
适配器模式、装饰模式、外观模式似乎都有代理的感觉
相似 | 目标 | |
---|---|---|
适配器模式 | 似乎在用适配器去代理适配者 | 接口间的兼容 |
装饰模式 | 似乎在用动态装饰去代理原生调用 | 不改变原生结构的情况下动态扩展功能 |
外观模式 | 似乎在用外观去代理多个子系统 | 化繁为简 |
代理模式 | - | 提供多个切面织入的接口 |
举个栗子
抽象主题
public interface UserService {
void login(String username, String password);
}
真实主题(负责核心业务)
public class UserServiceImpl implements UserService {
private static final String USERNAME = "IceFery";
private static final String PASSWORD = "123";
@Override
public void login(String username, String password) {
if (!username.equals(USERNAME) || !password.equals(PASSWORD))
System.out.println("failed to login");
else
System.out.println("login succeed");
}
}
代理类(织入一些切面功能)
@Slf4j
public class UserServiceProxy implements UserService {
// 真实对象
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
// 切入格式化参数功能、切入记录日志的功能
@Override
public void login(String username, String password) {
log.info("proxy start ...");
target.login(username.trim(), password.trim());
log.info("proxy end ...");
}
}
Client
public class Client {
public static void main(String[] args) {
// 真实对象
UserService target = new UserServiceImpl();
// 代理对象
UserService bind = new UserServiceProxy(target);
// 代理
bind.login(" IceFery ", "123 ");
}
}
若代理成功,则证明:真实主题(核心业务)调用成功,切面(周边功能)也织入成功
动态代理
动态代理解决了静态代理遇到的什么问题?
如果夸张点有100个业务类,还有50个周边功能。是写50个代理类代理100个业务;还是写100个代理类实现50个周边功能;还是另辟新径,动态生成代理类,动态织入周边功能呢?
JDK
动态代理
业务接口和业务实现不变
代理类实现IncocationHandler
接口,利用反射来代理真实对象的核心业务,并织入切面
@Slf4j
public class ServiceProxy<T> implements InvocationHandler {
private final T target;
public ServiceProxy(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("proxy start ...");
for (int i = 0; i < args.length; i++) {
args[i] = String.valueOf(args[i]).trim();
}
Object result = method.invoke(target, args);
log.info("proxy end ...");
return result;
}
}
利用静态工厂方法生产代理类
public class ServiceProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T newServiceProxy(T target) {
ServiceProxy<T> serviceProxy = new ServiceProxy<>(target);
/*
* param1: 生成代理类的类加载器(和真实对象使用同一类加载器)
* param2: 真实对象实现的接口(通常是业务接口)
* param3: 实现InvocationHandler接口的类(通常是织入周边功能的类)
*/
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
serviceProxy);
}
}
Client
public class Client {
public static void main(String[] args) {
// 真实对象
UserService target = new UserServiceImpl();
// 工厂生成代理对象
UserService bind = ServiceProxyFactory.newServiceProxy(target);
// 代理
bind.login("IceFery", "123 ");
}
}
CGLIB
动态代理
导入cglib
依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
不再需要业务接口,业务实现不变
代理类实现MethodInterceptor
接口,利用反射来代理真实对象的核心业务,并织入切面
@Slf4j
public class ServiceProxy implements MethodInterceptor {
@Override
public Object intercept(Object object,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
log.info("proxy start ...");
for (int i = 0; i < args.length; i++) {
args[i] = String.valueOf(args[i]).trim();
}
Object result = proxy.invokeSuper(object, args);
log.info("proxy end ...");
return result;
}
}
利用静态工厂方法模式生产代理类
public class ServiceProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T newServiceProxy(Class cls) {
// 增强类对象
Enhancer enhancer = new Enhancer();
// 设置增强类型
enhancer.setSuperclass(cls);
// 设置代理逻辑对象, 要求其实现MethodIntercepter接口
enhancer.setCallback(new ServiceProxy());
// 生成代理对象
return (T) enhancer.create();
}
}
Client(不再需要接口对象)
public class Client {
public static void main(String[] args) {
// 真实对象
UserServiceImpl target = new UserServiceImpl();
// 工厂生成代理对象
UserServiceImpl bind = ServiceProxyFactory.newServiceProxy(target.getClass());
// 代理
bind.login("IceFery", " 123 ");
}
}
说明
以上代码仅作演示,并不能动态织入切面实现AOP
JDK
动态代理和CGLIB
动态代理有什么区别?
JDK 动态代理 | CGLIB 动态代理 | |
---|---|---|
原理 | 利用反射机制生成一个实现代理接口的匿名类, 在调用具体方法时调用 InvokeHandler 来处理 | 利用asm 开源包,对代理对象类的class 文件加载进来,通过修改其字节码生成子类来处理。 |
AOP 实现 | InvocationHandler 是一个接口。可以通过实现该接口定义横切逻辑,并通过反射机制调使用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起 | CGLib 采使用底层的字节码技术,全称是:Code Generation LibraryCGLib 可以为一个类创立一个子类,在子类中采使用方法阻拦的技术阻拦所有父类方法的调使用并顺势织入横切逻辑。 |
注意事项 | JDK 动态代理是面向接口的(换言之被代理类必须要实现接口) | 对于final 类或方法,无法继承,无法代理 |
速度 | jdk.version ++ --> v ↑↑ | 创建慢,运行快 |
桥接模式
定义
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度
UML图
主要角色
-
抽象化角色:定义抽象类,并包含一个对实现化对象的引用
-
扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
-
实现化角色:定义实现化角色的接口,供扩展抽象化角色调用
-
具体实现化角色:给出实现化角色接口的具体实现。
举个栗子
通俗理解
抽象化角色——笔可以扩展为钢笔、铅笔、圆珠笔等等。这是一个维度的扩展
还可以按照扩展化角色——笔芯或笔墨的不同扩展为红笔、蓝笔、黑笔等等。这是第二个维度的扩展
还可以按照扩展化角色——笔的材质的不同扩展为塑料笔、铁笔、木头笔等等。这是第三个维度的扩展
然后就可以
Pen pen = new XXXPen();
pen.setImplementorA(...);
pen.setImplementorB(...);
在不同的维度上扩展实现
组合模式
定义
将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性
UML图
透明式组合模式
安全式组合模式
主要角色
-
抽象构件:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成
-
树叶构件:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口
-
树枝构件:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含
add()
、remove()
、getChild()
等方法
使用场景
-
需要表示一个对象整体与部分的层次结构的场合
-
要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合
举个栗子
JSON
对象变现为树的层次结构,隐藏组合的JSON
和单个的JSON
的不同,全部可以以key-value
的形式访问
享元模式
定义
运用共享技术来有效地支持大量细粒度对象的复用
主要角色
-
抽象享元角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入
-
具体享元:实现抽象享元角色中所规定的接口
-
非享元:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中
-
享元工厂:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象
可否用一句话直观的描述享元模式?
在工厂的基础上加入池的技术,用非享元改造享元生产对象
可否具体?
String类常量池、数据库连接池…等等各种池
可否再具体?
签到时,从已经到场的人里面(享元工厂),找一个人(享元),给他提供名字(非享元),出勤率100%