本文主要是介绍设计模式之结构性模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1 简介
在前面的博客中,自己分别为GOF23总结了设计模式之创建者模式和行为型模式,借着刚阅读完Java设计模式及实践,对辛格的阐述进行总结,期待可以增进对设计模式的理解。本文主要是对于辛格Java设计模式及实践中第四章结构性模式进行总结。
1.1 设计模式七大原则
其中依赖倒置原则(Dependence Inversion Principle)的范例如下:
母亲类有讲故事的方法TellStory,依赖一个Book类输入,并使用了Book类的getContent方法,以便讲故事。那么下次母亲类要讲报纸上的故事、手机上的故事原有接口无能为力。这时可以抽象一个包含getContent方法的IReader接口,Book,NewsPaper、CellPhone都实现该抽象接口,母亲类方法接受一个Ireader,并调用getContent方法即可。上述的例子的阐述较好。
参见设计模式七大原则总结
1.2 结构性模式的特征
结构性模式(Structural Pattern)利用对象和类之间的关系来创建复杂结构,大多数结构性模式都基于继承。可分为两种
- 类结构模式:关心类的组合,由多个类组合成一个更大的系统,在类结构模式中一般只存在于继承关系和实现关系。
- 对象结构性模式:关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。更符合“合成复用原则”
2 结构性模式类型
2.1 简述
2.2 逐个阐述
2.2.1 适配器模式
适配器提供了一种代码重用的方案,即将已有的代码适配或者包装到一些新接口中,而这些接口是在最初设计的时候没有考虑到的。
适配器的实现方式在其他语言中通常使用多继承的方式,也可以使用聚合法。
适配器模式的实现分为三类:
- 类适配器类型
Adapter继承Adaptee,实现Target接口(因为没有多继承,Target必须是接口) - 对象适配器类型
- 缺省适配器模式,也叫做默认适配器模式、接口适配器模式。
2.2.1.1 UML
上述图对象适配器模式(Object adapter pattern),与类适配器模式不同,Adapter只实现了Target的接口,没有继承Adaptee,而是使用聚合的方式引用adptee。
2.2.1.2 参与者
- Target
定义Client使用的与特定领域相关的接口,即支持的新接口 - Client
与符合Target接口的对象协同。 - Adaptee
定义一个已经存在的接口,这个接口需要适配。 - Adapter
对Adaptee的接口与Target接口进行适配
2.2.1.3 实践
参见 适配器模式
参见 GOF设计模式之适配器模式的理解
适配器把一个类的接口变换为客户端所期待的另一种接口,而使原来因接口原因不匹配而无法一起工作的两个类能够一起工作。
2.2.2 代理模式
代理模式的目的是为一个真实的对象提供一个代理对象。
适配器模式和代理模式之间的主要区别在于代理模式提供了完全相同的接口。装饰器模式增强了接口,而适配器更改了接口。
2.2.2.1 UML
上图为静态代理的UML。
在上图中,存在两个对象,一个是真实对象RealSubject,一个是ProxySubject代理对象,需要注意在代理对象中不存在业务逻辑,在建立句柄之后,会将业务请求委托给真实对象来处理,代理充当着中间人的角色。
在Java编程思想中p592页,则包含动态代理的阐述:
2.2.2.2 参与者
- Subject(共同接口):客户端使用的现有接口
- RealSubject(真实对象):真实对象的类
- ProxySubeject(代理对象):代理类。
2.2.2.3 实践
GOF23设计模式之动态代理模式之理解
GOF23设计模式之动态代理模式实践之经典
GOF23设计模式之静态代理模式实现之经典
GOF23 设计模式之静态代理模式理解之经典
2.2.3 装饰器模式
有时,在现有代码中添加或删除一些功能,同时对现有的代码结构不会造成影响,而这些添加或删除的功能又不足以做成一个子类。这种情况下装饰器模式就会排上用场。
装饰器模式聚合了它将要装饰的原有对象,实现了与原有对象相同的接口,代理微弱原有对象的所有公共接口调用,并且在子类中实现新增的功能,从而达到了上述的目的。
2.2.3.1 UML
2.2.3.2 参与者
- Componeent:抽象组件,可以是一个接口
- ComponentImplementation:想要装饰的组件之一。
- Decorator:这是一个抽象的组件装饰器。
- ExtendedComponent:这是添加额外功能的组件装饰器。
2.2.3.3 实践
package gof.structural.decorator;import java.util.stream.Collectors;
// 抽象的组件
interface PrintText {public void print(String text);
}
// printASCIIText是要装饰的组件,它只知道打印ASCII文本
class PrintAsciiText implements PrintText {public void print(String text) {System.out.println("Print ASCII: " + text);}
}
// printASCIIText是要装饰的组件,增强十六进制打印。使用了之前就有的功能
class PrintTextHexDecorator implements PrintText {private PrintText inner;public PrintTextHexDecorator(PrintText inner) {this.inner = inner;}public void print(String text) {String hex = text.chars().boxed().map(x -> "0x" + Integer.toHexString(x)).collect(Collectors.joining(" "));
// 使用了之前就有的功能 inner.print(text + " -> HEX: " + hex);}
}public class Main
{public static void main (String[] args) throws java.lang.Exception{final String text = "text";final PrintText object = new PrintAsciiText();final PrintText printer = new PrintTextHexDecorator(object);object.print(text);printer.print(text);}
}
printTextHexDecorator是装饰器,它也可以应用于其他PrintText组件。假设需要一个PrintToUpperText,仍然可以使用现有的装饰器使其打印十六进制。
参见 GOF23设计模式之装饰模式之理解
装饰模式与桥接模式都是为了解决多子类对象问题,但是他们的诱因是不同的。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。而装饰器则是为了增加新的功能。
2.2.4 桥接模式
桥接模式的目标是将抽象与实现解耦,使得二者可以独立地变化。它通过在公共接口和视线中使用继承来达到目的。在处理跨平台开发时是非常常见的。
桥接模式的实现核心要点如下:
- 处理多继承结构,处理多个维度变化的场景。
- 将多个维度设计成独立变化的继承体系,使各个维度可以独立的进行扩展。
- 在抽象层进行关联。
2.2.4.1 UML
2.2.4.2 参与者
- Abstraction:抽象类
- Implementation:抽象的实现类
- Refined:扩充的抽象类
- SpecificImplementation:具体实现类。
2.2.4.3 实践
package gof.structural.bridge;
// 平台继承体系
interface PlatformBridge {public void forwardMessage(String msg);
}class WindowsImplementation implements PlatformBridge {public void forwardMessage(String msg) {System.out.printf("Sending message \n%s \nFrom the windows machine", msg);}
}class PosixImplementation implements PlatformBridge {public void forwardMessage(String msg) {System.out.printf("Sending message \n%s \nFrom the linux machine", msg);}
}// 电子邮件客户端
class MessageSender {private PlatformBridge implementation;public MessageSender(PlatformBridge implementation) {this.implementation = implementation;}public void sendMessage(String from, String to, String body) {implementation.forwardMessage(String.format("From : %s \nTo : %s \nBody : %s", from, to, body));}
}class MyMessageClient extends MessageSender {private String to = "development_all@abc.com";public MyMessageClient(PlatformBridge implementation) {super(implementation);}public void sendMessageToAll(String from, String body) {sendMessage(from, to, body);}
}public class Main
{public static void main (String[] args){new MyMessageClient(new WindowsImplementation()).sendMessageToAll("abc@gmail.com", "Test");}
}
参见 GOF设计模式之桥接模式的实现
2.2.5 组合模式
组合模式是把一组对象组合为一个复杂的单一整体。JVM提供了组合模式的最佳示例,通常是利用堆栈的原理。各种操作从当前线程中推入和弹出。
使用模式的场景是:把部分和整体的关系使用树形结构来表示,从而使客户端可以使用统一的方式来处理部分对象和整体对象。组合模式与解释器模式很相似。因为解释器模式使用组合模式来定义对象结构的内部表示。解释器模式的UML图如下所示:
2.2.5.1 UML
2.2.5.2 参与者
- Component
为组合中的对象声明接口。
在适当的情况下,实现所有类共有接口的缺省行为。
声明一个接口用于访问和管理Component的子组件。
(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。 - Leaf
在组合中表示叶节点对象,叶节点没有子节点。
在组合中定义节点对象的行为。 - Composite
定义有子部件的那些部件的行为。
存储子部件。
在Component接口中实现与子部件有关的操作。 - Client
通过Component接口操纵组合部件的对象。
2.2.5.3 实践
package gof.structural.composite;
// 组合接口
interface ArithmethicComposite {public int getValue();
}
// 叶子节点
class NumericValue implements ArithmethicComposite {private int value;public NumericValue(int value) {this.value = value;}public int getValue() {return value;}
}// 复合节点
abstract class ArithmethicOperand implements ArithmethicComposite {protected ArithmethicComposite left;protected ArithmethicComposite right;public ArithmethicOperand(ArithmethicComposite left, ArithmethicComposite right) {this.left = left;this.right = right;}
}class PlusOperand extends ArithmethicOperand {public PlusOperand(ArithmethicComposite left, ArithmethicComposite right) {super(left, right);}public int getValue() {return left.getValue() + right.getValue();}
}class MinusOperand extends ArithmethicOperand {public MinusOperand(ArithmethicComposite left, ArithmethicComposite right) {super(left, right);}public int getValue() {return left.getValue() - right.getValue();}
}// 复合节点 逆波兰式计算。
public class Main
{public static void main (String[] args) throws java.lang.Exception{ArithmethicComposite expr = new MinusOperand(new PlusOperand(new NumericValue(1), new NumericValue(4)),new NumericValue(2));System.out.printf("Value equals %d\n", expr.getValue());}
}
另外参见GOF23设计模式之组合模式的理解
在阅读本书时,曾在组合模式结尾摘抄过如下的内容:
“慎勿放逸:放逸就是不去约束自己的行为,放纵自己为所欲为会给自己带来无穷无尽的烦恼和痛苦,其实很多祸害都不是你去做了一件事,而是因为你过度去做一件事。”因此知足常足,终身不辱,知止常止,终身不耻;如果能在该放行的时候放行,该止的时候止,不放纵自己的欲望,懂得控制自己的情绪,这同样也是一件积福积德的事。
2.2.6 外观模式
许多复杂的系统可以简化为几个子系统暴露的用例接口,这样可以让客户端代码不需要知道子系统的内部结构与联系。即客户端代码和复杂的子系统解耦,并且能让开发人员更简单的使用子系统,这被称为外观模式Façade,外观模式隐藏了子系统的复杂内部结构,只向外部提供可访问的通用接口。
目的是为复杂的子系统提供单一的统一接口。通过为最重要的用例提供接口,能够简化大型和复杂系统的使用。
2.2.6.1 UML
2.2.6.2 参与者
- Client:子系统客户端代码
- Façade:子系统接口
- Subsystem:子系统定义的类。
2.2.6.3 实践
GOF23设计模式之外观模式之实现
2.2.7 享元模式
对象池(创建者模式)模式与享元模式(结构模式)的区别在于:对象池模式是保存可变域的容器,二享元模式是不可变域对象。由于是不可变的,因此它们的状态是在创建时设置的,并且在每个方法调用时从外部给出外部状态。
抽象享元类通常是一个接口或者抽象类,声明公共方法,这些方法可以向外界提供内部状态,设置外部状态。围棋类棋子使用享元模式。
2.2.7.1 UML类图
2.2.7.2 参与者
- Flyweight
描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。 - ConcreteFlyweight
实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。
ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景。 - UnsharedConcreteFlyweight
并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。
在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。 - FlyweightFactory
创建并管理flyweight对象。
确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。
2.2.7.3 实践
参见 GOF23 设计模式之享元模式的实现
3 总结
结构型设计模式的主要内容就如上所示。代理模式、适配器模式、组合模式、装饰器模式在日常工作中较为经常使用。若想要用的纯熟,还是需要在日常工作编程中多多实践、揣摩反思这些用法的意义。
最后,附上完整的脑图:
2019-12-22 14:50于马塍路36号
这篇关于设计模式之结构性模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!