lizhiyong 2019-06-28
定义:一个类、模块和函数应该对扩展开放,对修改关闭。
举例说明什么是开闭原则,以书店销售书籍为例,其类图如下:
项目上线,书籍正常销售,但是我们经常因为各种原因,要打折来销售书籍,这是一个变化,我们要如何应对这样一个需求变化呢?
我们有下面三种方法可以解决此问题:
为什么使用(好处)
如何实现
定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。通俗讲:子类可以扩展父类的功能,但不能改变父类原有的功能。
为什么使用(好处)
缺点
实现原则
举例逐个讲解
子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解,事实上,子类也必须完全实现父类的抽象方法,哪怕写一个空方法,否则会编译报错。里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。如:类C1继承类C时,可以添加新方法完成新增功能,尽量不要重写父类C的方法。否则可能带来难以预料的风险:
public class C { public int func(int a, int b){ return a+b; } } public class C1 extends C{ @Override public int func(int a, int b) { return a-b; } } public class Client{ public static void main(String[] args) { C c = new C1(); System.out.println("2+1=" + c.func(2, 1)); } }
运行结果:2+1=1
上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。
子类中可以增加自己特有的方法
在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:
public class C { public int func(int a, int b){ return a+b; } } public class C1 extends C{ public int func2(int a, int b) { return a-b; } } public class Client{ public static void main(String[] args) { C1 c = new C1(); System.out.println("2-1=" + c.func2(2, 1)); } }
运行结果:2-1=1
当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参/入参)要比父类方法的输入参数更宽松
代码示例
import java.util.HashMap; public class Father { public void func(HashMap m){ System.out.println("执行父类..."); } } import java.util.Map; public class Son extends Father{ public void func(Map m){//方法的形参比父类的更宽松 System.out.println("执行子类..."); } } import java.util.HashMap; public class Client{ public static void main(String[] args) { Father f = new Son();//引用基类的地方能透明地使用其子类的对象。 HashMap h = new HashMap(); f.func(h); } }
运行结果:执行父类...
注意Son类的func方法前面是不能加@Override注解的,因为否则会编译提示报错,因为这并不是重写(Override),而是重载(Overload),因为方法的输入参数不同。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
代码示例:
import java.util.Map; 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=执行子类...}
持续更新中。。。