1700454359
1700454360
1700454361
1700454362
图1-4 电话类图
1700454363
1700454364
我不是有意要冒犯IPhone的,同名纯属巧合,我们来看一个这个过程的代码,如代码清单1-2所示。
1700454365
1700454366
代码清单1-2 电话过程
1700454367
1700454368
public interface IPhone{
1700454369
1700454370
//拨通电话
1700454371
1700454372
public void dial(String phoneNumber);
1700454373
1700454374
//通话
1700454375
1700454376
public void chat(Object o);
1700454377
1700454378
//通话完毕,挂电话
1700454379
1700454380
public void hangup();
1700454381
1700454382
}
1700454383
1700454384
实现类也比较简单,我就不再写了,大家看看这个接口有没有问题?我相信大部分的读者都会说这个没有问题呀,以前我就是这么做的呀,某某书上也是这么写的呀,还有什么什么的源码也是这么写的!是的,这个接口接近于完美,看清楚了,是“接近”!单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情,看看上面的接口只负责一件事情吗?是只有一个原因引起变化吗?好像不是!
1700454385
1700454386
IPhone这个接口可不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()实现的是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂的语言。我们可以这样考虑这个问题,协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(想想看,电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?会的!那就很简单了,这里有两个原因都引起了类的变化。这两个职责会相互影响吗?电话拨号,我只要能接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?通过这样的分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口,其类图如图1-5所示。
1700454387
1700454388
1700454389
1700454390
1700454391
图1-5 职责分明的电话类图
1700454392
1700454393
这个类图看上去有点复杂了,完全满足了单一职责原则的要求,每个接口职责分明,结构清晰,但是我相信你在设计的时候肯定不会采用这种方式,一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,你和我都有共同的生命期,这样的强耦合关系还不如使用接口实现的方式呢,而且还增加了类的复杂性,多了两个类。经过这样的思考后,我们再修改一下类图,如图1-6所示。
1700454394
1700454395
1700454396
1700454397
1700454398
图1-6 简洁清晰、职责分明的电话类图
1700454399
1700454400
这样的设计才是完美的,一个类实现了两个接口,把两个职责融合在一个类中。你会觉得这个Phone有两个原因引起变化了呀,是的,但是别忘记了我们是面向接口编程,我们对外公布的是接口而不是实现类。而且,如果真要实现类的单一职责,这个就必须使用上面的组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为地增加了设计的复杂性。
1700454401
1700454402
通过上面的例子,我们来总结一下单一职责原则有什么好处:
1700454403
1700454404
❑类的复杂性降低,实现什么职责都有清晰明确的定义;
1700454405
1700454406
❑可读性提高,复杂性降低,那当然可读性提高了;
1700454407
1700454408
❑可维护性提高,可读性提高,那当然更容易维护了;
[
上一页 ]
[ :1.700454359e+09 ]
[
下一页 ]