现在的位置: 首页 > 综合 > 正文

如何编写出拥抱变化的代码?

2013年09月25日 ⁄ 综合 ⁄ 共 7428字 ⁄ 字号 评论关闭

在实际的开发中,编写出易维护和易接受变化的代码并非易事,想要实现可能更加困难重重:源码难于理解、依赖关系指向不明、耦合也很令人头疼。难道就真的就没有办法了吗?本文中我们一起探讨几个技术原则和一些编码理念,让你的代码跟着需求走,而且易维护易拓展。

介绍些面向对象方法

面向对象编程(OOP)是一种很受欢迎的编程思想,它保证了代码的组织性和重用性。软件公司采用OOP思想编程已经好多年了,如今仍然在项目开发中使用这一思想。OOP拥有一系列非常好的编程原则,如果使用恰当,它会让你的代码更好、更整洁和更易维护。

1.内聚力

这里的内聚力是指拥有一些共同的特征的东西而逐渐凝聚到一起,而不能在一起的东西则会被移除出去。可以用一个类来说明内聚力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class
ANOTCohesiveClass {
   private
$firstNumber;
   private
$secondNumber;
   private
$length;
   private
$width;
   function
__construct($firstNumber,
$secondNumber) {
      $this->firstNumber =
$firstNumber;
      $this->secondNumber =
$secondNumber;
   }
   function
setLength($length) {
      $this->length =
$length;
   }
   function
setHeight($height) {
      $this->width =
$height;
   }
   function
add() {
      return
$this->firstNumber +
$this->secondNumber;
   }
   function
subtract() {
      return
$this->firstNumber -
$this->secondNumber;
   }
   function
area() {
      return
$this->length *
$this->width;
   }
}

该例定义了一个类以及一些表示数字和大小的字段。而这些属性通过他们的名称来判断是否应该在一起。add()和substract()方法来对两个number进行操作,此外还定义了area()来操作length和width这两个字段。   

这个类只负责各个独立的群体信息,显然,内聚力很低。重构上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class
ACohesiveClass {
   private
$firstNumber;
   private
$secondNumber;
   function
__construct($firstNumber,
$secondNumber) {
      $this->firstNumber =
$firstNumber;
      $this->secondNumber =
$secondNumber;
   }
   function
add() {
      return
$this->firstNumber +
$this->secondNumber;
   }
   function
subtract() {
      return
$this->firstNumber -
$this->secondNumber;
   }
}

重构以后,该类明显变成了高内聚特征的类。为什么?因为这个类里的每个部分都与另外一部分彼此联系。虽然在实际开发中编写出高内聚的类比较困难,但开发人员应该坚持这样做,坚持就是胜利。

2.正交性

就简单而言,正交是指隔离或排除副作用。一个方法、类或者模块改变了其他无关的方法、类或模块就不是正交。例如,飞机的黑匣子就具有正交性,它自身就具备电源、麦克风和传感器等这些功能。而它对外在的其他东西没有任何影响,它只提供一种机制,用来保存和检索飞行数据。

一个典型的非正交系统例子就是汽车电子设备。提高汽车的速度也存在些负面影响,比如会增加无线电音量,然而对汽车来说,速度并不是正交。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class
Calculator {
   private
$firstNumber;
   private
$secondNumber;
   function
__construct($firstNumber,
$secondNumber) {
      $this->firstNumber =
$firstNumber;
      $this->secondNumber =
$secondNumber;
   }
   function
add() {
      $sum
= $this->firstNumber +
$this->secondNumber;
      if
($sum
> 100) {
        
(new
AlertMechanism())->tooBigNumber(
$sum);
      }
      return
$sum;
   }
   function
subtract() {
      return
$this->firstNumber -
$this->secondNumber;
   }
}
class
AlertMechanism {
   function
tooBigNumber($number) {
      echo
$number . 'is too big!';
   }
}

在这个例子中,Calculator类里的add()方法里列了几个意想不到的行为:它生成AlertMechanism对象并调用其中的一个方法。实际上,该库的使用者并不希望消息被打印到屏幕上,相反,他们则是要计算数字之和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class
Calculator {
   private
$firstNumber;
   private
$secondNumber;
   function
__construct($firstNumber,
$secondNumber) {
      $this->firstNumber =
$firstNumber;
      $this->secondNumber =
$secondNumber;
   }
   function
add() {
      return
$this->firstNumber +
$this->secondNumber;
   }
   function
subtract() {
      return
$this->firstNumber -
$this->secondNumber;
   }
}
class
AlertMechanism {
   function
checkLimits($firstNumber,
$secondNumber) {
      $sum
= (new
Calculator(
$firstNumber,
$secondNumber))->add();
      if
($sum
> 100) {
        
$this->tooBigNumber($sum);
      }
   }
   function
tooBigNumber($number) {
      echo
$number . 'is too big!';
   }
}

这样明显好多了,AlertMechanish在Calculator中没有任何负面影响,相反,在任何需要弹出警告的地方都可以使用AlertMechanish。

3.依赖和耦合

大多数情况下,这两个单词是可以互换的,但是在某些情况下,又存在优先级关系。

那么,什么是依赖呢?当对象A需要使用对象B时,为了执行其规定的行为,我们说A依赖B。在OOP中,依赖是极其常见的。对象之间经常互相依赖才发挥功效。因此消除依赖是一项崇高的追求,这样做几乎是不可能的。控制依赖和减少依赖则是非常完美的。

就紧耦合(heavy-coupling)和松耦合(loose-coupling)而言,通常是指一个对象依赖于其他对象的程度。

在一个松耦合系统中,一个对象的变化会减少对其依赖对象的影响。在这样的系统中,类取决于接口而不是具体的实现(将会在下面提到)。这就是为什么松耦合系统对修改更加开放的原因。

Coupling in a Field

让我们看下面这个例子:

1
2
3
4
5
6
class
Display {
   private
$calculator;
   function
__construct() {
      $this->calculator =
new Calculator(1,2);
   }
}

这段代码很常见,在该例中,Display类依赖Calculator类并直接引用该类。Display类里的 $calculator字段属于Calculator类型。该对象和字段直接调用Calculator的构造函数。

通过访问其他类方法进行耦合

大家可以先看下面的代码:

1
2
3
4
5
6
7
8
9
class
Display {
   private
$calculator;
   function
__construct() {
      $this->calculator =
new Calculator(1, 2);
   }
   function
printSum() {
      echo
$this->calculator->add();
   }
}

Display类调用Calculator对象的add()方法。这是另外一种耦合方式,一个类访问另外一个类的方法。

通过方法引用进行耦合

你也可以通过方法引用进行耦合:

1
2
3
4
5
6
7
8
9
10
11
12
class
Display {
   private
$calculator;
   function
__construct() {
      $this->calculator =
$this->makeCalculator();
   }
   function
printSum() {
      echo
$this->calculator->add();
   }
   function
makeCalculator() {
      return
new Calculator(1, 2);
   }
}

需引起注意的是,makeCalculator()方法返回一个Calculator对象,这也是一种依赖。

利用多态进行耦合

遗传可能是依赖里的最强表现形式。

1
2
3
4
5
class
AdvancedCalculator
extends
Calculator {
   function
sinus($value) {
      return
sin($value);
   }
}

通过依赖注入降低耦合

开发人员可以通过依赖注入来降低耦合度,例如:

1
2
3
4
5
6
7
class
Display {
   private
$calculator;
   function
__construct(Calculator $calculator
= null) {
      $this->calculator =
$calculator ? :
$this->makeCalculator();
   }
// ... //
}

利用Display的构造函数对Calculator对象进行注入,从而减少了Display对Calculator类产生的依赖。

利用接口降低耦合

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface
CanCompute {
   function
add();
   function
subtract();
}
class
Calculator
implements
CanCompute {
   private
$firstNumber;
   private
$secondNumber;
   function
__construct($firstNumber,
$secondNumber) {
      $this->firstNumber =
$firstNumber;
      $this->secondNumber =
$secondNumber;
   }
   function
add() {
      return
$this->firstNumber +
$this->secondNumber;
   }
   function
subtract() {
      return
$this->firstNumber -
$this->secondNumber;
   }
}
class
Display {
   private
$calculator;
   function
__construct(CanCompute $calculator
= null) {
      $this->calculator =
$calculator ? :
$this->makeCalculator();
   }
   function
printSum() {
      echo
$this->calculator->add();
   }
   function
makeCalculator() {
      return
new Calculator(1, 2);
   }
}

该代码定义了一个CanCompute接口,在OOP中,接口可以看作一个抽象类型,它所定义的成员必须由类或结构来实现。在上述代码中,Calculator类来实现CanCompute接口。

Display构造函数期望有个对象来实现Cancompute接口,这时,Display的依赖对象Calculator被打破。然而,我们可以创建另一个类对象来实现Cancompute,并且传递一个对象到Display的构造函数中。Display现在只依赖于Cancompute接口,但即使这样依赖关系仍然是可选的。如果我们不传递任何参数给Display的构造函数,那么它将通过调用makeCalculator()方法来创建一个Calculator对象。这种技术经常被开发者们使用,尤其对驱动测试开发(TDD)极其有帮助。

SOLID原则

SOLID是一套代码编写守则,也就是大家常常说的敏捷开发原则,最初由Robert C. Martin所提出。使用它编写出来的代码不仅干净整洁,而且易维护、易修改和易扩展。实践表明,其在可维护性上有着非常积极的影响,更多资料大家可以阅读:   
Agile Software Development, Principles, Patterns, and Practices

SOLID所涵盖的话题非常广,下面我将会针对本文的主旨介绍一些简单易学的方法。

1.单一责任原则(SRP)

一个类只干一件事。听起来简单,但在实践中却可能相当难。

1
2
3
4
5
6
class
Reporter {
   function
generateIncomeReports();
   function
generatePaymentsReports();
   function
computeBalance();
   function
printReport();
}

查看上面的代码,你认为该类的受益者会是哪个部门?会计部是用于收支平衡、财政部可能用来编写收入/支出报告,甚至归档部来打印和存档报告。然而每个部门都希望有属于自己的方法,并且根据自身需求来做些自定义的方法。

这样的类往往都是高内聚低耦合的。

2.Open-Closed原则(OCP)

类(和模块)应具备很好的功能扩展性,以及对现有功能具有一定的保护能力。让我们一起来看下典型的电风扇例子,你有一个开关来控制风扇:

1
2
3
4
5
6
7
8
9
10
11
12
class
Switch_ {
   private
$fan;
   function
__construct() {
      $this->fan =
new Fan();
   }
   function
turnOn() {
      $this->fan->on();
   }
   function
turnOff() {
      $this->fan->off();
   }
}

这段代码创建了Switch_类,用来创建和控制Fan对象。注意这里的下划线,在PHP中是不允许把类名定义为Switch的。

这时,你的老板希望能利用该开关控制电风扇上的电灯,那么你就不得不修改Switch_这个类。

对现有代码进行修改存在一部分风险,很有可能对系统其他部分产生影响。所以在添加新功能时的最好的方法是避开现有功能。

在OOP中,你可以发现Switch_对Fan类有很强的依赖性。这正是我们的问题所在,基于此,做出如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface
Switchable {
   function
on();
   function
off();
}
class
Fan
implements Switchable {
   public
function on() {
      // code to start the fan
   }
   public
function off() {
      // code to stop the fan
   }
}
class
Switch_ {
   private
$switchable;
   function
__construct(Switchable $switchable) {
      $this->switchable =
$switchable;
   }
   function
turnOn() {
      $this->switchable->on();
   }
   function
turnOff() {
      $this->switchable->off();
   }
}

该代码定义了一个Switchable接口,它里面所定义的方法需要开关启用选项来实现。Fan对象实现Switchable和Switch_并且接受一个参数到Switchable对象的构造函数里。

这样做有哪些好处?

首先,该解决方案打破了Switch_和Fan之间的依赖关系。Switch_不知道它要开启风扇,并且也不关心。其次引进的Light类不会影响Switch_或Switchable。难道你想用Switch_类来控制Light对象吗?代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class
Light
implements
Switchable {
   public
function on() {
      // code to turn ligh on
   }
   public
function

抱歉!评论已关闭.