设计模式结构型(代理模式桥接模式装饰者模式适配器模式)
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式结构型(代理模式桥接模式装饰者模式适配器模式)相关的知识,希望对你有一定的参考价值。
好文推荐:
作者:ByteStefan
代理模式
在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能(非功能性需求,例如:监控、统计、事务、日志等)。
静态代理(原始类可维护-可实现统一接口)
public interface IDownload
void startDownload();
void stopDownload();
public class DownloadImpl implements IDownload
void startDownload()
//....
void stopDownload()
//....
public class DownloadProxy implements IDownload
private MonitorLog mLog;
private DownloadImpl mDownloadController;
public DownloadLog(DownloadImpl downloadController)
mLog = new MonitorLog();
mDownloadController = downloadController;
public void startDownload()
mLog.info(TAG, "xxxx");
mDownloadController.startDownload();
public void stopDownload()
mLog.info(TAG, "xxxxx");
mDownloadController.stopDownload();
public class RunClass
public static void main(String[] args)
IDownload downloadController = new DownloadProxy(new DownloadImpl);
download.startDownload();
静态代理(原始类不可维护-不可实现统一接口)
//该类为第三方库的类
public class DownloadManager implements IDownload
void startDownload()
....
void stopDownload()
....
public class DownloadProxy extends DownloadManager
private MonitorLog mLog;
public DownloadLog()
mLog = new MonitorLog();
public void startDownload()
mLog.info(TAG, "xxxx");
super.startDownload();
public void stopDownload()
mLog.info(TAG, "xxxxx");
super.stopDownload();
public class RunClass
public static void main(String[] args)
DownloadManager downloadController = new DownloadProxy();
download.startDownload();
动态代理
不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
实例场景:接口的缓存功能。可以提供一个缓存注解,对于接口调用都是通过动态代理类,动态代理类中解析是否有缓存注解,如果有则优先返回缓存内容,没有则返回实时内容。
public class DynamicProxy
private MonitorLog mLog;
public DynamicProxy()
mLog = new MonitorLog();
public Object createProxy(Object proxiedObject)
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
private class DynamicProxyHandler implements InvocationHandler
private Object proxiedObject;
public DynamicProxyHandler(Object proxiedObject)
this.proxiedObject = proxiedObject;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
mLog.info(TAG, "当前调用方法名 = " + method.getName());
Object result = method.invoke(proxiedObject, args);
return result;
public class RunClass
public static void main(String[] args)
DynamicProxy proxy = new DynamicProxy();
IDownload downloadController = (IDownload) proxy.createProxy(new DownloadManager());
downloadController.startDownload();
桥接模式
将抽象和实现解耦,让它们可以独立变化。另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
// mysql中实现JDBC驱动协议
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver
static
try
java.sql.DriverManager.registerDriver(new Driver());
catch (SQLException E)
throw new RuntimeException("Can't register driver!");
/**
* Construct a new driver and register it with DriverManager
* @throws SQLException if a database error occurs.
*/
public Driver() throws SQLException
// Required for Class.forName().newInstance()
//通过Class.forName 加载 MySql 驱动,并执行了 static 代码块,注册 MySql 驱动器
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next())
rs.getString(1);
rs.getInt(2);
举例:现在有两个纬度 Car 车 (奔驰、宝马、奥迪等) Transmission 档位类型 (自动挡、手动挡、手自一体等) 按照继承的设计模式,Car是一个Abstract基类,假设有M个车品牌,N个档位一共要写M*N个类去描述所有车和档位的结合。而当我们使用桥接模式的话,我首先new一个具体的Car(如奔驰),再new一个具体的Transmission(比如自动档)。然后奔驰.set(手动档)就可以了。
那么这种模式只有M+N个类就可以描述所有类型,这就是M*N的继承类爆炸简化成了M+N组合。所以桥接模式解决的应该是继承爆炸问题。
装饰者模式
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
public abstract class InputStream
//...
public int read(byte b[]) throws IOException
return read(b, 0, b.length);
public int read(byte b[], int off, int len) throws IOException
//...
//...
public class BufferedInputStream extends InputStream
protected volatile InputStream in;
protected BufferedInputStream(InputStream in)
this.in = in;
//...实现基于缓存的读数据接口...
public int read(byte b[]) throws IOException
//实现缓存
return in.read(b, 0, b.length);
public class DataInputStream extends InputStream
protected volatile InputStream in;
protected DataInputStream(InputStream in)
this.in = in;
//...实现读取基本类型数据的接口
public class RunClass
public static void main(String[] args)
InputStream in = new FileInputStream("/user/test/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1)
//...
适配器模式
类适配器(基于继承)
public interface ITarget
void f1();
void f2();
void fc();
public class Adaptee
public void fa() //...
public void fb() //...
public void fc() //...
public class Adaptor extends Adaptee implements ITarget
public void f1()
super.fa();
public void f2()
//...重新实现f2()...
// 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
对象适配器:基于组合
public interface ITarget
void f1();
void f2();
void fc();
public class Adaptee
public void fa() //...
public void fb() //...
public void fc() //...
public class Adaptor implements ITarget
private Adaptee adaptee;
public Adaptor(Adaptee adaptee)
this.adaptee = adaptee;
public void f1()
adaptee.fa(); //委托给Adaptee
public void f2()
//...重新实现f2()...
public void fc()
adaptee.fc();
针对这两种实现方式,在实际的开发中,到底该如何选择使用哪一种呢?判断的标准主要有两个,一个是 Adaptee 接口的个数,另一个是 Adaptee 和 ITarget 的契合程度。
- 如果 Adaptee 接口并不多,那两种实现方式都可以。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
- 如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
应用场景
封装有缺陷的接口设计
假设我们依赖的外部系统在接口设计方面有缺陷(比如包含大量静态方法),引入之后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计。
public class CD //这个类来自外部sdk,我们无权修改它的代码
//...
public static void staticFunction1() //...
public void uglyNamingFunction2() //...
public void tooManyParamsFunction3(int paramA, int paramB, ...) //...
public void lowPerformanceFunction4() //...
// 使用适配器模式进行重构
public class ITarget
void function1();
void function2();
void fucntion3(ParamsWrapperDefinition paramsWrapper);
void function4();
//...
// 注意:适配器类的命名不一定非得末尾带Adaptor
public class CDAdaptor extends CD implements ITarget
//...
public void function1()
super.staticFunction1();
public void function2()
super.uglyNamingFucntion2();
public void function3(ParamsWrapperDefinition paramsWrapper)
super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
public void function4()
//...reimplement it...
统一多个类的接口设计
某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑。
/**
* 集成多个敏感词系统,提供统一的对外接口
*/
public class ASensitiveWordsFilter // A敏感词过滤系统提供的接口
//text是原始文本,函数输出用***替换敏感词之后的文本
public String filterSexyWords(String text)
// ...
public String filterPoliticalWords(String text)
// ...
public class BSensitiveWordsFilter // B敏感词过滤系统提供的接口
public String filter(String text)
//...
public class CSensitiveWordsFilter // C敏感词过滤系统提供的接口
public String filter(String text, String mask)
//...
// 未使用适配器模式之前的代码:代码的可测试性、扩展性不好
public class RiskManagement
private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
public String filterSensitiveWords(String text)
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterPoliticalWords(maskedText);
maskedText = bFilter.filter(maskedText);
maskedText = cFilter.filter(maskedText, "***");
return maskedText;
// 使用适配器模式进行改造
public interface ISensitiveWordsFilter // 统一接口定义
String filter(String text);
public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter
private ASensitiveWordsFilter aFilter;
public String filter(String text)
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterPoliticalWords(maskedText);
return maskedText;
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...
// 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统,
// 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。
public class RiskManagement
private List<ISensitiveWordsFilter> filters = new ArrayList<>();
public void addSensitiveWordsFilter(ISensitiveWordsFilter filter)
filters.add(filter);
public String filterSensitiveWords(String text)
String maskedText = text;
for (ISensitiveWordsFilter filter : filters)
maskedText = filter.filter(maskedText);
return maskedText;
替换依赖的外部系统
把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。
// 外部系统A
public interface IA
//...
void fa();
public class A implements IA
//...
public void fa() //...
// 在我们的项目中,外部系统A的使用示例
public class Demo
private IA a;
public Demo(IA a)
this.a = a;
//...
Demo d = new Demo(new A());
// 将外部系统A替换成外部系统B
public class BAdaptor implemnts IA
private B b;
public BAdaptor(B b)
this.b= b;
public void fa()
//...
b.fb();
// 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,
// 只需要将BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));
兼容老版本接口
在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部实现逻辑委托为新的接口实现。这样做的好处是,让使用它的项目有个过渡期,而不是强制进行代码修改。这也可以粗略地看作适配器模式的一个应用场景。
//JDK2.0 Collections 兼容 JDK1.0 Enumeration
public class Collections
public static Emueration emumeration(final Collection c)
return new Enumeration()
Iterator i = c.iterator();
public boolean hasMoreElments()
return i.hashNext();
public Object nextElement()
return i.next():
适配不同格式的数据
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
4中设计模式区别
代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
以上是关于设计模式结构型(代理模式桥接模式装饰者模式适配器模式)的主要内容,如果未能解决你的问题,请参考以下文章