对《设计模式之禅》中里氏替换原则的思考

里氏替换原则的定义:
定义一:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时(o2都替换成o1吧?),程序P的行为没有发生变化,那么类型S是类型T的子类型。
定义二:所有引用基类的地方必须能透明地使用其子类的对象。

这个定义的四层含义是:

1 子类必须完全实现父类方法这里的父类是对抽象类和接口说的。

2 子类可以有自己的个性

3 覆盖或实现父类方法时输入参数可以被放大。我觉得这里有点疑问,看看书上的程序

1.package hit;   
2.import java.util.Map;   
3.import java.util.HashMap;   
4.import java.util.Collection;   
5.class Father{   
6.    public Collection doSomething(HashMap map)   
7.    {   
8.        System.out.println("父类被执行");   
9.        return map.values();   
10.    }   
11.}   
12.  
13.class Son extends Father{   
14.    public Collection doSomething(Map map){   
15.        System.out.println("子类被执行");   
16.        return map.values();   
17.    }   
18.}   
19.  
20.public class Client {   
21.  
22.    public static void invoker(){   
23.        Father f = new Father();   
24.        HashMap map = new HashMap();   
25.        f.doSomething(map);   
26.    }   
27.       
28.    public static void main(String[] args) {   
29.        invoker();   
30.    }   
31.}  

执行结果是:父类调用

然后将父类的对象替换成子类,这里我就有疑问了,按照定义,替换的是对象,应该不包括声明吧?我认为应该将

1.Father f = new Father();  

改成

Father f = new Son();  

这样就可以了。而书上说的是改成

1.son f = new Son();  


我觉得有点违背定义以及现时中的应用。如果觉得不明显,我们可以改造一下invoker方法

1.public static void invoker(Father f ){   
2.        HashMap map = new HashMap();   
3.        f.doSomething(map);   
4.    }   
5.   public static void main(String[] args) {   
6.        Father f = new Father();   
7.        invoker(f);   
8.    }  

invoker是把Father作为参数,这样在main方法中给invoker传递Father的对象或者其子类的对象,如果能够这样做就符合里氏替换原则啊。而且我觉得不修改invoker方法是不是不符合迪米特法则呢?

不管是按照书上的方法改,还是按照我说的方法以及将invoker方法修改,都得到同样的结论,即:子类的方法不可能被执行。这是正确的。

如果父类的输入参数类型宽于子类的输入参数类型会怎么样呢?书上说,父类出现的地方子类未必可以出现。的确,如果按照书上的这种将

Father f = new Father();

改成

1.son  f = new Son();  


的做法确实可以得出“父类出现的地方子类未必可以出现”。但是如果看一下里氏替换原则的两个定义,你会发现替换的是对象,没有提到声明类型也替换吧?

按照只替换对象的方法我们来看一下父类的输入参数类型宽于子类的输入参数类型会怎么样?

1.package hit;   
2.import java.util.Map;   
3.import java.util.HashMap;   
4.import java.util.Collection;   
5.class Father{   
6.    public Collection doSomething(Map map)   
7.    {   
8.        System.out.println("父类被执行");   
9.        return map.values();   
10.    }   
11.}   
12.  
13.class Son extends Father{   
14.    public Collection doSomething(HashMap map){   
15.        System.out.println("子类被执行");   
16.        return map.values();   
17.    }   
18.}   
19.  
20.public class Client {   
21.  
22.    public static void invoker(){   
23.        Father f = new Son();// Son f = new Son();   
24.        HashMap map = new HashMap();   
25.        f.doSomething(map);   
26.    }   
27.       
28.    public static void main(String[] args) {   
29.        invoker();   
30.    }   
31.}  

结果是:父类调用.看注释的代码,如果用注释后的Son f = new Son();

替换掉 Father f = new Son(); 那结果就会是 :子类被调用

因为声明的就是Son的对象,Son有两个方法 doSomething(Map map)和 doSomething(Hashmap map ),现在传递过来的是 HashMap 的对象,HashMap 实现了Map接口,那调用一个方法呢?答案:调用精确匹配的那个。所以如果将f声明为Son类型,按照精确匹配,会调用doSomething(HashMap map).如果将f申明为Father类型,Father类型只有一个doSomething(Map map)方法,HashMap类型的map对象会向上转型为Map类型,执行Father类中的方法。虽然传递给f的是Son类型的对象,但是Java还是会根据声明来判定到底执行哪一个类中的方法

以上是我看设计模式之禅中里氏替换原则后的一些感想,如果有错误请批评指正。

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...