(四)依赖倒置原则
(五)接口隔离原则
(六)迪米特原则
里式替换原则最早是在1988年,由麻省理工学院一位姓里的女士(Liskov)提出来的。Liskov女士的简介里式替换原则 基于这样的一个事实,那就是客户程序在调用某一个类时,实际上是对该类的整个继承体系设定了一个契约,继承体系中的所有类必须遵循这一契约,即前置条件和 后置条件必须保持一致 。这就超越了继承中子类与父类之间形成的is-a关系,为对象继承加上了一把严格的枷锁。显然,里式替换原则对于约束继承的泛滥具有重要意义
问题由来:
有一功能P,由类A完成。现需要将功能P进行扩展,扩展后的功能为P1,其中P由原有功能P与新功能N组成。新功能N由类A的子类B来完成,则子类B在完成新功能N的同时,有可能会导致原有功能P1发生故障。比如B重写了父类A中已经实现的方法,将会导致原有功能的故障。
例如:
原有类A实现两个整数相加功能,客户端Client调用类A的add方法实现输入俩个数求和的功能。
/**
* @author zhengzhong on 2017/10/10.
* email zheng_zhong@163.com
* @version V1.0.0
*/
public class A {
public int add(int a,int b){
return a+b;
}
}
/**
* @author zhengzhong on 2017/10/10.
* email zheng_zhong@163.com
* @version V1.0.0
*/
public class Client {
private static A a;
public static void main(String[] args) {
a=new A();
System.out.print("2+1="+a.add(2,1));
}
}
2+1=3
Process finished with exit code 0
现在PM大妈提出了一个新的需求让你的程序去增加一个功能,实现两个数相乘求积,学习了设计原则的“开闭原则”的老哥肯定都清楚现在需要去扩展一个新的乘法功能,我需要添加而不是去修改现有类A,好那么我就扩展一个类B。让类B去继承类A。具体代码如下
/**
* @author zhengzhong on 2017/10/10.
* email zheng_zhong@163.com
* @version V1.0.0
*/
public class B extends A {
@Override
public int add(int a, int b) {
return a-b;
}
public int multi(int a,int b){
return a*b;
}
}
/**
* @author zhengzhong on 2017/10/10.
* email zheng_zhong@163.com
* @version V1.0.0
*/
public class Client {
private static A a;
public static void main(String[] args) {
a=new B();
System.out.println("2+1="+a.add(2,1));
System.out.println("2*1="+((B)a).multi(2,1));
}
}
2+1=1
2*1=2
Process finished with exit code 0
运行发现我们新添加的乘法功能运行正常,但是我们以前的加法功能确出错了,仔细检查发现是由于我们的继承A类时不小心重写了父类的add方法,这导致了加法功能的bug。
问题的解决:
通过让子类B继承A时遵守里式替换原则的相关规则,就可以避免当功能扩展时影响以前的功能。
接下来我们看一下关于里式替换原则的定义:
定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。
相信第一次看完定义的各位老哥肯定有点晕,不要紧让我们来进一步解释一下里式替换原则,里式替换原则的大体包含一下四层含义:
1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
2. 子类中可以增加自己特有的方法。
3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
举例解释一下这四种含义:
1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
这一条参考我们上面的例子相信大家都可以理解,当重写父类的非抽象方法有时会产生一些bug。所以里式替换原则不建议我们重写父类中非抽象方法破坏父类中的定义好的一些契约。(见上例)
2. 子类中可以增加自己特有的方法
好我们这次来正确的解决PM大妈新增加的乘法功能。
/**
* @author zhengzhong on 2017/10/10.
* email zheng_zhong@163.com
* @version V1.0.0
*/
public class B extends A {
public int multi(int a,int b){
return a*b;
}
}
遵守里式替换原则不重写父类中的非抽象方法。子类B中添加乘法功能方法实现新增乘法功能。
3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。。
举个例子如下:
public class Father {
public void func(HashMap m){
System.out.println("执行父类...");
}
}
public class Son extends Father{
public void func(Map m){//方法的形参比父类的更宽松
System.out.println("执行子类...");
}
}
public class Client{
public static void main(String[] args) {
Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
HashMap h = new HashMap();
f.func(h);
}
}
运行结果:执行父类...
此代码示例来源网络
当客户端执行func方法时,虽然子类重载func方法,但是子类中func方法的参数要比父类的参数层级更高,初学java时我们肯定都清楚这时会根据func方法参数类型匹配执行父类的func方法,此时子类重载的方法并不会对父类的方法执行产生影响。
当重载父类中的方法时,需要注意遵守里式替换原则。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
public abstract class Father {
public abstract Map func();
}
import java.util.HashMap;
public class Son extends Father{
@Override
public HashMap func(){//方法的返回值比父类的更严格
HashMap h = new HashMap();
h.put("h", "执行子类...");
return h;
}
}
public class Client{
public static void main(String[] args) {
Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
System.out.println(f.func());
}
}
执行结果:{h=执行子类...}
此代码示例来源网络
实现父类抽象方法时,要求子类方法返回值类型小于等于父类方法返回值。如上代码,(HashMap < Map)此条现在几乎所有的编辑器会自动帮我们检查,如果大于父类的方法的返回值会给出错误提示。
好的各位老哥关于里式替换原则的介绍大体上就是这些,希望对第一次接触这个设计原则的老哥有所帮助,如果有什么问题欢迎留言指正,祝大家生活愉快!
最后欢迎对Android开发感兴趣的老哥一起讨论。