【模块化那些事】 拆散的模块化

模块化原则倡导利用集中和分解等手法创建高内聚、低耦合的抽象。

mark

为了理解模块化的含义及其很重要的原因,来看看一本书的极端情况。假设一本书像讲一个长故事一样阐述其中的内容,中间没有任何停顿,也没有章节。试问面对这样的图书,读者将作何反应呢?我估计心中一定有千万只草泥马在崩腾吧。如果这本书根据内容分为不同的章节(模块)进行讲述,情况是不是就完全不一样了呢?同样,设计软件时,遵循模块化原则也很重要。需要指出的是模块化通常是一个系统级考虑因素,指的是如何将抽象组织成逻辑模块。但是我们这里的术语模块指的是类级抽象:具体类、抽象类和接口。模块化的目标是创建高内聚、低耦合的抽象。

应用模块化原则的实现手法

mark

  • 将相关的数据和方法集中在一起:每个抽象都必须是内聚的,即抽象应将相关的数据和操作它们的方法集中在一起
  • 将抽象分解为易于管理的规模:将大抽象分解为规模适中(既不太大也不太小)的小抽象。大类不仅难以理解,而且难以修改,因为这种类实现的职责可能交织在一起。
  • 创建非循环依赖:抽象之间不应该存在循环依赖。否则修改一个抽象可能引起连锁反应,波及整个设计。
  • 限制依赖关系数:创建扇入和扇出低的抽象。扇入指的是有多少个抽象依赖于当前的抽象,因此修改扇入高的抽象时,可能需要修改大量依赖于它的抽象。扇出指的是当前抽象依赖于多少个其它的抽象,高扇出意味着修改很多抽象时都可能影响当前抽象。为避免潜在的修改引发连锁反应,减少设计中抽象之间的依赖关系数很重要。

违反模块化原则导致的坏味

mark

我们这篇博客主要讲解分析拆散的模块化坏味,对于其它模块化坏味将在后面的博客讲解分析。

拆散的模块化

应集中放在一个抽象中的数据和方法分散在多个抽象中,将导致这种坏味。

常见表现形式如下:

  • 类被用作数据容器,其中没有任何操作这些数据的方法

  • 类的方法更多的被其它类成员调用

为什么不能有拆散的模块化?

如果抽象只包含数据成员,而操作这些数据成员的方法分散在多个抽象中时,原本应属于一个抽象的成员分散在多个抽象中时,将导致这些抽象之间紧密耦合。违反了模块化原则。

拆散的模块化潜在原因

以过程型思维使用面向对象语言

过程型语言倾向于将数据和操作它的函数分开,从而导致这种坏味。

不熟悉既有设计

大型的项目设计很复杂。在这样的项目中,每位开发人员通常只负责系统中很小的一部分,不了解设计的其它部分。这可能导致成员被放置到错误的类中。

示例分析

来看一个设备管理应用程序。在这个应用程序中,与设备相关的数据存储在DeviceData类中,而处理这些设备数据的方法由Device类提供。DeviceData类只有公共数据成员,没有任何方法。而Device类包含一个类型为DeviceData的对象,并提供了访问和操作该数据成员的方法。

mark

这些数据和方法原本应该集中放在一个类中,却分散在了Device和DeviceData类中,显然存在"拆散的模块化"坏味。

代码实现:

public class DeviceData
{
    /// <summary>
    /// 设备ID
    /// </summary>
    public string DeviceID { get; set; }
    /// <summary>
    /// 设备位置
    /// </summary>
    public string DevicePath { get; set; }
    /// <summary>
    /// 是否可用
    /// </summary>
    public bool Enabled { get; set; }
}
public class Device
{
    private DeviceData deviceData = new DeviceData();
    
    /// <summary>
    /// 获取设备ID
    /// </summary>
    /// <returns></returns>
    public string GetDeviceID()
    {
        return deviceData.DeviceID;
    }
    /// <summary>
    /// 设置设备ID
    /// </summary>
    /// <param name="deviceID">设备ID</param>
    /// <returns></returns>
    public bool SetDeviceID(string deviceID)
    {
        deviceData.DeviceID = deviceID;
        return true;
    }
    /// <summary>
    /// 是否可用
    /// </summary>
    /// <returns></returns>
    public bool IsEnabled()
    {
        return deviceData.Enabled;
    }
}

重构"拆散的模块化"

  • 如果一个方法更多地被另一个类(Target类)而不是定义它的类(Source类)调用,就采用“移动方法”,将这个方法从Source类移到Target类中。

  • 如果一个字段更多地被另一个类(Target类)而不是定义它的类(Source类)使用,就采用“移动字段”,将这个字段从Source类移到Target类中。

mark

mark

重构后的代码实现:

public class Device
{
    /// <summary>
    /// 设备ID
    /// </summary>
    private string DeviceID { get; set; }
    /// <summary>
    /// 设备位置
    /// </summary>
    private string DevicePath { get; set; }
    /// <summary>
    /// 是否可用
    /// </summary>
    private bool Enabled { get; set; }

    /// <summary>
    /// 获取设备ID
    /// </summary>
    /// <returns></returns>
    public string GetDeviceID()
    {
        return DeviceID;
    }
    /// <summary>
    /// 设置设备ID
    /// </summary>
    /// <param name="deviceID">设备ID</param>
    /// <returns></returns>
    public bool SetDeviceID(string deviceID)
    {
        DeviceID = deviceID;
        return true;
    }
    /// <summary>
    /// 是否可用
    /// </summary>
    /// <returns></returns>
    public bool IsEnabled()
    {
        return Enabled;
    }
}

现实考虑

数据传输对象

在使用远程接口的情况下,常常使用数据传输对象(DTO)在进程之间传输数据,以减少远程调用数。DTO聚合数据但不包含行为。这是有意为之,为了方便数据同步。

来源:http://songwenjie.cnblogs.com/
声明:本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。如果您认为还不错,不妨点击一下下方的推荐按钮,谢谢支持。转载与引用请注明出处。
微信公众号:
posted @ 2018-05-10 08:25  CoderFocus  阅读(886)  评论(0编辑  收藏  举报