继承引发的问题
子类继承父类,主要目的是为了复用父类的方法
代码结构
对应相关代码
Animal
package headfirst.hd.strategy.base;
public class Animal {
//呼吸
public void breath() {
System.out.println("我是动物,我用鼻子呼吸");
}
//看
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
Dog
package headfirst.hd.strategy.base;
public class Dog extends Animal {
}
Person
package headfirst.hd.strategy.base;
public class Person extends Animal {
}
DriveTest
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Person;
public class DriveTest {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
Dog dog = new Dog();
dog.breath();
dog.look();
}
}
符合继承
-子类具有相同的行为,这里person(人),dog(狗)都具有呼吸,看行为
-当子类具有相同行为,且具有相同的实现,这里person(人),dog(狗)都是用鼻子呼吸,用眼睛看强调内容
子类具有相同的行为,不具有相同的实现
动物都有move(移动)方法,但是person(人)使用两只脚移动,dog(狗)是用四只脚移动
这里我们引入接口,如下图所示
对应相关代码
Animal
package headfirst.hd.strategy.interfaces;
public interface Animal {
void move();
}
Dog
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Animal;
public class Dog implements Animal {
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
}
Person
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Animal;
public class Person implements Animal {
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
}
DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.impl.Dog;
import headfirst.hd.strategy.impl.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.move();
Dog dog = new Dog();
dog.move();
}
}
引入抽象类,使Animal具有三个行为
Animal当前有breath,look,move方法,breath与look是父类提供实现,子类共同拥有,move方法是父类提供行为,子类负责具体实现。
对应相关代码
Animal
package headfirst.hd.strategy.base;
public abstract class Animal {
//呼吸
public void breath() {
System.out.println("我是动物,我用鼻子呼吸");
}
//看
public void look() {
System.out.println("我是动物,我用眼睛看");
}
public abstract void move();
}
Dog
package headfirst.hd.strategy.base;
public class Dog extends Animal {
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
}
Person
package headfirst.hd.strategy.base;
public class Person extends Animal {
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
}
DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
person.move();
Dog dog = new Dog();
dog.breath();
dog.look();
dog.move();
}
}
引入抽象类,使Animal具有三个行为
Animal当前有breath,look,move方法,breath与look是父类提供实现,子类共同拥有,move方法是父类提供行为,子类负责具体实现。
对应相关代码
Animal
package headfirst.hd.strategy.base;
public abstract class Animal {
//呼吸
public void breath() {
System.out.println("我是动物,我用鼻子呼吸");
}
//看
public void look() {
System.out.println("我是动物,我用眼睛看");
}
public abstract void move();
}
Dog
package headfirst.hd.strategy.base;
public class Dog extends Animal {
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
}
Person
package headfirst.hd.strategy.base;
public class Person extends Animal {
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
}
DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
person.move();
Dog dog = new Dog();
dog.breath();
dog.look();
dog.move();
}
}
引入implements,使Animal具有三个行为
Animal当前有breath,look,breath与look是父类提供实现,子类共同拥有,move方法是子类通过实现其他接口,获取行为,子类负责具体实现。
对应相关代码
Moveable
package headfirst.hd.strategy.interfaces;
public interface Moveable {
void move();
}
package headfirst.hd.strategy.base;
public class Animal {
//呼吸
public void breath() {
System.out.println("我是动物,我用鼻子呼吸");
}
//看
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
Dog
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Moveable;
public class Dog extends Animal implements Moveable {
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
}
Person
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Moveable;
public class Person extends Animal implements Moveable {
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
}
DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
person.move();
Dog dog = new Dog();
dog.breath();
dog.look();
dog.move();
}
}
不符合继承
- 新增加子类,不再满足原子类具有相同的行为
- 父类添加新行为,子类部分满足,部分不满足
继承问题1-新增加子类,不再满足原子类具有相同的行为
新增鱼子类,鱼不是用鼻子呼吸的,这里我认为是用鱼鳃呼吸的,继承了错误的行为,因为鱼不是用鼻子呼吸的。
新增蝙蝠子类,蝙蝠是不能用眼睛看到,继承了不该拥有的行为,,但是他可以用耳朵听,这里添加listen方法
使用abstract方式关系图说明,图如下
解决蝙蝠bat拥有不该拥有的look行为
关键方法使用implement方式关系图说明,图如下
两种方式的区别
- 继承,继承了错了方法,要把错误方法修改或者去掉,使用重写父类方法方式,简单理解可以理解为在做减法,把不合适的行为减掉
- implement,为子类添加方法,如果子类有该行为,则实现该接口,简单理解可以理解为做加法,为子类添加行为
但是在类规模大的情况下,两种方法后期维护都极其困难,现在假设一种情景。当前有一个父类,父类下有20个子类。为父类添加A方法,其中,子类中,有10个子类与父类A方法相同,5个子类与A方法不同,需要修改,5个子类没有A方法。
1. 用继承方法,那么子类需要重写10个子类,其中5个需要重写新行为,5个需要覆盖掉行为
2. 用implement方法,那么子类需要实现15个子类,其中10个子类实现代码一致,没有达到代码复用,如果这10个子类实现后期有变化,需要修改10个子类的实现
综合以上分析,继承与实现都无法满足我们的要求,幸运的是,有一种设计模式能解决这个问题
策略模式 strategy
为什么会有模式,因为我们需求总是在不断变化,导致我们不得不修改代码,这个模式可以应对这些问题的发生,现在,在这里将这些问题总结出来。
- 父类拥有A方法,新增子类,子类没有A方法,或许说,需要重写的A方法,大前提,新增子类不影响之前的代码
- 父类增加B方法,子类部分满足B方法,部分不满足,上面已经描述了这个情况,很复杂,如何解决代码复用大前提,新增B方法不影响之前的代码
- 父类A方法,实现发生变化,我们只希望修改一处代码
- 子类A方法,实现发生变化(可能子类有几个类实现都是相同的),我们只希望修改一处代码
个人总结,strategy模式,将变化的方法,独立封装成接口
封装变化
将子类中,变化大的方法封装,这里先将breath方法封装起来,因为子类中有不同的实现。
使用下图模型说明,这是改造前的UML图
对应代码
Animal
package headfirst.hd.strategy.base;
public abstract class Animal {
//呼吸
public void breath() {
System.out.println("我是动物,我用鼻子呼吸");
}
public abstract void move();
}
Lookable
package headfirst.hd.strategy.interfaces;
public interface Lookable {
void look();
}
Dog
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Lookable;
public class Dog extends Animal implements Lookable {
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
Person
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Lookable;
public class Person extends Animal implements Lookable {
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
Fish
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Lookable;
public class Fish extends Animal implements Lookable {
@Override
public void move() {
System.out.println("我是一只鱼,我用手划水移动");
}
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
@Override //重写父类方法,鱼不是鼻子呼吸的
public void breath() {
System.out.println("我是一只鱼,我用鱼鳃呼吸");
}
}
Bat
package headfirst.hd.strategy.base;
//蝙蝠不能用眼睛看,不实现看行为
public class Bat extends Animal {
@Override
public void move() {
System.out.println("我是一只蝙蝠,我用翅膀飞行移动");
}
//蝙蝠使用耳朵听得,增加listen方法
public void listen() {
System.out.println("我是一只蝙蝠,我可以用耳朵判断距离");
}
}
测试类DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Bat;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Fish;
import headfirst.hd.strategy.base.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
person.move();
Dog dog = new Dog();
dog.breath();
dog.look();
dog.move();
Fish fish = new Fish();
fish.move();
fish.look();
fish.breath();
Bat bat = new Bat();
bat.move();
bat.breath();
bat.listen();
}
}
运行结果
代码符合我们预期设计
改造breath方法
将breath方法封装成独立的接口
对应代码
Breathable
package headfirst.hd.strategy.interfaces;
public interface Breathable {
void breath();
}
NormalBreath
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Breathable;
public class NormalBreath implements Breathable {
@Override // 呼吸
public void breath() {
System.out.println("我是动物,我用鼻子呼吸");
}
}
FishBreath
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Breathable;
public class FishBreath implements Breathable {
@Override // 呼吸
public void breath() {
System.out.println("我是一只鱼,我用鱼鳃呼吸");
}
}
Animal
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Breathable;
public abstract class Animal {
Breathable breathable;
//呼吸
public void breath() {
breathable.breath();
}
public abstract void move();
}
Lookable
package headfirst.hd.strategy.interfaces;
public interface Lookable {
void look();
}
Dog
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Lookable;
public class Dog extends Animal implements Lookable {
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
Person
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Lookable;
public class Person extends Animal implements Lookable {
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
Fish
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Lookable;
public class Fish extends Animal implements Lookable {
@Override
public void move() {
System.out.println("我是一只鱼,我用手划水移动");
}
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
@Override //重写父类方法,鱼不是鼻子呼吸的
public void breath() {
System.out.println("我是一只鱼,我用鱼鳃呼吸");
}
}
Bat
package headfirst.hd.strategy.base;
//蝙蝠不能用眼睛看,不实现看行为
public class Bat extends Animal {
@Override
public void move() {
System.out.println("我是一只蝙蝠,我用翅膀飞行移动");
}
//蝙蝠使用耳朵听得,增加listen方法
public void listen() {
System.out.println("我是一只蝙蝠,我可以用耳朵判断距离");
}
}
测试类DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Bat;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Fish;
import headfirst.hd.strategy.base.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
person.move();
Dog dog = new Dog();
dog.breath();
dog.look();
dog.move();
Fish fish = new Fish();
fish.move();
fish.look();
fish.breath();
Bat bat = new Bat();
bat.move();
bat.breath();
bat.listen();
}
}
测试结果
改造breath方法后的优点
抛出之前预留的问题,来看看改造后的方法是否能够满足,这里针对breath方法
- 父类拥有A方法,新增子类,子类没有A方法,或许说,需要重写的A方法,大前提,新增子类不影响之前的代码
- 父类增加B方法,子类部分满足B方法,部分不满足,上面已经描述了这个情况,很复杂,如何解决代码复用大前提,新增B方法不影响之前的代码
- 父类A方法,实现发生变化,我们只希望修改一处代码
- 子类A方法,实现发生变化(可能子类有几个类实现都是相同的),我们只希望修改一处代码
- 新增Snail (蜗牛)子类,蜗牛是通过皮肤呼吸的,对于蜗牛,我们只用为其添加新的行为实现SnailBreath ,不会影响已经实现的代码,满足条件1
SnailBreath
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Breathable;
public class SnailBreath implements Breathable {
@Override // 呼吸
public void breath() {
System.out.println("我是一只蜗牛,我用皮肤呼吸");
}
}
- 在breath不涉及情况2,pass
- 父类定义需要修改,我们只需要修改定义一个地方,子类所有都会被修改到,例如,呼吸前,我们的有些激素要先分泌一下,满足情况3
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Breathable;
public abstract class Animal {
Breathable breathable;
//呼吸
public void breath() {
System.out.println("分泌激素");
breathable.breath();
}
public abstract void move();
}
- 当通过鼻子呼吸方式实现,需要修改,我们只需要修改NormalBreath一个地方,即可,(反例,当需要修改用眼睛看look方法,我们需要修改Person,Dog,Fish等多处),满足情况4
改造look方法
将look方法封装成独立的接口,为蝙蝠(bat)也封装一个方法,如图标1所示,改造后UML图
对应代码,这里已改造步骤展示代码
- 将有变化的方法封装成独立的接口,且子类维护一组实现
Lookable
package headfirst.hd.strategy.interfaces;
public interface Lookable {
void look();
}
NormalLook
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Lookable;
public class NormalLook implements Lookable {
@Override
public void look() {
System.out.println("我是动物,我用眼睛看");
}
}
CannotLook
package headfirst.hd.strategy.impl;
import headfirst.hd.strategy.interfaces.Lookable;
public class CannotLook implements Lookable {
@Override
public void look() {
System.out.println("我是蝙蝠,我不能用眼睛看");
}
}
- 改造含有业务的父类,为其添加一个改造后的接口属性,并调用接口方法
Animal
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.interfaces.Breathable;
import headfirst.hd.strategy.interfaces.Lookable;
public abstract class Animal {
Breathable breathable;
Lookable lookable; //添加属性
//呼吸
public void breath() {
breathable.breath();
}
//看
public void look() { //调用接口
lookable.look();
}
public abstract void move();
}
- 改造业务子类
Person
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.impl.NormalBreath;
import headfirst.hd.strategy.impl.NormalLook;
public class Person extends Animal {
public Person() {
breathable = new NormalBreath();
lookable = new NormalLook();
}
@Override
public void move() {
System.out.println("我是一个人,我用两只脚走路");
}
}
Fish
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.impl.FishBreath;
import headfirst.hd.strategy.impl.NormalLook;
public class Fish extends Animal {
public Fish() {
breathable = new FishBreath();
lookable = new NormalLook();
}
@Override
public void move() {
System.out.println("我是一只鱼,我用手划水移动");
}
}
Dog
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.impl.NormalBreath;
import headfirst.hd.strategy.impl.NormalLook;
public class Dog extends Animal {
public Dog() {
breathable = new NormalBreath();
lookable = new NormalLook();
}
@Override
public void move() {
System.out.println("我是一个只狗,我用四只脚走路");
}
}
Bat
package headfirst.hd.strategy.base;
import headfirst.hd.strategy.impl.CannotLook;
import headfirst.hd.strategy.impl.NormalBreath;
//蝙蝠不能用眼睛看,不实现看行为
public class Bat extends Animal {
public Bat() {
breathable = new NormalBreath();
lookable = new CannotLook();
}
@Override
public void move() {
System.out.println("我是一只蝙蝠,我用翅膀飞行移动");
}
//蝙蝠使用耳朵听得,增加listen方法
public void listen() {
System.out.println("我是一只蝙蝠,我可以用耳朵判断距离");
}
}
测试类
DriveTest2
package headfirst.hd.strategy.run;
import headfirst.hd.strategy.base.Bat;
import headfirst.hd.strategy.base.Dog;
import headfirst.hd.strategy.base.Fish;
import headfirst.hd.strategy.base.Person;
public class DriveTest2 {
public static void main(String[] args) {
Person person = new Person();
person.breath();
person.look();
person.move();
Dog dog = new Dog();
dog.breath();
dog.look();
dog.move();
Fish fish = new Fish();
fish.move();
fish.look();
fish.breath();
Bat bat = new Bat();
bat.look();
bat.move();
bat.breath();
bat.listen();
}
}
满足业务场景
改造look方法后的优点
抛出之前预留的问题,来看看改造后的方法是否能够满足,这里针对look方法
- 父类拥有A方法,新增子类,子类没有A方法,或许说,需要重写的A方法,大前提,新增子类不影响之前的代码
- 父类增加B方法,子类部分满足B方法,部分不满足,上面已经描述了这个情况,很复杂,如何解决代码复用大前提,新增B方法不影响之前的代码
- 父类A方法,实现发生变化,我们只希望修改一处代码
- 子类A方法,实现发生变化(可能子类有几个类实现都是相同的),我们只希望修改一处代码
ps: 1,3,4点优点和breath方法一致,这里我们谈谈2
假设look方法是父类新增的方法,子类中有三个具有该行为,一个不具有,我们将其设计为如下图,解决问题
现在假设,新增一个子类,他是用其他器官来看,我们只需要改变仅仅做如下改变,不会影响之前的任务代码
完美解决问题2
是否有必要改造move方法
基于当前子类,由于当前每个子类move方法实现都不同,没有任务重合度,没有任何复用,反之,观察NormalLook,NormalBreath子类,业务子类中有多个子类复用,如果后期增加猫,大象,老虎等这些和狗一样都是四只脚走路的,代码实现重复时,可考虑这种模式
总结
以上就是策略模式 strategy
用到了面向对象的三个原则
- 封装变化
策略模式封装的是变化的方法,后期我们还会封装变量等(状态模式) - 多用组合,少用继承
多用图中圈出来这种调用设计关系,组合 - 针对接口编程,不针对具体的实现编程
这是一个很完美的诠释,具体实现在子类,子类会选择何种实现,如蝙蝠bat
自己体会吧