介绍Java结构型设计模式
本文我们聚焦java中实现结构设计模式,讨论它们是什么以及它们之间的基本差异。
设计模式分类
根据GOF对设计模式的分类,有三种:
- 创建型模式
- 结果型模式
- 行为型模式
简言之,结构模式处理类和对象的组合。它们提供不同方式使用对象组合和继承创建一些抽象。属于结构型模式有代理模式,装饰模式,适配器模式,桥接模式,门面模式,享元模式,组合模式。
代理模式
通过该模式,我们创建媒介作为其他资源的接口,如文件、连接。这种辅助方式作为真正组件的代理,隐藏底层的复杂性。
示例
假设有一个需要初始化配置的大对象(如JDBC连接或SessionFactory)。这样对象一旦被初始化后,希望后续调用可以重用:
下面创建一个简单接口,用于配置该对象:
public interface ExpensiveObject {
void process();
}
然后实现该接口,带有繁重的初始化配置:
public class ExpensiveObjectImpl implements ExpensiveObject {
public ExpensiveObjectImpl() {
heavyInitialConfiguration();
}
@Override
public void process() {
LOG.info("processing complete.");
}
private void heavyInitialConfiguration() {
LOG.info("Loading initial configuration...");
}
}
现在利用代理模式根据需要初始化对象:
public class ExpensiveObjectProxy implements ExpensiveObject {
private static ExpensiveObject object;
@Override
public void process() {
if (object == null) {
object = new ExpensiveObjectImpl();
}
object.process();
}
}
无论什么时间客户端调用process方法,它们仅能看到方法调用,而初始化配置总是被隐藏:
public static void main(String[] args) {
ExpensiveObject object = new ExpensiveObjectProxy();
object.process();
object.process();
}
注意当调用process方法两次,背后初始化部分仅执行一次,即对象初始化。后续每次调用,该模式会跳过初始化过程,仅调用process方法:
Loading initial configuration...
processing complete.
processing complete.
什么时候使用代理模式
理解如何使用模式很重要,但理解什么时候使用时关键的。下面讨论什么时候使用代理模式:
-
当我们想简化复杂或繁重对象时。这时我们可以使用框架对象表示原始对象,其根据需要加载原始对象,也称为懒加载。即所谓虚拟代理。
-
当原始对象在不同的地址空间中,我们想在本地表示它。可以创建其代理负责执行所有必要的例行操作,如创建和维护连接、编码、解码等。当客户端访问它时,它存在本地地址空间中。这称为远程代理。
-
当我们想要在原始基础对象上添加一层安全性,以便基于客户机的访问权限提供受控访问时。这称为保护代理。
关键差异
-
代理提供与原理对象相同接口,其包括对原始对象的引用,但它不会以任何方式修改数据;与适配器和装饰器模式相反,它们两者分别更改和装饰已有实例的功能。
-
代理通常在编译时拥有关于实际主题的信息,而装饰器和适配器则在运行时被注入,只知道实际对象的接口。
装饰模式
该模式用于增强一个对象的行为。详细内容请看这篇博文。
关键差异包括下面几点:
- 虽然代理和装饰器模式有类似的结构,但它们的意图不同。代理模式首要目的是便于使用或控制访问,装饰模式是增加额外的功能。
- 两者都持有原始对象的引用。
- 装饰器模式中装饰元素可以递归使用,不限制次数,而其他模式不能。
适配器模式
适配器模式用于连接两个不兼容接口,否则两者不能直接连接。适配器使用新的接口包装已经存在的类,这样使其兼容称为我们需要的接口。详细描述可以查看介绍java适配器短文。
适配器与代理模式的主要差异点为:
- 虽然代理模式提供了相同的接口,但适配器模式提供了与其客户机兼容的不同接口。
- 适配器模式在应用组件已经设计好之后使用,因此可以不修改源码进行使用。这与桥接模式相反,其一般在组件设计之前使用。
桥接模式
GOF官方定义桥接模式为将抽象与其实现解耦,以便两者能够独立地变化。
这意味着使用OOP原则创建一个将职责划分为不同抽象类的桥接接口。
示例
对于Bridge模式,我们将考虑两个抽象层;一个是几何形状(如三角形和正方形),填充了不同的颜色(我们的第二个抽象层):
首先定义color接口:
public interface Color {
String fill();
}
然后定义接口的实现类:
public class Blue implements Color {
@Override
public String fill() {
return "Color is Blue";
}
}
现在创建抽象Shape类,其包含一个color对象引用(桥接):
public abstract class Shape {
protected Color color;
//standard constructors
abstract public String draw();
}
现在创建Shape的具体实现类,其也利用Color接口的方法:
public class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public String draw() {
return "Square drawn. " + color.fill();
}
}
测试该模式,下面测试代码返回true:
@Test
public void whenBridgePatternInvoked_thenConfigSuccess() {
//a square with red color
Shape square = new Square(new Red());
assertEquals(square.draw(), "Square drawn. Color is Red");
}
这里我们使用桥接模式并传入期望的color对象。通过输出信息shape使用期望的color进行绘制:
Square drawn. Color: Red
Triangle drawn. Color: Blue
什么时候使用桥接模式
- 当我们想要父抽象类定义一组基础规则,具体实现类增加额外规则时。
- 当我们有一个抽象类,它有一个对象引用,并且它有抽象方法,这些方法将在每个具体类中重载。
关键差异点
- 桥接模式仅在应用设计之前被实现。
- 桥接模式允许抽象和实现独立更改,而适配器模式是不兼容类可以连接成为可能。
总结
本文我们聚焦结构型模式并对比分析它们之间差异。