前言:本来上篇已经是完整篇,可在上篇第22楼virus的回复中,我发现了上篇文章中的最终解决方案还是存在着本质的缺陷。再看到第24楼richardzeng的回复,仔细想来,的确应该是像richardzeng的写法一样有更深一步的考虑,感谢两位的回复。我将补充方案的讲解加了进来,希望可以给大家以帮助。
接上篇 凭什么要用面向对象编程——面向对象重要设计原则概述
★2007-10-02 08:00 小菜在家中准备
小菜开始准备给人家讲座的内容,一开始都很顺利。可是当要把不同的验证方式给细化时,发现了问题。
24 原有的接口实现关系图
如果要再把是用户名密码验证,还是指纹验证的代码加进来,应该如何写具体的实现类呢?
25 SqlServer实现类改造
此时你会发现,如果要实现这个功能,你必须在你的每个实现类中写出上面的判断语句,如果某一天要增加一种数据访问(比如MySql)你就得再写一遍类似的代码,如果某天增加一个用户验证的方式(比如人脸识别验证),你就得改动所有的实现类的分支判断。这显然是让人难以接受的。
小菜决定换一种思路
26 多个类的多重继承
也就是把分支判断的语句,通过继承的方式给分解掉,这样每一种实现都体现成为一个类。只要有新的数据访问或者用户验证方式,不外乎就是增加类就可以了。应该是解决了问题。
可是,看着这张图,小菜感觉不到设计的美。如果,我们增加一种数据访问(如MySql),我们需要增加至少四个类。如果我们增加一种验证方式(如人脸识别验证),那就需要每个数据访问下都继承一个类。目前是3*3共9个类,当扩展需求来了以后,类的增加会成为一个梦魇。
难道没有办法了吗?
1)当想不出解决办法时,分析自己的代码有什么容易被改变的地方,可能找到解决思路。
回到那个接口
其实当我们要增加一种验证方式时,这个Users类也是要修改的。而且对于Users类来说,验证时通常只会用到当中的一两个属性,而现实中,Users类的属性如性别、生日、姓名等等都对验证来说没有意义。此时发现,验证和用户类本身还是有区别的,验证属性其实只是用户类一小部分属性而已。对于数据库来说,我们把用户名、密码、门禁卡编号、指纹数据存到Users表里的字段中是没有问题,可在面向对象编程时,将它们混在一起的确不利于应对变化。
2)当想不出解决办法时,再次仔细分析需求,并找出需求的本质是个很好的办法。
登录是为了验证用户,验证有很多种表现形式。网络上的验证通常就是用户名和密码,但随着科技的进步,指纹试别、面部试别等技术都可以成为验证用户的手段。而这一切,其本质上,验证就是抽象概念,其它都是它的实现方式。那么验证应该就是接口,那几个表现方式是它的实现类。OK,思路有了。
思路:提炼出验证接口,将不同的验证实现这个接口,将用户管理接口的登录方法聚合这个接口。
27 相对较好实现办法的类图
将验证分离成一个接口,不同的验证方式不过是验证的一种表现形式。
32 更改了原用的参数,改用验证接口
33 Sqlserver实现类的代码(其它实现类类似)
34 用户名密码登录用的界面事件代码
35 指纹登录的界面代码
此时,小菜算是松了口气,不管是增加新的数据访问或是增加新的验证方式,都只不过是增加一个类就可以了,这就把“开放封闭原则”和“依赖倒转原则”再次得到了充分的体现。实际上,在不知不觉中,小菜已经使用了“简单工厂模式”、“门面(外观)模式”、“桥接模式”。在这个完整的例子中,并不是为了模式而模式,而是在需求的变更中,为了应对需求的变化而不断演变出来。相信以后需求再有变化,也可以从容面对。
最后,小菜在新建文档的标题处打上“凭什么要用面向对象编程”,开始培训材料的写作。
附:有朋友质疑一个登录功能写这么多的代码的必要性。我想说的是,这是一个讲解面向对象编程的例子,为了理解的需要,杜撰了一些需求的变更。通过这样的讲解,是希望你可以在你的编程工作中,学会如何去设计和考虑问题,更好的应用面向对象技术来创建可维护、可扩展、可复用,并灵活性好的程序。
源代码下载