1.策略模式
1.什么是策略模式?
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
2.结合案例理解此模式
注意:完整项目代码在文章末尾
借用设计模式head first书本中的例子,我们根据一次简单的项目设计来体现策略模式的优势。
2.1 首次需求
2.1.1需求描述
我们需要做一款模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一般呱呱叫。请提供此游戏的后端支持,提供各种的鸭子api
2.1.2 程序设计
设计一个鸭子抽象类,用来定义所有鸭子的共有属性行为
/**
* 鸭子抽象类
*/
public abstract class Duck {
/**
*外观抽象方法,
因为不同鸭子的外观可能不同,需要具体的鸭子实体类去实现
*/
public abstract void disPlay();
/**
*呱呱叫行为
*/
public void Quack(){
System.out.println("Quack ! Quack!!");
}
/**
*游泳行为
*/
public void swim(){
System.out.println("游啊游~~~");
}
}
然后所有的鸭子实体类去继承它来进行编程,这样就达到了代码复用的效果:
目前游戏中只需要两类鸭子的API: MallardDuck , RedheadDuck
public class MallardDuck extends Duck {
@Override
public void disPlay() {
System.out.println("我是绿头鸭!!");
}
}
public class RedheadDuck extends Duck {
@Override
public void disPlay() {
System.out.println("我是红头鸭!!");
}
}
好了,这时我们就可以提供API了。
第一次需求完美解决了哈哈!! 又可以摸鱼了呀!!可是好景不长,没过几天,新的需求又来了。。。。。。
2.2 第二次需求
2.2.1 需求描述
产品决定给这个游戏里加上会飞的鸭子来增强游戏的可玩性。
这时,我们该怎么做呢?
2.2.2 程序设计
这时,你可能会说,这还不简单? 直接在抽象类Duck上加一个fly实现方法就好了呀,它的子类都拥有这个行为了呀,搞定!!
但是,产品说的是给游戏里加上会飞的鸭子,并不是说给所有鸭子加上飞行技能!! 于是产品让你返工。
这时,你又可能会说,这简单啊,直接修改那些本不具备飞行能力的鸭子,让他覆盖掉fly方法就好了,覆盖的方法啥都不做不就行了。
/**
* 鸭子抽象类
*/
public abstract class Duck {
/**
*外观抽象方法,
因为不同鸭子的外观可能不同,需要具体的鸭子实体类去实现
*/
public abstract void disPlay();
/**
*呱呱叫行为
*/
public void Quack(){
System.out.println("Quack ! Quack!!");
}
/**
*游泳行为
*/
public void swim(){
System.out.println("游啊游~~~");
}
public void fly(){
System.out.println("飞呀飞~~~");
}
}
public class MallardDuck extends Duck {
@Override
public void disPlay() {
System.out.println("我是绿头鸭!!");
}
}
public class RedheadDuck extends Duck {
@Override
public void disPlay() {
System.out.println("我是红头鸭!!");
}
/**
*红头鸭不会飞,覆盖掉父类的fly方法
*/
@Override
public void fly(){}
}
嗯嗯,这次产品那边没啥好说的了。
这次的需求勉强应付过去了吧,可是,在你修改的过程中,你会发现,你要如果要更改以前某些会飞的鸭子为不会飞,这样你需要一个一个鸭子的去覆盖,工作量也就上来了。
2.3 第三次需求
2.3.1 需求描述
现在需要加入一个诱饵鸭子,这个鸭子及不会飞,也不会叫。
2.3.2 程序设计
public class DecoyDuck extends Duck {
@Override
public void disPlay() {
System.out.println("我是诱饵鸭子!!");
}
/**
*诱饵鸭不会飞,覆盖掉父类的fly方法
*/
@Override
public void fly(){}
/**
*诱饵鸭不会叫,覆盖掉父类的方法
*/
@Override
public void quack(){}
}
这样做虽然可以,但是我们很快就意识到不对劲,按照这样的频率进行更新,每当有新的鸭子子类出现,我们就要被迫检查并可能需要覆盖父类的方法,这简直是工作量爆炸好吧。
意识到这点后,我们决定优化设计,让其能轻松应对频繁的更新而让我们的工作量很少。
2.4 设计优化
于是,技术主管让员工们想出一个较好的设计方案来进行优化,并且悬赏,解决这个问题将奖励10万元。
员工A的优化方案:
员工A: 我们可以把Duck类的fly方法提取出来,放进一个叫Flyable的接口里,这么一来,只有会飞的鸭子去实现这个接口,同样的方式,我们也可以设计一个Quackable接口。
主管思考一会儿后回答:这真是一个超笨的主意好吧,你没发现如果这样做重复的代码会变多吗? 如果你认为覆盖几个方法是差劲的,那么对于你这种做法,对于50个鸭子类都要修改以下飞行的行为,你又怎么说?
员工A: 好吧,我承认这确实不是个好主意。
员工B的方案:
员工B经过思考后,总结出了使用继承并不能很好的解决问题,因为鸭子的行为在子类里是不断改变的,并且让所有的子类都有这些行为是不恰当的。 后面员工A的方法虽然解决了一些问题,但是java接口不具有实现代码的特性,所以继承接口无法达到代码的复用。 这意味着: 无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心就会出现新的错误。
这时,他记得一个设计原则:找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
这个原则可能会有帮助,那么从何开始呢?
首先分析出变化和非变化的部分: 我们知道Duck类的fly() 和quack() 会随着鸭子的不同而改变。所以它是变化的部分,我们需要将这两个行为从Duck类中分离出来,我们可以建立一组新类来代表每个行为。
这时,员工B又想到一个原则:针对接口编程,而不是针对实现编程。 所以他决定把这组新类用接口描述,然后又具体的实体类去实现它。
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
这样,具体的fly和quack的实现实体的动作实体类中就可以了:
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("我飞起来了!!");
}
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱呱!");
}
}
好啦,现在把这些可变的部分从Duck中分离出来了,现在Duck是这样的:
/**
* 鸭子抽象类
*/
public abstract class Duck {
/**
*外观抽象方法,
因为不同鸭子的外观可能不同,需要具体的鸭子实体类去实现
*/
public abstract void disPlay();
/**
*游泳行为
*/
public void swim(){
System.out.println("游啊游~~~");
}
}
员工B卡住了,他想,虽然这些动作抽离出来了,但是要怎样用在具体的鸭子类里呢?
这时他想到了一个原则:多用组合,少用继承
于是他有了下面的做法:
public class MallardDuck extends Duck {
FlyBehavior fly;
QuackBehavior quack;
@Override
public void disPlay() {
System.out.println("我是绿头鸭!!");
}
public void fly(){
fly.fly();
}
public void quack(){
quack.quack();
}
public void setFlyBehavior(FlyBehavior fly){
this.fly = fly;
}
public void setQuackBehavior(QuackBehavior qb){
this.quack = qb;
}
}
public class RedheadDuck extends Duck {
FlyBehavior fly;
QuackBehavior quack;
@Override
public void disPlay() {
System.out.println("我是红头鸭!!");
}
public void fly(){
fly.fly();
}
public void quack(){
quack.quack();
}
public void setFlyBehavior(FlyBehavior fly){
this.fly = fly;
}
public void setQuackBehavior(QuackBehavior qb){
this.quack = qb;
}
}
员工B仔细一看,不对啊,还是又一些重复的代码,能不能不要彻底丢弃继承呢? 适当的时候还是用一下,于是他进行了优化,得到下面的做法:
将组合的动作在父类做了,子类直接使用即可。
/**
* 鸭子抽象类,组合了飞行接口和呱呱接口,
* 实现了面对抽象编程
*/
public abstract class Duck {
//接口,实现可变
FlyBehavior flyBehavior;
//接口,实现可变
QuackBehavior quackBehavior;
public Duck(){
}
public abstract void disPlay();
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
public void swim(){
System.out.println("All ducks float , even decoys!!");
}
public void setFlyBehavior(FlyBehavior fb){
this.flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb){
this.quackBehavior = qb;
}
}
这时,子类的编写既可以简化成下面的:
public class MallardDuck extends Duck {
@Override
public void disPlay() {
System.out.println("我是绿头鸭!!");
}
public MallardDuck(){
}
public MallardDuck(QuackBehavior quackBehavior, FlyBehavior flyBehavior){
setQuackBehavior(quackBehavior);
setFlyBehavior(flyBehavior);
}
}
至此,一个模式就此诞生!! 策略模式!! 员工B的这个模式得到了老板的认同,他拿到了奖金!!
策略模式定义了算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
作者:small-water
出处:https://www.cnblogs.com/small-water/p/17870047.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)