《HF 设计模式》 C1 策略模式
作者:互联网
文章目录
1 模拟鸭子系统
在一个模拟鸭子游戏的应用中,存在若干种鸭子,有绿头鸭,红头鸭,橡皮鸭,木头鸭等,每一种鸭子都有其独特的行为,该如何设计这个系统,并保证以后有更多的鸭子加入时,系统具有良好的可扩展性和可维护性?
1.1 粗糙的使用继承完成
对于每一种鸭子,我们先思考它们的特点:
重量
高度
...
飞行行为,有的鸭子会飞,有的鸭子不会...
鸣叫行为,有的鸭子呱呱叫,有的鸭子吱吱叫,有的鸭子不会叫...
外貌描述,(绿头,纤细)绿头鸭,(塑料,可爱)模型鸭...
...
不管是绿头鸭,还是红头鸭,模型鸭,其本质都是鸭子,很自然的我们想到了使用继承来解决这个问题:
Duck父类:
/**
* @author 雫
* @date 2021/3/1 - 10:17
* @function 所有鸭子的父类
* 父类中鸭子的行为方法等待被覆盖
*/
public class Duck {
protected int weight;
protected int height;
public Duck(int weight, int height) {
this.weight = weight;
this.height = height;
}
public void fly() {}
public void quack() {}
public void show() {}
}
MallardDuck绿头鸭子类:
/**
* @author 雫
* @date 2021/3/1 - 10:23
* @function 绿头鸭
*/
public class MallardDuck extends Duck {
public MallardDuck(int weight, int height) {
super(weight, height);
}
@Override
public void fly() {
System.out.println("会飞,飞的很低");
}
@Override
public void quack() {
System.out.println("呱呱叫");
}
@Override
public void show() {
System.out.println("绿头,纤细");
}
}
ModelDuck模型鸭子类:
/**
* @author 雫
* @date 2021/3/1 - 10:25
* @function 模型鸭
*/
public class ModelDuck extends Duck {
public ModelDuck(int weight, int height) {
super(weight, height);
}
@Override
public void fly() {
System.out.println("不会飞");
}
@Override
public void quack() {
System.out.println("不会叫");
}
@Override
public void show() {
System.out.println("塑料,可爱");
}
}
测试:
1.2 使用继承后的问题
使用继承虽然能粗糙的完成模拟鸭子系统,但是随着系统迭代,我们要求给所有鸭子加上游泳,进食,休息等方法
自然的想到去父类Duck中增加新的方法:
但是在父类中增加方法后,我们的ModelDuck可以直接调用swim()方法!
这明显是不合理也不应该发生的,直接的解决方法是在ModelDuck中重写父类的方法,打印不会游泳,或者直接更改swim的权限为private,但是随着系统的升级迭代,当我们需要增加或修改父类Duck中的一个方法时,伴随而来的就是所有子类成千上万次的重写父类方法,这毫无意义
继承虽然能保留“好东西”,但是牵一发而动全身
1.3 软件是不断变化的
不管当初软件设计的多好,一段时间后,总需要成长与改变,否则软件就会变成一堆无意义的碎片
驱动改变的因素有很多:
1,数据库更换
2,客户需要新功能
3,项目需要移植到别的平台
4,和竞品竞争,需要迭代升级
5,为了面对更多的客户,需要重构代码
6,出现了新的技术,可以让原先的代码更好
7,预算有限,无法租用大量高质量服务器,只能在代码上面优化
软件开发完成前和软件开发完成后,“后”需要的精力和时间更多,我们需要花许多时间在系统的维护和变化上,比原来开发所需的时间还要多,所以应该在设计时就致力于提高可维护性和可扩展性
再回到模拟鸭子系统的问题,继承并不能很好地解决问题,因为鸭子的子类在不断的发生变化
除去继承,自然就想到了接口,在使用接口完成问题前,学习一个设计原则
设计原则:
找出应用中可能需要变化之处,把它们独立出来
不要和那些不需要变化的代码放在一起
该设计原则,让系统的某部分改变不会影响其它部分,将使得代码变化引起的不经意后果变少,系统变得更有弹性
1.4 使用接口完成问题
回顾刚才的设计原则,将不变的和改变的分开,再次思考这个模拟鸭子系统,我们再引入一个新的设计原则:
设计原则:
针对接口编程,而不是针对实现编程
不变:
1,重量
2,高度
改变:
1,飞行行为
2,鸣叫行为
3,外貌描述
对于上述改变的行为,采用接口来处理,对于飞行,采用一个统一的飞行接口FlyBehavior,若干种具体飞行方式来实现FlyBehavior接口
FlyBehavior:
/**
* @author 雫
* @date 2021/3/1 - 11:19
* @function 飞行行为接口
*/
public interface FlyBehavior {
void fly();
}
具体的飞行类:
/**
* @author 雫
* @date 2021/3/1 - 11:21
* @function
*/
public class CantFly implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞");
}
}
/**
* @author 雫
* @date 2021/3/1 - 11:21
* @function
*/
public class FlyHigh implements FlyBehavior {
@Override
public void fly() {
System.out.println("可以飞的很高");
}
}
我们仍然创建一个父类,这个父类仅包含“不变”的重量和高度,而且能够“动态的”根据子类的特征来进行飞行或叫等行为
Duck父类:
/**
* @author 雫
* @date 2021/3/1 - 11:18
* @function 所有鸭子的父类
*/
public class Duck {
protected int weight;
protected int height;
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public Duck(int weight, int height) {
this.weight = weight;
this.height = height;
}
public void fly() {
this.flyBehavior.fly();
}
public void quack() {
this.quackBehavior.quack();
}
}
MallardDuck:
/**
* @author 雫
* @date 2021/3/1 - 11:24
* @function 绿头鸭
*/
public class MallardDuck extends Duck {
public MallardDuck(int weight, int height) {
super(weight, height);
this.flyBehavior = new FlyHigh();
this.quackBehavior = new Quack();
}
}
ModelDuck:
/**
* @author 雫
* @date 2021/3/1 - 11:33
* @function 模型鸭
*/
public class ModelDuck extends Duck {
public ModelDuck(int weight, int height) {
super(weight, height);
this.flyBehavior = new CantFly();
this.quackBehavior = new CantQuack();
}
}
我们把接口当作成员,在生产具体对象时,通过在构造器中更改这些特殊成员的值,来动态地生成特性不一的鸭子
上述代码中两个对象调用的是父类中的quack()和fly()方法,而父类不是直接执行,而是委托行为类处理
对于这种实现方式,对于FlyBehavior接口,我们可以创建很多实现类来模拟鸭子不同地飞行行为,在生成一种鸭子对象时,在构造器中决定该鸭子的行为特征,这样就可以通过父类中已被调整好的方法来模拟鸭子的行为
这样做,可以让各种鸭子行为与鸭子分离,这些FlyBehavior的实现类也可以完成代码复用,避免了重复的代码,也可以新增/替换一些行为,这些行为的更改通过这种方式尤为简单,这样不仅利用了继承的"复用",还避免了继承的包袱
1.5 使用set方法替换行为
上述的设计虽然让整个系统具有了弹性,但是有一点存在缺陷,行为特征的描述被放在了构造器里,一旦生成该对象,该对象不能再改变
但考虑如下的场景,我要生成两个绿头鸭,绿头鸭A身体健康,绿头鸭B存在残缺,绿头鸭A可以飞的很高且可以叫,而绿头鸭B不能飞,只能叫
但是之前的代码绿头鸭类已经写好,默认构造生成了能飞能叫的绿头鸭,这里不可能为B生成一个类并写好它的默认构造,这就是同类对象替换行为的需求,自然想到的set方法
我们回到Duck,为两个接口类型的成员添加set方法:
/**
* @author 雫
* @date 2021/3/1 - 11:18
* @function 所有鸭子的父类
*/
public class Duck {
protected int weight;
protected int height;
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public Duck(int weight, int height) {
this.weight = weight;
this.height = height;
}
public void fly() {
this.flyBehavior.fly();
}
public void quack() {
this.quackBehavior.quack();
}
}
有了set方法,就可以生成鸭子对象后替换它的行为:
1.6 多用组合,少用继承
对于每个行为,我们为其准备一系列的实现类,这些实现类称为一族算法,这些“算法”被用于构建对象,为对象赋予特征或替换对象的同类特征
在模拟鸭子的系统中,采用了组合的模式,每一个鸭子都有FlyBehavior和QuackBehavior,将这两个类结合起来使用,这就是组合
这里再次引入一个设计原则:
设计原则:
多用组合,少用继承
使用组合建立的系统具有很大的弹性,不仅可以将算法族封装成类,更可以在生成对象后改变其特征
1.7 策略模式
策略模式:定义算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户
即为某个可能变化的功能,设计一个接口,为该接口扩展出一系列实现类,在生成该对象时再赋予该对象特有的特征,让功能的实现与对象脱离,并可以替换该特征,将对象和行为分离,以降低耦合,提高代码复用
1.8 关于设计模式
我们在开发的过程中大量使用Java API,但库和框架无法帮助我们将应用组织成容易了解,容易维护,具有弹性的结构,为此需要设计模式,当学习过设计模式后,就应该在新设计中采用它们,或者利用设计模式重构混乱的旧代码
知道抽象,继承,多态并不意味了解了设计模式,设计模式关心的是建立弹性的设计,可以维护,可以应付变化,良好的设计必须满足:可复用,可扩充,可维护
标签:weight,int,void,height,鸭子,C1,设计模式,HF,public 来源: https://blog.csdn.net/weixin_43541094/article/details/114254225