软件设计有很多原则,比如软件设计上的 SOLID principle,单元测试中的 FIRST和AAA,代码实现上的 DRY principle 等。熟悉这些原则,可以把我们的经验上升到理论高度,有利于程序员的成长,也便于团队带头人和组员控制软件质量。
我们先介绍 SOLID 原则。SOLID 是下面几个英文词组的缩写
- Single-Responsibility principle (单一职责原则)
- Open-Close principle (开放扩展,封闭修改)
- Liskov substitution principle (Liskov 替换原则)
- Interface segregation principle (接口隔离原则)
- Dependency inversion principle (依赖反转原则)
Single-Responsibility principle (单一职责原则)
这个原则是指一个软件模块只应该负责一件事情。当规范修改的时候,我们只需要修改与规范相关的模块。软件模块不应该像瑞士军刀那样,什么都能干,而应该像厨房里刀具套装里的刀具一样各司其责。
比如下面的代码:
public class ProtocolTranslator
{
public String Translate(String content)
{
try {
// Translate the protocol
}
catch(Exception ex)
{
WriteLogToFile(ex.getMessage());
}
}
public void WriteLogToFile(String log)
{
// write log to file
}
}
上面的代码是一个协议翻译类,在协议翻译的过程中如果出现了异常,则把异常写入文件日志中。粗略看来这个类没有问题,但是如果我们需要把日志写入数据库,那么我么就需要改变代码。按照单一职责原则,这个类的设计就没有达到要求,因为日志规范的修改,确需要修改协议翻译类。为此我们可以引入专门的日志类来解决这个问题。代码如下:
public class ProtocolTranslator
{
private final Logger logger;
ProtocolTranslator(Logger logger)
{
this.logger = logger;
}
public String Translate(String content)
{
try {
// Translate the protocol
}
catch(Exception ex)
{
this.logger.log(ex);
}
}
}
如果再遇到日志相关的需求变更,我们只需要修改日志类就好了。
Open-Close principle (开放扩展,封闭修改)
开放扩展说的是我们设计的模块或者类只有在收到新的需求的时候才会增加新的功能,只有在发现了缺陷 (bug) 的时候才需要修改。现代软件往往要求单元测试达到一定覆盖率,如果不遵从OCP,那么光重新修改单元测试测试就会产生巨大的工作量。如果我们增加新功能都要大量修改我们的单元测试代码,那么就说明我们需要引入OCP原则。这里说的增加新功能是指通常通过继承类来实现的。
假设我们有下面的鸡和狗的类:
public class Dog
{
}
public class Chicken
{
}
public class AnimalCounter
{
public int countFeet(ArrayList<Object> animals)
{
int count = 0;
for(Object animal: animals)
{
if(animal instanceof Dog)
count += 4;
else if(animal instanceof Chicken)
count +=2;
}
return count;
}
}
上面这个程序 AnimalCounter 负责统计动物的腿数,如果我们要增加一种新动物比如 Sheep,我们就需要给 AnimalCounter 的 countFeet 函数增加一个判断,判断数组中是不是有 Sheep 实例,这就是说当有新的需求来的时候,我们得修改 AnimalCounter 代码,而不是扩展它。
下面的代码可以解决这个问题。我们使用了一个接口,鸡类和狗类都继承了这个接口,在 AnimalCounter 中我们只要调用这个接口就可以知道动物有多少只脚了。无论是再有绵羊类或者是昆虫类,它们只要继承了这个接口,AnimalCounter 都可以计算出动物的总脚数。也就是我们通过扩展 IAnimal 接口就可以满足需求,而不用修改 AnimalCounter 类。