结构型模式上
1、结构型模式
结构型模式描述如何将类或者对象按照某种布局组成更大的结构,就像搭积木,可以通过简单的积木的组合成为复杂功能强大的结构。它分为类结构模式和对象结构模式。前者采用继承机制来组织接口和类,后者采用组合和聚合在组合对象。由于组合或者聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型模型分成七种:代理模式,适配器模式、桥接模式、装饰模式、外观模式、享元模式、组合模式。
这七种结构型模式中,除了适配器模式分为结构型模式和对象结构型模式两种。其他六种都属于对象结构型模式。
2、代理模式
在某些情况下,一个客户不能直接访问另一个对象,只是需要一个找一个中介帮忙完成某一个任务。代理模式在客户端与代理目标对象之间起到了一个中介作用和保护目标对象的作用。代理对象可以扩展目标对象的功能,代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
2.1、代理模式的结构与实现
代理模式的主要角色如下:
抽象主题类subject类:通过接口或者抽象类声明真实主题或者代理对象实现的业务方法,
真实主题类Real subject:实现了抽象主题中的具体业务,是代理对象多代表的真实对象。
代理类Proxy类:提供了与真实主题相同的接口,其内部含有对真是对象的引用,它可以访问控制或者拓展真实主题的功能。

实现代码如下:
//抽象主题 interface AbstractSubject { void request(); } //真实主题 class RealSubject implements AbstractSubject { public void request() { System.out.println("访问真实主题方法..."); } } //代理 class Proxy implements AbstractSubject { private RealSubject realSubject; public void request() { if (realSubject==null) { realSubject=new RealSubject(); } preRequest(); realSubject.request(); postRequest(); } private void preRequest() { System.out.println("访问真实主题之前的预处理。"); } private void postRequest() { System.out.println("访问真实主题之后的后续处理。"); } } public class TestProxyPattern { public static void main(String[] args) { Proxy proxy = new Proxy(); proxy.request(); } }
2.2.代理模式应用场景
远程代理、虚拟代理
2.3.代理模式的扩展
代理用以控制对对象的访问,本质上是对其功能提供某种形式的增强。按实现又可分为静态代理和动态代理。上述的静态代理模式中代理类包含了对真实主题的引用,这就存在问题了:真实主题和代理主题一一对应,增加真实主题也要增加代理主题,这样一旦设计代理以前真实主题必须事先存在才能设置代理主题,很死板。这时候引进了动态代理,动态代理因其代理逻辑和业务逻辑相分离的特点,具有良好的适用性和可扩展性,是Spring中AOP的底层实现。
静态代理和动态代理的不同之处在于:
- 静态代理编译期生成代理类;动态代理运行期生成代理类。
- 静态代理和被代理类及其业务逻辑耦合,适用性较差且代理逻辑难以扩展;动态代理可以在不知道被代理类的前提下编写代理逻辑,运行时才决定被代理对象,适用性好且代理逻辑易于扩展。
动态结构图如下图:
实现代码:
//抽象主题 interface AbstractSubject1 { void request(); } //真实主题 class RealSubject1 implements AbstractSubject1 { public void request() { System.out.println("访问真实主题方法..."); } } class DynamicProxy implements InvocationHandler { private Object object; public DynamicProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(object, args); after(); return result; } private void before() { System.out.println("hello!"); } private void after() { System.out.println("bye!"); } } public class TestDynamicProxyPattern { public static void main(String[] args) { AbstractSubject1 abstractSubject1 = new RealSubject1(); DynamicProxy dynamicProxy = new DynamicProxy(abstractSubject1); AbstractSubject1 abstractProxy = (AbstractSubject1) Proxy.newProxyInstance(abstractSubject1.getClass().getClassLoader(), abstractSubject1.getClass().getInterfaces() , dynamicProxy); abstractProxy.request(); } }
动态代理只需要传入需要被代理类的对象,然后调用Proxy类的工厂方法newProxyInstance去动态地创建一个代理类,最后调用代理类的方法便实现了“增强功能”。Proxy.newProxyInstance方法需要传入的参数有:
参数1:ClassLoader
参数2:该实现类的所有接口
参数3:动态代理对象
最后还要进行一次强制类型转换。
使用了动态代理之后,无论有多少类多少方法需要增加逻辑,只需要在使用的时候将类对象传入得到代理对象,然后使用代理对象调用需要增强的方法即可。
但是到这里就会发现一个问题,上述动态动态代理只能代理接口类,有接口才能工作,那么这个时候如果没有接口需要代理该怎么办?
引入CGlib代理!
CGLIB(Code Generation Library)是一个强大的高性能Code生成类库。 它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
关于CGlib代理介绍会再写一篇。
3、适配器模式
现实生活中,经常会出现两个对象因为接口不兼容而不能在一起工作的实例,比如用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。这时候需要第三者进行适配。
在软件设计中,需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但是他们与当前系统的接口规范不兼容,如果重新开发这些组件成本很高,这时用适配器模式能很好解决这些问题。适配器分为类结构模式和对象结构型模式两种,前者类之间的耦合度比后者高,应用较少。
3、1模式的定义与特点
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且要求开发了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
该模式的主要优点如下。客户端通过适配器可以透明地调用目标接口。复用了现存的类,不需要修改原有代码而重用现有的适配者类。将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
缺点是:对类适配器来说,更换适配器的实现过程比较复杂。
3.2 模式的结构
Java中不支持多继承,定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
模式的结构:
目标接口(Target):当前系统业务所期待的接口,它可以使抽象类或者接口
适配者类(Adaptee):它是被访问和适配的现存组件库中组件接口
适配器类(Adapter):它只是一个转换器,通过继承或者引用适配者的对象,把适配者接口转换成目标接口,让客户按照目标接口的格式访问适配者。
下图为对象适配器模式的结构图
实现代码如下:
//目标接口 interface Target{ public void request(); } //适配者接口 class Adaptee{ public void specificRequest(){ System.out.println("适配器中的业务代码被调用"); } } //对象适配器类 class ObjectAdapter implements Target{ private Adaptee adaptee; public ObjectAdapter(Adaptee adaptee){ this.adaptee = adaptee; } @Override public void request() { adaptee.specificRequest(); } } public class TestAdapterObjectPattern { public static void main(String[] args) { System.out.println("对象适配器模式:"); Adaptee adaptee = new Adaptee(); Target target = new ObjectAdapter(adaptee); target.request(); } }
下图是类适配器模式的结构图:
实现代码:
//类适配器类 class ClassAdapter extends Adaptee implements Target{ @Override public void request() { specificRequest(); } } public class TestAdapterClassPattern { public static void main(String[] args) { System.out.println("类适配器模式测试"); Target target = new ClassAdapter(); target.request(); } }
问题:应该使用类继承适配模式还是对象适配器模式?
从上述代码中可以看到 类适配器的重点在于类ClassAdapter,是通过构造一个继承Adaptee类来实现适配器的功能,继承Adaptee (被适配类),同时实现Target 接口来实现多继承功能; 对象适配器的重点在于对象类ObjectAdapter,是通过在直接包含Adaptee类来实现的,当需要调用特殊功能的时候直接使用Adapter中包含的那个Adaptee对象来调用特殊功能的方法即可。
对象的适配是因为无法知道适配者接口Adaptee的细节,无法从Adaptee继承,只能引用一个Adaptee的实例。反之如果知道Adaptee的细节,就可以从Adaptee继承,实现类的适配器。
但是,适配器应该少从继承的角度出发, 本着尽量使用组合而不使用继承的原则,继承之后对父类进行了适配的工作还是改变了类的实现细节。 使用组合可以最大限度的减少对adaptee的依赖。因此,建议在能使用对象适配器的时候尽量不要使用类继承适配器的方式。
下一个问题来了:适配器模式和代理模式有什么区别?
设计模式总是通过增加层来进行解耦合,提高扩展性,适配器模式和代理模式这两种模式都是增加了一层,但是这一层有什么区别?
适配器模式是因为新旧接口不一致导致出现了客户端无法调用的问题,但是旧的接口在不能被完全重构掉的情况下(有可能会影响其他调用),仍然想要使用实现了这个接口的一些服务。那么为了使用以前实现旧接口的服务,我们就应该把新的接口转换成旧接口;实现这个转换的类就是抽象意义的转换器比如ClassAdapter。
相比于适配器模式的应用场景,代理模式就不一样了,虽然代理也同样是增加了一层,但是,代理提供的接口和原本的接口是一样的,代理模式的作用是不把实现直接暴露给client,而是通过代理这个层,代理能够在调用原本的接口前后做一些处理,详细见代理模式代码。
3.3 模式的应用场景
适配器模式(Adapter)通常适用于以下场景。
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。 比如在java中早期的枚举接口是Enumeration而后定义的枚举接口是Iterator;有很多旧的类实现了enumeration接口暴露出了一些服务,但是这些服务我们现在想通过传入Iterator接口而不是Enumeration接口来调用,这时就需要一个适配器,那么客户端就能用这个服务了
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
3.3 模式的扩展
适配器模式可以扩展成为双向适配器模式,双向适配器模式既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配器接口。结构图如下所示:
示例代码:
//目标接口 interface TwoWayTarget{ public void request(); } //适配者接口 interface TwoWayAdaptee { public void specificRequest(); } //目标实现 class TargetRealize implements TwoWayTarget { @Override public void request() { System.out.println("目标代码被调用"); } } //适配者实现 class AdapteeRealize implements TwoWayAdaptee { @Override public void specificRequest() { System.out.println("适配者代码被调用"); } } //双向适配器 class TwoWayAdapter implements TwoWayTarget,TwoWayAdaptee{ private TwoWayTarget target; private TwoWayAdaptee adaptee; public TwoWayAdapter(TwoWayTarget target) { this.target = target; } public TwoWayAdapter(TwoWayAdaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { adaptee.specificRequest(); } @Override public void specificRequest() { target.request(); } } public class TestTwoWayAdapterPattern { public static void main(String[] args) { System.out.println("目标通过双向适配器访问适配者:"); TwoWayAdaptee adaptee=new AdapteeRealize(); TwoWayTarget target=new TwoWayAdapter(adaptee); target.request(); System.out.println("--------------------------"); System.out.println("适配者通过双向适配器访问目标:"); target=new TargetRealize(); adaptee=new TwoWayAdapter(target); adaptee.specificRequest(); } }
4、桥接模式
现实生活中,某些类拥有两个甚至更多维度的变化,如图形既可以按照形状分,也可以按照颜色分。如何设计类似于photoshop这样的软件,能画出不同形状和不同颜色的图形?如果使用继承的方式的话,m种形状和n种颜色的图形有mxn种,不但子类多,而且扩展困难。这样的例子很多,比如支持不同平台和不同文件格式的媒体播放器。
4.1 模式的定义与特点
桥接模式的特点就是将抽象与实现分离,使他们独立变化,用组合关系代替继承来实现,降低抽象和实现这两个可变维度的耦合度
4.2 模式的结构与实现
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
模式的结构:
抽象化类:Abstraction角色,定义抽象类,并包含一个对实现化对象的引用
扩展抽象化角色(Refined Abstraction):抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
具体实现化角色(Concrete Implementor):给出实现化角色接口的具体实现
其结构图如下所示:
实现代码如下:
//实现化角色 interface Implementor{ public void operationImpl(); } //具体实现化角色A class ConcreteImplementA implements Implementor{ @Override public void operationImpl() { System.out.println("具体实现化角色A被访问"); } } //具体实现化角色B class ConcreteImplementB implements Implementor{ @Override public void operationImpl() { System.out.println("具体实现化角色B被访问"); } } //抽象化角色 abstract class Abstraction{ protected Implementor implementor; protected Abstraction(Implementor implementor){ this.implementor = implementor; } public abstract void operation(); } //扩展抽象化角色 class RefinedAbstraction extends Abstraction { protected RefinedAbstraction(Implementor implementor){ super(implementor); } @Override public void operation(){ System.out.println("扩展抽象化(Refined Abstraction)角色被访问"); implementor.operationImpl(); } } public class TestBridge { public static void main(String[] args) { Implementor impleA = new ConcreteImplementA(); Implementor impleB = new ConcreteImplementB(); Abstraction abstraction = new RefinedAbstraction(impleA); abstraction.operation(); System.out.println("------------------------------------"); abstraction = new RefinedAbstraction(impleB); abstraction.operation(); } }
4.3 桥接模式的应用场景
桥接模式通常适用于以下的场景:
当一个类存在两个独立的变化维度,且这个两个维度都需要进行扩展的时候
当一个系统不希望使用继承或因为多层次继承导致类的个数急剧增加时
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时
4.4 桥接模式的扩展
软件开发过程中,有的桥接模式可与适配器模式联合使用,当桥接模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来。其具体的结构如图所示:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术