欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-28 19:14:28
模块化原则倡导利用集中和分解等手法创建高内聚、低耦合的抽象。 为了理解模块化的含义及其很重要的原因,来看看一本书的极端情况。假设一本书像讲一个长故事一样阐述其中的内容,中间没有任何停顿,也没有章节。试问面对这样的图书,读者将作何反应呢?我估计心中一定有千万只*在崩腾吧。如果这本书根据内容分为不同 ......

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

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

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

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

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

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

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

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

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

拆散的模块化

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

常见表现形式如下:

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

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

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

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

拆散的模块化潜在原因

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

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

不熟悉既有设计

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

示例分析

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

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

这些数据和方法原本应该集中放在一个类中,却分散在了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类中。

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

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

重构后的代码实现:

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聚合数据但不包含行为。这是有意为之,为了方便数据同步。



作者:

来源:
声明:本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。如果您认为还不错,不妨点击一下下方的推荐按钮,谢谢支持。转载与引用请注明出处。

微信公众号:
【模块化那些事】 拆散的模块化


posted @ 2018-05-10 08:25 撸码那些事 阅读(...) 评论(...)