三、设计模式
3.1 设计模式简介
- 软件设计中的
三十六计
- 是人们在长期的软件开发中的经验总结
- 是对某些特定问题的经过实践检验的特定解决方法
- 被广泛运用在 Java 框架技术中
3.1.1 设计模式的优点
- 设计模式是可复用的面向对象软件的基础
- 可以更加简单方便地复用成功的设计和体系结构
- 帮助开发者做出有利于系统复用的选择,避免损害系统复用性的设计
- 使其他开发者更加容易理解其设计思路,便于团队交流
3.1.2 设计模式分类
GoF(Gang of Four,四人组)设计模式分为23种
范围/目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法解释器 |
对象模式 | 单例 原型 抽象工厂 建造者 | 代理 (对象)适配器 桥接 装饰 外观 享元 组合 | 策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
3.1.3 面向对象设计原则
单一职责原则
- 一个类应该有且仅有一个引起它变化的原因
- 一个类应该只负责一个职责
开闭原则
- 对扩展开放,对修改关闭
里氏替换原则
- 引用基类的地方必须能透明地使用其子类的对象
- 可以用来判断继承关系是否合理
依赖倒置原则
- 依赖于抽象而不依赖于具体实现,针对接口编程
接口隔离原则
- 尽量将庞大臃肿的接口拆分成更小更具体的接口
- 接口中只包含客户感兴趣的方法
迪米特法则
- 又称最少知道原则
- 一个软件实体应当尽可能少地与其他实体发生相互作用
合成复用原则
- 尽量使用组合/聚合的方式而不是继承关系达到软件复用的目的
- 是 has-a 关系
3.2 简单工厂模式
如何解决类似“Service与某个具体Dao实现”耦合的问题?
将创建工作转移出来避免在Service中创建具体的Dao实现类,产生耦合
简单工厂模式,又叫做静态工厂方法模式,不属于 GoF 的23种设计模式之一,可以理解为工厂模式的一个特殊实现
3.2.1 简单工厂模式+依赖倒置原则
依据依赖倒置原则,使用setter方法传递依赖关系,减少Service对工厂类的依赖,降低耦合
public class NewsServiceImpl implements NewsService {
private NewsDao dao;
public void setDao(NewsDao dao) {
this.dao = dao;
}
… …
}
3.2.2 简单工厂+参数
简单工厂模式可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
// 创建NewsDao实例的工厂方法
public static NewsDao getInstance(String key) {
switch (key) {
case "mysql":
return new NewsDaoMySqlImpl();
case "oracle":
return new NewsDaoOracleImpl();
case "redis":
return new NewsDaoRedisImpl();
default:
throw new RuntimeException("无效的数据库类型:" + key + " ,DAO获取失败");
}
}
要创建的产品不多且逻辑不复杂的情况,可以考虑简单工厂模式
简单工厂模式包含如下角色
- 工厂(Factory)
- 抽象产品(Product)
- 具体产品(Concrete Product)
增加新的产品需要修改,工厂方法的判断逻辑,不符合开闭原则
3.3 工厂方法模式
3.3.1 实现方式
对简单工厂模式的进一步抽象,工厂方法模式的主要角色如下
- 抽象产品(Product)
- 抽象工厂(Abstract Factory)
- 具体产品(Concrete Product)
- 具体工厂(Concrete Factory)
3.3.2 代码案例
创建抽象工厂接口
public interface AbstractFactory {
public NewsDao getInstance();
}
为不同NewsDao实现创建相对应的具体工厂
// 以生产NewsDaoMySqlImpl实例的工厂为例
public class MySqlDaoFactory implements AbstractFactory {
@Override
public NewsDao getInstance() {
return new NewsDaoMySqlImpl();
}
}
在测试方法中通过特定工厂生产相关的NewsDao实例
AbstractFactory factory = new MySqlDaoFactory();
// 改变具体工厂可创建不同产品
NewsDao dao = factory.getInstance();
3.3.3 优缺点
优点
- 只需要知道具体工厂就可得到所要的产品,无须知道产品的具体创建过程
- 基于多态,便于对复杂逻辑进行封装管理
- 增加新的产品时无须对原工厂进行任何修改,满足开闭原则
缺点
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度
3.4 代理设计模式
单一职责原则的体现,包含如下角色
- 抽象主题(Subject)
- 真实主题(Real Subject)
- 代理(Proxy)
实现方式总体上分为静态代理和动态代理
- 静态代理由开发者针对抽象主题编写相关的代理类实现,编译之后生成代理类的class文件
- 动态代理是在运行时动态生成的,在运行时动态生成代理类字节码
3.4.1 基于接口的静态代理实现
// 抽象主题接口 - 图片
public interface Image {
void display();
}
// 真实主题类 - 真实图片
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading image from disk: " + filename);
}
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 代理类 - 图片代理
public class ImageProxy implements Image {
private RealImage realImage;
private String filename;
public ImageProxy(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 调用代码
public class Client {
public static void main(String[] args) {
// 创建代理对象并显示图片
Image image = new ImageProxy("example.jpg");
image.display();
}
}
3.4.2 代理模式优点分析
- 代理模式将客户与目标对象分离,在一定程度上降低了系统的耦合度
- 代理对象可以对目标对象的功能进行扩展,目标对象和扩展功能职责清晰且不会产生耦合
3.4.3 动态代理
静态代理需要手工编写代理类,存在以下弊端
- 目标对象API发生变化,代理类也必须进行修改,增加工作量且不符合开闭原则
- 通过继承得到的代理类只能对一种类型进行代理,组件较多时,代理类的开发工作量巨大
- 动态代理提供了运行时动态扩展对象行为的能力
- 能够依据给定的业务规则,在运行时动态生成代理类
3.4.4 JDK 动态代理
从JDK 1.3版本开始引入
是面向接口的代理实现
- 要求被代理的目标对象必须通过抽象主题接口进行定义
核心API
- java.lang.reflect.InvocationHandler接口
- 代理方法的调用处理程序,负责为代理方法提供业务逻辑
- 包含方法:Object invoke(Object proxy, Method method, Object[] args)
- java.lang.reflect.Proxy类
- 负责动态创建代理类及其实例
- 主要方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
3.4.5 CGLIB 动态代理
如果被代理的目标对象不是通过接口进行定义的,JDK 动态代理将无法实施
- CGLIB(Code Generation Library)是一个功能强大,高性能的代码生成库
- 可以为没有实现接口的类提供代理,原理是为需要代理的类动态生成一个子类作为其代理类
需要使用继承和重写机制,CGLIB动态代理对于final类或final方法无能为力
从cglib https://github.com/cglib/cglib/releases下载所需的 jar 文件
- cglib-nodep-x.x.x.jar
主要 API
- net.sf.cglib.proxy.MethodInterceptor 接口
- 负责拦截父类的方法调用,以便加入代理的业务逻辑
- 包含方法
- Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
- net.sf.cglib.proxy.Enhancer 类
- 负责动态创建代理类及其实例
- 主要方法
- setSupperclass()
- setCallback()
- set…
- create()
3.4.6 JDK 和 CGLIB 动态代理的对比
- JDK 动态代理面向接口代理,只能对基于接口设计的目标对象进行代理
- CGLIB 动态代理可以通过继承方式实现,不依赖接口,但是不能代理 final 的类和方法