正确示范
public class OpenClosed2 { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); graphicEditor.drawShape(new Triangle());//+新增绘制三角形 } } //这是一个用于绘图的类 class GraphicEditor { //接收 Shape 对象,调用 draw 方法 public void drawShape(Shape s) { s.draw(); } } abstract class Shape { int m_type; public abstract void draw(); } //以前就写好的 class Rectangle extends Shape { Rectangle() { super.m_type = 1; } @Override public void draw() { System.out.println("绘制矩形"); } } //以前就写好的 class Circle extends Shape { Circle() { super.m_type = 2; } @Override public void draw() { System.out.println("绘制圆形"); } } //+新增绘制三角形 class Triangle extends Shape { Triangle() { super.m_type = 3; } @Override public void draw() { System.out.println("绘制三角形"); } }
绘制矩形 绘制圆形 绘制三角形
3、里氏替换原则
定义
子类可以扩展父类的功能,但不能改变原有父类的功能。
详细描述
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
根据上述理解,对里氏替换原则的定义可以总结如下:
● 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
● 子类中可以增加自己特有的方法
● 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
● 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
枪支抽象类: public abstract class AbstractGun { public abstract void shoot(); } 手枪实现类: public class HandGun extends AbstractGun { public void shoot() { System.out.println("手机射击"); } } public class Rifle extends AbstractGun { public void shoot() { System.out.println("步枪射击"); } } 士兵实现类: public class Soldier { private AbstractGun gun; public void setGun(AbstractGun gun) { this.gun = gun; } public void killEnemy() { System.out.println("士兵杀敌人"); gun.shoot(); } } 场景类: public class Client { public static void main(String[] args) { Soldier sanMao = new Soldier(); sanMao.setGun(new Rifle()); sanMao.killEnemy(); } }
注意
在类中调用其他类时务必要使用父类或者接口(例如Solider类的setGun(AbstractGun gun)方法),否则说明类的设计已经违背了LSP原则。
现在有个玩具枪该怎么定义?直接继承AbstractGun类吗?如下:
public class ToyGun extends AbstractGun { @Override public void shoot() { //玩具枪不能像真枪杀敌,不实现 } } 场景类: public class Client { public static void main(String[] args) { Soldier sanMao = new Soldier(); sanMao.setGun(new ToyGun()); sanMao.killEnemy(); } }
在这种情况下,士兵拿着玩具枪杀敌,发现业务调用类已经出现了问题,正常的业务逻辑运行结果是不正确的。(因为玩具枪并不能杀敌)ToyGun应该脱离继承,建立一个独立的类,可以与AbstractGun建立关联委托关系。类图如下:
注意
如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中发生重写或者重载,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。
2、子类中可以增加自己特有的方法
说下面两层含义之前先要明白 重载 重写(覆盖) 的区别:
重写(覆盖)的规则:
1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载.
2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。
3、重写的方法的返回值必须和被重写的方法的返回一致;
4、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;
5、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
6、静态方法不能被重写为非静态的方法(会编译出错)。
重载的规则:
1、在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样);
2、不能通过访问权限、返回类型、抛出的异常进行重载;
3、方法的异常类型和数目不会对重载造成影响;
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
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 main(String[] args) { invoker(); } public static void invoker(){ Son son = new Son();//子类对象 HashMap map=new HashMap<>(); son.doSomething(map); } }
运行是”父类被执行了”,这是正确的,父类方法的参数是HashMap类型,而子类的方法参数是Map类型,子类的参数类型范围比父类大,那么子类的方法永远也不会执行。
如果我们反过来让父类的参数类型范围大于子类,并在调用时用子类去调用,我们会发现打印时的结果是”子类被执行了”,这就违反了里氏替换原则,在开发中很容易引起业务逻辑的混乱,所以类的方法重载父类的方法时,方法的前置条件(形参)要比父类方法的输入参数更宽松(相同也可以)。
4、当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
父类的一个方法的返回值是一个类型T,子类的相同方法(重载或者重写)的返回值为S,那么里氏替换原则就要求S必须小于等于T。
4、接口隔离
原则介绍
客户端不应该依赖它不需要的接口, 即一个类对另一个类的依赖应该建立在最小的接口上。
示范
//A、B总的接口 interface InterfaceAll { void operation1(); void operation2(); void operation3(); } //实现类A只用InterfaceAll中的operation1、operation2方法,所以实现两个方法 class A implements InterfaceAll { @Override public void operation1() { System.out.println("A 实现了 operation1..."); } @Override public void operation2() { System.out.println("A 实现了 operation2..."); } @Override public void operation3() { //A用不到,但是还需要空实现 } }
//实现类B只用InterfaceAll中的operation1、operation3方法,所以实现两个方法 class B implements InterfaceAll { @Override public void operation1() { System.out.println("B 实现了 operation1..."); }
@Override public void operation2() { //B用不到,但是还需要空实现 } @Override public void operation3() { System.out.println("B 实现了 operation3..."); } } 正确示范 //接口Interface1 interface Interface1 { void operation1(); } //接口Interface2 interface Interface2 { void operation2(); } //接口Interface3 interface Interface3 { void operation3(); } //实现类A用Interface1中的operation1和Interface2中的operation2 class A implements Interface1, Interface2 { @Override public void operation1() { System.out.println("A 实现了 operation1..."); } @Override public void operation2() { System.out.println("A 实现了 operation2..."); } } //实现类B用Interface1中的operation1和Interface3中的operation3 class B implements Interface1, Interface3 { @Override public void operation1() { System.out.println("B 实现了 operation1..."); } @Override public void operation3() { System.out.println("B 实现了 operation3..."); } }