一、前言
在上一篇 《深入了解JAVA中的23种设计模式(一)- 创建型模式》 中介绍了Java中的23种设计模式的创建型模式中的一些设计模式,本文将继续介绍设计模式中的结构型模式。
二、结构型模式
1. 适配器模式
1.1 简介
适配器模式就是起到一个中间桥梁的作用,将不兼容的接口转换成可兼容的接口
1.2 使用场景
当新系统需要接入一个旧的接口,但该接口的协议或数据格式与新系统不兼容时,可以使用适配器模式进行转换。
当系统需要与多个不同的接口进行交互,而这些接口的协议或数据格式各不相同时,可以使用适配器模式将这些接口进行统一封装,从而简化系统的接口调用。
当需要复用已有的类库,但该类库的接口不符合当前系统的要求时,可以使用适配器模式进行接口转换,从而在不修改类库代码的情况下实现复用。
1.3 代码示例
// 目标接口
public interface Target {
void request();
}
// 需要适配的类
public class Adaptee {
public void specificRequest() {
System.out.println("Called specificRequest()");
}
}
// 适配器类,实现了客户端期望的接口,并持有需要适配的类的实例
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 在适配器的request方法中,调用被适配类的specificRequest方法
adaptee.specificRequest();
}
}
2. 装饰模式
2.1 简介
是一种用于动态地给一个对象添加一些额外的职责的模式。就增加功能来说,装饰模式相比生成子类更为灵活。
2.2 使用场景
当需要给一个对象动态地增加功能,而且这些功能可以动态地被撤销时,可以使用装饰模式。
当不能采用继承的方式对系统进行扩展,或者采用继承不利于系统扩展和维护时,可以使用装饰模式。
客户端可以透明地使用装饰之前的对象和装饰之后的对象,因为装饰之后的对象实现了和被装饰对象相同的接口。
2.3 代码示例
// 抽象接口
public interface Component {
void operation();
}
// 具体的实现
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("执行具体组件的操作");
}
}
// 装饰者抽象类
public abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
if (component != null) {
component.operation();
}
}
}
// 具体的装饰者类
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedFunctionA();
}
public void addedFunctionA() {
System.out.println("执行装饰者A的额外操作");
}
}
// 另一个具体的装饰者类
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedFunctionB();
}
public void addedFunctionB() {
System.out.println("执行装饰者B的额外操作");
}
}
3. 代理模式
3.1 简介
代理对象在客户端和目标对象之间起到中介的作用,客户端并不直接访问目标对象,而是通过代理对象间接地访问目标对象。以下是代理模式的主要作用:
保护目标对象:在某些情况下,一个对象不想或者不能直接被外部访问,这时可以通过一个代理对象来间接访问,隐藏目标对象的实现细节,确保目标对象的安全性。
增强目标对象:代理对象可以在客户端调用目标对象的方法前后添加一些额外的处理逻辑,比如权限校验、日志记录、事务处理等。
控制访问:代理对象可以控制对目标对象的访问权限,比如限制某些方法的调用,或者根据条件决定是否将请求转发给目标对象。
3.2 使用场景
当需要为原始对象添加额外的功能时,可以使用代理模式。代理对象可以在不修改原始对象代码的情况下,为其添加新的功能。
当需要对对象访问进行权限控制时,可以使用代理模式来实现。例如,对敏感操作进行权限验证,只有具有相应权限的用户才能执行操作
3.3 代码示例
// 目标接口
public interface Image {
void display();
}
// 目标对象实现
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName); // 假设这是一个耗时操作
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
// 模拟从磁盘加载图片
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
// 这里省略了实际的加载逻辑
}
}
// 代理对象实现
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName); // 在第一次显示时加载图片
}
realImage.display();
}
}
4. 外观模式
4.1 简介
它为子系统中的一组接口提供一个统一的高层接口,使得子系统更加容易使用
4.2 使用场景
随着系统的不断演进,子系统可能会变得越来越复杂,客户端与这些子系统之间的依赖关系也会变得难以管理。这时,可以使用外观模式为子系统提供一个简单的高层接口,客户端只需要与这个接口交互,而无需关心子系统内部的复杂结构。
在某些情况下,我们可能希望隐藏子系统的某些细节,只向客户端暴露必要的接口。
当客户端需要调用子系统的多个接口时,如果直接调用这些接口,可能会导致客户端代码变得复杂且难以维护。使用外观模式可以将这些接口组合成一个统一的接口,客户端只需要调用这个接口即可完成所需的操作。
4.3 代码示例
// 子系统接口A
public interface SubSystemA {
void operationA();
}
// 子系统接口B
public interface SubSystemB {
void operationB();
}
// 子系统A的实现
public class SubSystemAImpl implements SubSystemA {
@Override
public void operationA() {
System.out.println("Subsystem A operation A");
}
}
// 子系统B的实现
public class SubSystemBImpl implements SubSystemB {
@Override
public void operationB() {
System.out.println("Subsystem B operation B");
}
}
// 外观类
public class Facade {
private SubSystemA subSystemA;
private SubSystemB subSystemB;
public Facade() {
subSystemA = new SubSystemAImpl();
subSystemB = new SubSystemBImpl();
}
public void operation() {
subSystemA.operationA();
subSystemB.operationB();
}
}
5. 桥接模式
5.1 简介
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
5.2 使用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。
在抽象和具体实现之间需要增加更多的灵活性的场景。
一个类存在两个或多个独立变化的维度,而这两个或多个维度都需要独立进行扩展。
不希望使用继承,或因为多层继承导致系统类的个数剧增。
5.3 代码示例
// 实现化接口
public interface Implementor {
void operationImpl();
}
// 具体实现类A
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("Concrete Implementor A: operationImpl");
}
}
// 具体实现类B
public class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println("Concrete Implementor B: operationImpl");
}
}
// 抽象化角色
public abstract class Abstraction {
protected Implementor implementor;
public Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
protected void operationImpl() {
implementor.operationImpl();
}
}
// 修正抽象化角色
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
// 调用抽象化角色的方法
operationImpl();
// 添加额外的操作
System.out.println("Refined Abstraction: additional operation");
}
}
6. 组合模式
6.1 简介
它将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。它模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
6.2 使用场景
组合模式可以用来表示文件系统的层次结构,使得客户端可以以统一的方式操作文件和文件夹,例如创建、删除、移动等。
组合模式可以用于表示组织机构的层次结构,例如公司的组织结构可以以树形结构来表示,根节点表示公司,子节点表示部门,叶子节点表示员工。
6.3 代码示例
// 抽象组件接口
public interface Component {
void operation(); // 定义一个公共的操作方法
void add(Component component); // 添加子组件
void remove(Component component); // 移除子组件
}
// 叶子节点类(员工)
public class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("员工 " + name + " 执行操作");
}
// 叶子节点没有子节点,所以add和remove方法不需要实现具体功能
@Override
public void add(Component component) {
throw new UnsupportedOperationException("叶子节点不能添加子节点");
}
@Override
public void remove(Component component) {
throw new UnsupportedOperationException("叶子节点不能移除子节点");
}
}
// 组合类(部门)
public class Composite implements Component {
private List<Component> children = new ArrayList<>();
private String name;
public Composite(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("部门 " + name + " 执行操作");
for (Component child : children) {
child.operation(); // 递归调用子组件的操作方法
}
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
}
7. 享元模式
7.1 简介
运用共享技术来有效地支持大量细粒度的对象
7.2 使用场景
在一些系统中,可能会存在大量的共享对象,例如数据库连接池、线程池等。这些对象可以通过享元模式来实现共享,减少对象的创建和销毁,提高系统的性能和可扩展性。
在一些需要处理大量数据的系统中,可能会存在大量的重复对象,例如图像处理中的像素点、文本处理中的单词等。这些对象可以通过享元模式来共享,减少对象的创建和内存消耗,提高系统的性能和可扩展性。
在一些高并发的系统中,可能会存在大量的请求,例如电商网站的购物车、在线游戏的排行榜等。这些对象可以通过享元模式来共享,减少对象的创建和销毁,提高系统的并发处理能力和响应速度。
在分布式系统中,可能会存在大量的对象需要在不同的节点之间共享,例如分布式缓存系统、分布式锁等。
7.3 代码示例
// 抽象享元角色
public interface Flyweight {
void operation(UnsharedConcreteFlyweight state);
}
// 内部状态(内蕴状态)
public class UnsharedConcreteFlyweight {
// 这里可以定义一些需要由客户端来保持的外部状态
// ...
}
// 具体享元角色
public class ConcreteFlyweight implements Flyweight {
// 这里可以定义一些可以共享的内部状态
// ...
@Override
public void operation(UnsharedConcreteFlyweight state) {
// 在这里使用内部状态和外部状态进行操作
// ...
}
}
// 享元工厂角色
public class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = flyweights.get(key);
if (flyweight == null) {
flyweight = new ConcreteFlyweight();
flyweights.put(key, flyweight);
}
return flyweight;
}
}
以上是结构型模式中的设计模式简介与应用场景,下一篇将继续介绍行为型模式下的设计模式的应用场景以及代码示例展示。