关于耦合

在做程序设计时我们经常会听到“高内聚,低耦合”,这是我们追求的目标。 其中内聚是指一个模块内各个元素彼此结合的紧密程度, 高内聚是指一个模块或一个类内部各个元素之间关系紧密,争取用最少的元素和方法实现相应的功能; 耦合是指模块之间互相关联程度的度量, 而低耦合是指一个程序中各个模块之间的联系少和相互依赖程度低,一个模块只需要具体实现一个功能。 总的来说,我们所追求的设计是这样的,每个模块职责单一,一个模块只完成一个相对独立的特定子功能,并且和其他模块之间的关系简单(嗯,保持简单), 在不同的应用场景下复用这些模块,从而组成新的系统或大的模块。

在编写代码的过程中,我们应该尽量实现程序的内聚性。内聚性差的程序通常会让人产生这里什么都有,一片杂乱的景象, 特别是那种将类作为若干不同功能函数集的设计。和内聚性差一样,也会让人产生不好情绪还有高耦合。 耦合就是一些代码或一些模块对另一些代码或模块的依赖。而依赖破坏了当初的分块,严重的模块间耦合会让当初的模块化的设想付之一炬。 虽然这种耦合产生的依赖不是很好,但是我们并不能消除耦合。因为正是这种耦合,各种程序模块才能产生互操作。 所以我们只能在允许的范围内尽量减少不必要的耦合。

我们可以将耦合分为以下三类:

  1. 可以接受的耦合
  2. 不建议使用的耦合
  3. 真的不建议使用的耦合

直接看这三个分类真没啥意义,关键是这些分类中所包含的耦合实例。

可以接受的耦合

可用的耦合是指这种耦合会经常存在,而且有时也必须存在,其害处不大。如数据耦合和标记耦合。 何为 数据耦合,看这样一个例子:

function main() {
    output_name('name');
}

function output_name($name) {
    echo $name;
}
main();

这种以函数间传递参数的方式产生的函数是不可避免的,这对程序没有太大的影响。

标记耦合 是指将一个大的数据结构或类传递给另一个模块或类,而该模块只需要这个数据结构的一小部分,或只需要这个类的一个属性值。 以传递为类为例:

class Foo {
    private $_name;

    public function __construct() {
        $this->_name = __CLASS__;
    }

    public function getName() {
        return $this->_name;
    }
}

function output_name(Foo $foo) {
    echo $foo->getName();
}

output_name(new Foo());

其实我们应该是将getName()的输出直接传递给输出函数。 标记函数在一定程序上阻碍了我们对于程序的理解,因为我们需要去了解这个程序到底调用了类中的哪一个方法或使用了数据结构中的哪一个属性。

不建议使用的耦合

不建议使用的耦合是指那种我们会经常见到的不会影响整体的架构,但是有一些坏味道的代码,如控制耦合,时间耦合

控制耦合 是指一个函数传递的某个参数影响另一个函数中所作的控制决定。有时候这种实现只是为了复用一些代码。 如下示例:

function edit($is_add = 0) {
    if ($is_add) {
        //  一些需要添加的字段
    }else{
        //  一些编辑的字段
    }

    //  公共的字段
    //  保存数据
}

以上的示例只是想复用编辑和添加的操作中一些公共的东西,从而引入是否是添加操作的变量。 如果要重构以上代码,可以将一个函数分为三个函数,添加和修改各是一个,公共代码放一个函数。

时间耦合 是指代码中各个元素之间依赖于时间的关系, 以类的public方法调用为例,如果一个方法A在调用时需要先调用另一个方法B,则这里就是时间耦合。 看一个示例:

class File {

    private $_path;

    public function __construct() {

    }

    public function read() {
        echo 'read file from ', $this->_path, '<br />';
    }

    public function setPath($path) {
        $this->_path = $path;
    }
}

$file = new File();
$file->setPath(__FILE__);
$file->read();

以上是一个示例性质的文件类,在读取一个文件的文件内容之前需要先设置这个文件所在的地址。 这种形式的时间耦合应该尽量避免,如果要重构这样的代码,可以将路径作为一个变量传递给构造函数。

真的不建议使用的耦合

这类耦合一旦出现,虽然不会赤地千里,但会严重影响整体的架构和模块间的关联。如公用耦合

公用耦合 通俗的讲,公共耦合就是使用了全局变量。当两个函数或两个模块访问同一个全局变量时,它们就被公用耦合了。 在PHP中一些全局变量$_GET,$_POST等经常会在一些代码中用到,如果有两段代码都使用了这些变量并且修改了同一个值,但是双方互不相知,此时就会出BUG。

领域耦合 指的是在应用的代码中嵌入了领域或商业知识与规则,简单的讲就是将业务规则硬编码到应用中,为一些特定的业务定制应用。 这在我们的开发中会经常出现,一般会通过配置或特定的领域语言减轻系统某些部分因为耦合带来的问题。

以上的各种耦合的定义都来自《高质量程序设计艺术》一书。

关于耦合》上有7条评论

  1. 小螺丝钉

    Hi
    过来再次膜拜一下,嘿嘿。正式工作都快一个月了,发现时间过的太快了。要学的东西太多了还需要继续学习。

    回复
  2. rey

    pan哥,有个问题想请教,我有个类 Session implements ArrayAccess,里面有个offsetSet()方法,这个方法在什么情况下会被触发呢?

    回复
    1. 胖胖 文章作者

      在实现了ArrayAccess接口的类的实例使用类似于数组方式设置值时调用,
      如你所写的类:
      $session = new Session();
      $session['value'] = 10;

      此时就会调用了。

      回复
  3. tmkook

    博主我有个疑问,我写了个验证码类,使用方法是:
    $captcha = new Captcha();
    $captcha->setImSize(128,40);设置验证码尺寸
    $captcha->create(); //首先创建画布
    $captcha->drawNoise(); //绘制燥点
    $captcha->drawCurve(); //绘制干扰线
    $captcha->display();//输出图像

    设置参数必须在创建画布之前调用
    绘制燥点和干扰线必须在创建画布之后调用
    最后输出图像
    这样有犯“时间耦合”吗?
    如果有,该怎么改,如果将参数都放到构造函数中,那构造函数中可能会充斥5个以上的参数

    回复

tmkook进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注


*

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>