软件架构《设计模式二》

Posted 生活黑客35

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件架构《设计模式二》相关的知识,希望对你有一定的参考价值。

 创建型模式解决创建问题,当对象或模块创建完成之后,就需要一种设计方案来简化它们之间的关系。本文主要整理自己熟悉的代理模式,桥接模式,适配器模式,装饰模式,门面模式,组合模式,享元模式整理分享。

代理模式

所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。



场景问题:A 对象需要对 B 对象中的 method 进行功能扩展,这个时候的B对象的 method 方法已经在系统中广泛使用,这个时候应该如何处理A和B的关系?

解决方案:这里有关前提条件就是不对B对象进行任何改变!

  1. 静态代理

  针对B类创建一个代理类C,A 使用 C 对象完成扩展。可以解决问题但是产生一个新的问题就是如果其他对象也需要对 B 对象中的 method 进行扩展,如果都采用静态代理的方式就会产生代码的冗余,从而增加维护的成本。就需要动态代理的方式来解决这一问题。在 JAVA 中可以使用 实现统一一个接口或继承的方式编码。

  1. 动态代理

  不事先为 B类创建代理类,而是在运行的时候动态创建B类的代理类,然后再系统中用代理类替换掉原始类。Spring AOP 的底层实现原理就是基于动态代理的方式。

源码:org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator

/**
 * Create an AOP proxy for the given bean. 为指定bean 创建代理类。
 * @return the AOP proxy for the bean
 */

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
                             @Nullable Object[] specificInterceptorsTargetSource targetSource
{
    // 省略具体实现(有兴趣的可以自己查看) ......
}

桥接模式

桥接模式是软件设计模式中最复杂的模式之一,它把事物对象和其具体行为、具体特征分离开来,使它们可以各自独立的变化。也可以理解为抽象和实现的解耦。



场景问题:如果你现在需要开发一个中间件需要兼容市面已有的主流的消息框架或持久化框架,你该怎么设计这个中间件呢?

解决方案:参考Spring-jdbc模块的应用案例,可以把整个 Spring-jdbc 理解为一个整体 “抽象”,不同的具体实现如 com.mysql.jdbc.Driver 或 oracle.jdbc.driver.OracleDriver 。以上三种各自开发,互不干扰。他们之间通过组合的原则将对象都委托给 java.sql.DriverManager 来执行。概念理解比较绕,代码相对简单理解。下面就是 Spring-Jdbc模块实现的代码一看便知。

源码:org.springframework.jdbc.datasource.DriverManagerDataSource。

/**
 * 初始化指定驱动类并注册到jdbc 的管理类中。
 * Set the JDBC driver class name. This driver will get initialized。
 * on startup, registering itself with the JDK's DriverManager.
 * @see java.sql.DriverManager#registerDriver(java.sql.Driver)
 */

public void setDriverClassName(String driverClassName) {
    Assert.hasText(driverClassName, "Property 'driverClassName' must not be empty");
    String driverClassNameToUse = driverClassName.trim();
    try {
        Class.forName(driverClassNameToUse, true, ClassUtils.getDefaultClassLoader());
    }
    catch (ClassNotFoundException ex) {
        throw new IllegalStateException("Could not load JDBC driver class [" + driverClassNameToUse + "]", ex);
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Loaded JDBC driver: " + driverClassNameToUse);
    }
}



/**
 * Registers the given driver with the {@code DriverManager}
 * @since 1.8
 */

public static synchronized void registerDriver(java.sql.Driver driver,
                                               DriverAction da)

        throws SQLException 
{
    /* Register the driver if it has not already been added to our list */
}


 

适配器模式

Adapter pattern 有时候也称包装样式或者包装 Wrapper。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中。



场景问题:假如说现有系统中存在以下几个的问题,我们应该怎么办?

  1. 接口设计有缺陷需要重新封装。

  2. 新业务的接口要依赖多个接口,或者对多个接口功能进行整合。

  3. 将系统系统与外包系统隔离。

  4. 兼容历史版本功能。

  5. 处理不同的数据格式。

解决方案:

  1. 对象适配器。

  在这种适配器模式中,适配器容纳一个它包裹的类的实例。也就是使用组合的方式进行适配。

  1. 类适配器。

  这种适配器模式下,适配器继承自己实现的类(一般多重继承)。     

在解决上述问题的时候需要针对具体问题选择适合的适配方案。适配器的方式更像是补救设计上的坑。

装饰模式

一种动态地往一个类中添加新的行为。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。



场景问题:A对象需要对B对象进行扩展,新增功能行为。为了更为灵活就不可以使用继承的方式实现。还需要保持对B对象现有功能的使用。

解决方案:可以将B对象作为A对象的构造参数使用。Java IO 类库是一个很好的学习案例。有兴趣的可以自己看一下。下面用一个例子说明一下 ,对普通的文件读取增加缓冲区实现方式。

 
   
   
 
//文件读取
InputStream in = new FileInputStream(path);
//缓冲区方式
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[2048];
while (bin.read(data) != -1) {
    //...
}

假设BufferedInputStream使用继承方式直接继承FileInputStream会产生怎样的问题呢?你可以这样做,如果InputStream下的子类都需要支持缓冲区,那就悲催了!!!。还有一个问题就是 InputStream 的子类都是为不同目的而产生的,如果需要对子类做其他方面的增强,岂不是又要产生一个新的子类!会导致类爆炸,很难想象该如何维护这样的代码。

使用装饰模式,还是遵循了组合优于继承的原则,InputStream 作为一个抽象类,里面有很多默认实现的方法。Java 中 InputStream  采用构造参数的方式与 FilterInputStream 组合使用。带有缓冲功能的 BufferedInputStream 将基础功能委托给InputStream 实现,自己关注与缓冲相关功能扩展。到这里想分享一下王铮在极客时间《设计模式之美》中提到的以上四种设计模式的区别,简单容易理解。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

门面模式

它为子系统中的一组界面提供一个统一的高层界面,使得子系统更容易使用。

场景问题:如何提高接口(广义范围上)易用性。

解决方案:将复杂的实现隐藏起来,只暴露接口(有点接口隔离的意思~)。在定义中的子系统可以理解整个系统,模块甚至更细粒度的web接口。

组合模式(低频)

将一组对象组织(Compose)成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(使用者)可以统一单个对象和组合对象的处理逻辑。省略场景问题,解决方案。

Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly.

享元模式(低频)

它使用物件用来尽可能减少内存使用量;于相似物件中分享尽可能多的资讯。当大量物件近乎重复方式存在,因而使用大量内存时,此法适用。通常物件中的部分状态(state)能够共享。常见做法是把它们放在数据结构外部,当需要使用时再将它们传递给享元。省略场景问题,解决方案。

享元模式 VS 单例、缓存、对象池

应用单例模式是为了保证对象全局唯一。应用享元模式是为了实现对象复用,节省内存。缓存是为了提高访问效率,而非复用。池化技术中的“复用”理解为“重复使用”,主要是为了节省时间。

结束语

    设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。



内容迁移至InfoQ写作平台。


以上是关于软件架构《设计模式二》的主要内容,如果未能解决你的问题,请参考以下文章

软件架构设计常用方法-软件架构设计学习第五天(非原创) 发布成功,点击查看文章

项目管理之信息系统开发基础(二架构设计)

项目管理之信息系统开发基础(二架构设计)

软件需求分架构设计与建模最佳实践

SOA架构与微服务的区别异同

009_计算器界面代码重构