《设计模式之禅》六大设计原则(二)里氏替换原则

来源:互联网 发布:杭州富谦网络 编辑:程序博客网 时间:2024/05/01 08:33
  • (二) 里氏替换原则(LSP-Liskov Substitution Princle)
  1. 概念:所有引用基类的地方都必须能透明的使用其子类(通俗的说就是父类出现的地方子类就可以出现,反之不一定成立)
  2. 示例:如图2-1一个简单的CS士兵与枪的示例,soldier建立与抽象Gun的联系,等到具体的场景类Client创建时,才创建AbstractGun的具体子类,示例代码:
    public class Client{Soldier sanMao=new Soldier();sanMao.setGun(new Rifle());sanMao.killEnemy();}

             
                                            图2-1
    使用原则:在类中调用其他的类时务必要使用父类或接口,如果不能使用父类或接口,则类的设计已经违背了LSP原则。
  3. 示例引申:
            如果有了一把玩具枪ToyGun,它在形状声音等都和真枪相似(假设AbstractGun中有描述形状和声音的属性),但是不能shoot杀人,该如何进行设计?两种解决办法,在Soldier类中增加InstanceOf的判断,如果为玩具枪则不能杀人,如果是这样则所有与AbstractGun有关联的类,都必须增加这一判断条件;第二种,ToyGun脱离继承,建立一个独立的父类,为了实现代码复用,可以与AbstractGun建立关联委托关系,如图2-2所示

                                                            图2-2
    如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生了“畸变”,则建议断开父子继承关系,采用依赖、聚合、组合等关系来代替继承。
        实际上,这就衍生出了另一个设计模式的关键词Delegate即委派,该设计模式主要的应用场景为由于敏捷编程的兴起,重构技术也得到了大家的广泛使用;要使用重构,其中的一个重要原则是:使用组合代替聚合,这样才能方便重构。使用组合,最终是要使用到委派技术了。同时,我们经常有这样的一些需求,我们只想继承一个类的部分功能,而另外一部分功能是我们不想要、甚至是对我们有害的功能,这时候,我们只能使用委派技术,而不能使用继承了。
        基于上面的一些分析,我们知道,继承和委派技术都能扩展一个类,使它拥有另外一个类的部分或者全部功能。但委派技术更为灵活,它可以使得一个类可以全部或部分的扩展得到另一个类的功能,而继承只能是全盘接收。网址http://java.chinaitlab.com/advance/750844.html给出了为什么java语言强调委派比较少的原因,以及委派模式在Groovy语言中的应用。
  4. 实施原则
    (1)子类在实现或覆盖父类的方法时,输入参数可以被放大。
    public class Father{      public Collection doSomething(HashMap map){      System.out.println("父类被执行");      return map.values(); }}public class Son extends Father{      public Collection doSomething(Map map){      System.out.println("子类被执行");      return map.values(); }}public class Client{public static void invoker(){//父类存在的地方Father f= new Father();HashMap map= new HashMap();f.doSomething(map);}public static void main(String[] args){invoker();}}
    以上代码输出为“父类被执行”,如果将父类存在的地方换成Son,则同样输出“父类被执行”,该逻辑是合理的,遵循LSP原则,若将二者输入参数尽心调换,即Father类doSomething(Map map),Son类为doSomething(HashMap map),则执行结果为“子类被调用了”。
    (2)子类在实现或覆盖父类的方法时,返回结果可以被缩小,代码不在赘述。
  5. 最佳实践做法为:尽量减少子类的个性,一旦子类拥有个性,则很难和父类进行调和。
0 0