首页 > 其他分享 >策略模式

策略模式

时间:2024-01-25 23:33:35浏览次数:25  
标签:fly 策略 子类 接口 模式 鸭子 Duck 行为

从简单的应用开始
首先创建一个Duck父类,其他所有鸭子来继承
所有鸭子都有嘎嘎叫和戏水
例如

Duck中有
quack(); //不是抽象方法
swim(); //不是抽象方法
display(); //抽象方法
//其他鸭子方法

子类
GreenHeadDuck     
display(){        
  //绿头鸭           
}   

子类
RedHeadDuck
 display(){     
 //红头鸭
}

但是现在需要鸭子会飞
只需要在Duck类中添加一个fly()方法,所有鸭子都会继承它
例如

Duck中有
quack(); //不是抽象方法
swim(); //不是抽象方法
display(); //抽象方法
fly(); //所有子类继承
//其他鸭子方法

但是出大问题了,不是所有的Duck的子类都应该会飞,给Duck类添加新的行为时,也给某些Duck子类添加了不适合的行为

对代码的局部更新导致了非局部的副作用 例如(飞行的橡皮鸭)
可以重写橡皮鸭的fly()方法,重写为不做任何事
但是当我们给程序添加木质诱饵鸭时,诱饵鸭不会飞也不会叫,要重写quack()方法和fly()方法为不做任何事
想用继承来达到复用的目的,当涉及到维护时,效果并不那么好

继承可能不是答案,因为需求规约会保持变化,每一次程序中添加新的Duck子类,都需求被迫检查,有可能还要重写fly()和quack()直到永远
因此需要更干净的方式,只让某些(而不是全部)鸭子类型飞或嘎嘎叫
那么用接口怎么样?
把fly()从Duck父类拿出来,并且做一个带fly()方法的Flyable()接口。这样只有能够飞的鸭子实现该接口,并且有一个fly()方法...
同样,做一个Quackable,因为不是所有鸭子都能嘎嘎叫

例如
例如

接口:Flyable      接口Quackable
fly()方法         quack()方法
 
父类Duck
swim();
display();

子类
GreenHeadDuck      RedheadDuck
display()          display()
fly()              fly()
quack()            quack()
 
RubberDuck(橡皮鸭) DecoyDuck(诱饵鸭)
display()          display()
qucuk()

这个设计怎么样?

看似解决了问题,但是当你需要对所有飞行Duck子类的飞行行为做小小改变时,你的感觉怎么样?

我们知道,不是所有子类都有飞行和嘎嘎行为,因此继承不是正确的答案
让子类实现Flyable和/或Quackable解决了部分问题(没有了不恰当的飞行橡皮鸭),但是它完全摧毁了这些行为的代码复用
因此,它只是创造了一个不同的维护噩梦。当然,还有可能出现:飞行鸭子有多种飞行行为

如果有一种方法,当我们需要变更时,我们可以使用对现有代码影响最小的方式,那我们就可以花更少的时间重写代码,让程序去做更酷的事情

思考一下,软件开发中唯一不变的是什么?你唯一深信不疑的是什么?
不管在哪里工作,构造什么软件,用什么语言编程,一直伴随,唯一不变的东西是什么?

是变化,不管应用设计的多好,随着时间推移,应用必定成长和变更

现在聚焦于问题
继承不能很好的解决问题,因为各个子类的鸭子行为一直在改变,让所有子类都拥有这些事不适合的,
Flyable和Quackable接口一开始似乎还听不错(只有会飞的鸭子才实现),除了一点,Java接口通常没有实现的代码,因此没有代码复用。
无论何时,如果你需要修改一个行为,常常被迫往下追踪到所有定义了该行为的子类并修改它,在这个过程中,可能会引入新的bug

针对这种情况有一种设计原则:识别应用中变化的方面,把他们和不变的方面分开
换句话说,如果每次由新的需求,某方面的代码就要变,那么你就知道了,这个行为需要抽取出来,与其他不变的代码分离
另一种思考方式:把会变化的部分取出来并封装,这样以后就可以修改或扩展这个部分,而不会影响其他不需要变化的部分,代码变更引起的不经意后果变小,系统更加有弹性了

开始从Duck类抽出鸭子的行为
目前我们能分辨的是,除了fly()和quack()问题之外,Duck类还算正常,没有其他看起来经常变化的地方
因此,除了一些小的变更,先把Duck类放到一边

现在分离变和不变的部分。创建两组类
一组和fly相关,另一组和quack相关,每一组持有各自行为的所有实现
例如,我们可以有一个类实现嘎嘎叫,另一个实现吱吱叫,另一个实现沉默不语

我们知道fly()和quack()是Duck类中因不同鸭子而变化的部分
为了从Duck类分离这些行为,我们把两个方法都从Duck类抽出,并创建一组新的类来表示每个行为

Duck类依然是所有鸭子的父类,但我们把飞行和嘎嘎叫行为抽出来,并把他们放进另一个类结构
现在,飞行和嘎嘎叫各种获得自己的一组类

设计Duck的行为
那该怎么设计实现飞行和嘎嘎叫行为的类?

我们希望保持各种东西的弹性,毕竟一开始,是鸭子行为的僵化让我们陷入困境。我们也知道,我们要分配行为Duck的实例

例如,我们可能要实例化一个新的绿头鸭实例,并在初始化时指定特点类型的飞行行为
既然这样做了,为什么不确保我们能够动态地改变鸭子的行为?
换句话说,我们应该在Duck类中包含设置行为的方法,这样,我们就能够在运行时改变绿头鸭的飞行行为

让我们来看看第二个设计原则:针对接口编程,而不是实现编程

我们将使用接口来表示每一个行为,行为的每个实现将实现其中一个接口,因此,这一次不是Duck类实现飞行和嘎嘎接口

从现在开始,Duck的行为将放在分离的类中,实现特点行为接口的类,这样Duck类不需要知道行为的任何实现细节
我们专门做了一组类表示行为(例如,吱吱叫),这就是行为类,由行为类而不是Duck类来实现行为接口

这和我们之前所做的大有不同,行为类既来自父类的Duck的具体实现,也通过在子类自身中提供一个特化实现得到
两张情况都依赖于实现。所有代码被锁定使用特定实现,没有改变行为的空间了(除非写更多的代码)

在我们的新设计中,Duck子类将使用接口(FlyBehavior和QuackBehavior)所表示的行为,
这样行为实际的实现(实现FlyBehavior和QuackBehavior的特定具体行为)不会锁定在Duck子类中

例如
接口:FlyBehavior
void fly();

实现类
FlyWithWings                  FlyNoWay
fly(){                           fly(){
//实现鸭子飞行        //什么都不做——不会飞 
}                              }

从现在开始,Duck的行为将放在分离的类中,实现特点行为接口的类
这样,Duck类不需要知道行为的任何实现细节

针对接口编程真正的意思是针对超类型编程
接口一词在这里有多个含义,接口是一个概念,也是Java的一个构造,针对接口编程不是真的使用Java接口,
要点是通过针对超类型编程来利用多态,这样,实际的运行时对象不会被锁定到代码

我们可以重新描述针对超类型编程为变量所声明类型应该是超类型,通常是抽象类或接口,
这样,分配给这些变量的对象可以是超类型的任何具体实现,这意味这类声明时不必知道实际的对象类型

下面是一个使用多态类型的简单例子,详细一个抽象类Animal,它有两个具体实现,Dog和Cat
对实现编程是
Dog d =new Dog();
d.brak();
声明变量d为Dog类型(一个Animal的具体实现)强迫我们针对具体实现编程

而针对接口编程/超类编程则是
Animal animal =new Dog();
animal.makeSound();

我们知道它是一个Dog,但我们可以多态的使用animal引用
更棒的是,子类型再实例化不用在代码中硬编码(像new Dog()),而是在运行时分配具体的实现对象:
a=getAnimal();
a.makeSound();
我们不知道实际的动物子类型,我们在意的只是它知道如何响应makSound()

实现Duck的行为

接口 
FlyBeahavior
//所有飞行的类都要实现的接口,所有新的飞行类只需要实现fly()方法
fly();

实现类
FlyWithWings                    FlyNoway
fly(){                          fly(){                
​//实现鸭子                        //什么都不做->不会飞  	
​//这是所有有翅膀的飞行实现          //这是所有不会飞的鸭子的实现
}                               }

接口
QuackBehavior
//嘎嘎叫行为也一样,它只包含一个需要实现的quack()方法

实现类
Quack                Squeak              MuteQuack
quack(){             quack(){            quack(){
 //实现鸭子嘎嘎叫      //橡皮鸭子嘎嘎叫     //不做任何事->不会叫
 //真的嘎嘎叫         //实则为吱吱叫       //名为嘎嘎叫,实则不出声
}                    }                    }

通过这个设计,其他类型的对象可以服用飞行和嘎嘎叫行为,因为这些行为不再隐藏在Duck类中
我们可以增加新的行为,不用修改任何已有行为类或者涉及任何使用飞行行为的Duck类

整合Duck的行为
关键在于:Duck类现在将委托其飞行和嘎嘎叫行为,而不是使用Duck类(或子类)中定义的嘎嘎叫和飞行方法

做法如下:
1.首先,添加两个实例变量,类型为FlyBeahavior和QuackBehavior,名称为flyBeahavior和quackBehavior。
在运行时,每个具体鸭子对象将给这些变量分配特定行为,像飞行的FlyWithWings...
移出Duck类和任何子类中的fly()和quack()方法
将用两个类似的方法proformFly()和proformQuack()方法来替代

Duck
FlyBeahavior flyBeahavior
QuackBehavior quackBehavior

proformFly();
performQuack();
swim();
display();
//其他鸭子方法

2.现在我们完成这个performQuack()方法
public void performQuack(){
  quackBehavior.quack();
}  

Duck只要让quackBehavior所引用的对象为它嘎嘎叫即可,在这部分代码中我们不关心Duck是哪种对象,只要它知道怎么quack()就行了

3.如何设置flyBehavior和quackBehavior实例变量

public class RedHeadDuck extends Duck{
  public RedHeadDuck(){
    //继承来自Duck的quackBehavior和flyBehavior实例变量
    //使用Quack类来处理嘎嘎叫,因此performQuack()被调用时,嘎嘎叫的责任被委托给Quack对象
    quackBehavior=new Quack();
    //使用FlyWithWings作为其flyBehavior类型
    flyBehavior=new FlyWithWings();
  }
}

4.测试

public class Demo{
  public static void main(String[] args){
    Duck redHeadDuck = new RedHeadDuck();
    redHeadDuck.performQuack();
    redHeadDuck.proformFly();
  }
}

也可以动态的设置行为
加上两个set方法到Duck类

public void setFlyBehavior(FlyBehavior fb){
    this.flyBehavior=fb;
}

public void setQuackBehavior(QuackBehavior qb){
    this.quackBehavior=qb;
}

要在运行时改变鸭子的行为,只需要调用鸭子的set方法

标签:fly,策略,子类,接口,模式,鸭子,Duck,行为
From: https://www.cnblogs.com/zhao-zong-yu-hai/p/17988335

相关文章

  • Spring Cloud 系列:基于Seata 实现 XA模式
    https://seata.io/zh-cn/docs/user/mode/xahttps://seata.io/zh-cn/docs/dev/mode/xa-modeXA规范是X/Open组织定义的分布式事务处理(DTP,DistributedTransactionProcessing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对XA规范提供了支持。XA......
  • 券商Darwinex达尔文新型的交易策略存在一定的风险!
    最近毒舌君发现了一家名为Darwinex达尔文的外汇社交券商,与传统的券商略有区别,我们就来看看有何区别呢!    一、跟单交易策略  Darwinex达尔文券商属于跟单交易平台。跟单交易其实是一种投资策略,就是跟随其他交易者的交易策略进行投资操作,这种比较适用于那些没有足够专业知识......
  • 解释器模式
    定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子简单来说:为了解释一种语言而为语言创建的解释器类型:行为型适用场景:某个特定类型问题发生频率足够高优点:语法由很多类表示,容易改变及扩展此“语言”缺点:当语法规则数目太多时,......
  • .NET GC的SustainedLowLatency模式引发内存的问题
    最近遇到一个问题,应用的内存占用升上去后一直降不下来,打了dump文件后发现GC的Generation0上有很多空白区间没释放,按道理第0代堆是经常回收的,怎么会有那么多空白区间呢?查阅了相关文档后,发现这是由代码中的System.Runtime.GCSettings.LatencyMode=System.Runtime.GCLatencyMode......
  • goland开启debug模式的修复
    1、使用下载的老版golandide工具,使用debug模式无法正常生效:异常信息是因为goland中的dlv.exe版本太老,也就是dlv.exe不能适配最新的go版本:errorlayer=debuggercouldnotpatchruntime.mallogc:notypeentryfound,use'types'foralistof2、处理方案:goinstallg......
  • openGauss学习笔记-207 openGauss 数据库运维-常见故障定位案例-btree 索引故障情况下
    openGauss学习笔记-207openGauss数据库运维-常见故障定位案例-btree索引故障情况下应对策略207.1btree索引故障情况下应对策略207.1.1问题现象偶发索引丢失错误,报错如下。ERROR:index'xxxx_index'containsunexpectedzeropage或ERROR:index'pg_xxxx_index'cont......
  • Centos桌面模式和命令行模式切换
    希望在VMware中的CentOS虚拟机默认启动到命令行模式(无桌面模式),而不是图形用户界面(GUI),可以按照以下步骤操作:打开终端,使用以下命令获取当前的默认目标(target),如果是图形模式,通常会显示graphical.target。systemctlget-default将默认目标设置为多用户模式,这是一个无桌面的......
  • 多快好省| 4 条策略完美化解 BI 场景的敏感数据保护难题
    超过2000人每天取数、用数查看报表BI平台原生的权限控制和脱敏难以有效落地敏感数据基本处于“裸奔”状态4条策略如何实现有效保护?项目背景金融消费者个人信息保护与数据安全风险排查成为近年来金融监管机构的检查重点之一。G保险企业开展数据安全治理自查,已逐步完成了数据分类分......
  • 探讨Go语言中的HTTP代理模式:看Go如何玩转网络中转站
    在互联网的海洋中,HTTP代理服务器像一座灯塔,为我们的网络冲浪提供了指引。而当Go语言遇上HTTP代理,会碰撞出怎样的火花呢?今天,让我们一起探讨Go语言中的HTTP代理模式,看看它如何玩转这个网络中转站!首先,让我们来了解一下什么是HTTP代理模式。简而言之,HTTP代理模式就是通过一个代理服务器......
  • db2设置归档模式
    环境:Os:Centos7DB:V11.5.61.查看数据库是否处于归档模式[db2inst1@host135SQL00001]$db2getdbcfgfordb_hxl|grepLOGARCHFirstlogarchivemethod(LOGARCHMETH1)=OFFArchivecompressionforlogarchmeth1(LOGARCHCOMPR1)=OFFOptio......