天天看点

设计原则利剑二--里氏替换原则

英文名称:Liskov Substitution Principle(LSP)

中文名称:里氏替换原则

原      则:所有引用基类的地方必须能透明地使用其子类的对象

               1、子类必须完全实现父类的方法

               2、子类可以有自己的个性

               3、覆盖或实现父类的方法时输入参数可以被放大

               4、覆盖或实现父类的方法时输出结果可以被缩小

作       用:让继承的利大于弊

真       言:时时刻刻记住,在使用父类的时候,随时要想到被子类替换后是否产生错误,所以必须要遵守以上四个原则

理论实践:

             这个设计模式,看了2遍我才看懂意思,特别是什么覆盖后参数的放大,返回结果的缩小,完全被蒙了,其实仔细想想,由于在使用父类的地方可以随时替换成子类,那么自然会出现很多的问题,就自然有了上面条件的限制。按照这个意思来理解,觉得有点像中国式家庭父子关系,父亲的岗位可以替换给子女,但是子女首先必须要会父亲的业务,也允许有一些自己的创新,父亲有了大爱,随时将自己的工作岗位贡献出来了,那么子女当然也要有所付出,也要受到某些限制,如父亲工作的岗位一些默认的潜规则,你仍然得去遵守,尽管你有自己的方法,如果没有遵守父亲的规则去做事情,那么这件事情的成果也必须要小于或等于用父亲的方法做事的成果,可以理解为给父亲面子吧,嘿嘿。这也许就是继承所必须要付出和注意的地方。

             用CS游戏中来做个简单的例子,每个士兵都要有枪来杀敌人,枪有很多种类型,手枪,机枪,步枪等,那么所有的枪都从AbstractGun继承过来,那么子类中必须要求全部实现Shoot方法,那么在士兵类中,就不在乎使用的是什么枪,直接用AbstractGun来实现方法的调用Shoot,而具体使用什么枪,则可以动态调用,那么此时我们就要考虑到了,如果规定每个士兵只允许时用一种枪支,并且提前将枪支给士兵,那么这个时候在士兵类中就可以直接使用子类了,必须要设计的时候注意替换不会引起程序的错误。具体UML图如下:

          上面的设计方式,无论如何替换成子类都不会出现问题,但是此时想想,如果出现了一个玩具枪,给士兵用来练习的,却不能真正在战场上杀敌,但是玩具枪实现不了杀敌人的任务,所以这里不应该将玩具枪放到AbstractGun下面做为子类,即使作为了子类,shoot方法并不能真正杀死敌人,那么士兵在战场的结果就会是死。当然也可以在士兵模块进行判断,判断配发的是否是玩具枪,但是一旦更改了士兵类,那么很多相关的类都会受到影响,如这个士兵类下面很定还会有各种等级的士兵,都必须要做相应的改变。另外,如果是子类,很定会有shoot方法,那么很定会给士兵传递错误的信息。所以将玩具枪不要定义为枪,新的设计方式如下:

      按照此次里氏设计原则,就将玩具枪从枪里面彻底分离出来了,我们可以理解为玩具更好,不要理解为枪。要不然会误大事。在这里让我想到自己正在开发的一个项目,也有继承,但是好似没有遵守这个原则,这个类主要用来处理用户数据交互,如刷新,导出数据等操作,其实首页与图形报表根本就不需要上述所说的功能,说到这里这个类连第一个单一职责原则都没有遵守,将数据传输方法应该放入到另外一个类中去实现,这样的操作方式,传递给最上层就是每个模块都有此功能,那么最上层还以为此表有这些操作,虽然这个设计在系统中并不会造成任何影响,但是如果改变一种设计方式,可以让用户体验更好,如果是加载图形主页的时候,我就让最上层的类不要去处理导出事件,快捷键事件,我现在系统的设计如下:

          经过重新设计,将HomePageControl,GraphPageControl独立成2个类,这样每次新增加一个子类的时候没有必要去修改MainForm类了,实现UML图如下:

继续阅读