首页 > 其他分享 >行为型模式之策略模式

行为型模式之策略模式

时间:2024-06-02 21:04:57浏览次数:42  
标签:fly quack 策略 void 模式 鸭子 Duck 行为 public

提示:本文只是想教会大家策略模式,案例代码用的是c++,如果你已经掌握了策略模式,请跳过。内容是模仿有关设计模式的一书《Head first Design Patterns》,如有差错请在评论区指出。

从SimDuck应用设计中学习策略模式

1.SimUDuck介绍

SimUDuck应用:有一款鸭塘模拟游戏SimUDuck,游戏中会出现各种鸭子,一边戏水,一边嘎嘎叫,系统最初的设计师用的是标准的OO技巧,创建了一个Duck抽象基类,然后所有鸭子继承它。

class Duck{
public:
	void quack();//鸭子都会嘎嘎叫
	//每个鸭子长相都不同,因此将display设置为纯虚函数
	virtual void display()=0;
	//其他鸭子的方法
};

2.需要鸭子会飞——Duck中添加fly方法

现在管理层突然决定需要鸭子会飞。自然而然我们就会想到:只需在Duck中添加一个fly()方法,然后所有鸭子也继承Duck,就可实现鸭子会飞,同时也满足代码复用原则

class Duck{
public:
	void quack();//鸭子都会嘎嘎叫
	
	//每个鸭子长相都不同,因此将display设置为纯虚函数
	virtual void display()=0;
	void fly();//Ok,现在鸭子会飞了
	//其他鸭子的方法
};

3.代码复用概念

什么是代码复用?
代码复用是指在软件开发过程中,利用已有的代码模代码复用是指在程序设计中尽量利用已有的代码,以便在不必重新编写相同或类似功能的情况下实现特定功能。代码复用可以通过将一段代码封装成函数、模块、类等形式来实现,然后在需要的地方调用这些可复用的代码。代码复用可以提高代码的可维护性、可读性和可扩展性,同时也可以减少代码的重复性,提高开发效率。

4.选择继承fly和quack

是不是感觉这样设计还行,如果你也这样想就大错特错了!
原因是并不是所有的鸭子都应该会飞,当给Duck类添加飞的行为的同时让不应该会飞的也会飞了,在游戏中出现了飞行的橡皮鸭,多荒唐!
接下来你可能会想:把Duck中的fly()设置成virtual,然后选择性的覆盖fly,只要在向类似于橡皮鸭这种不会飞的鸭子重写fly,然后什么也不做就好了

class Duck{
public:
	void quack();//鸭子都会嘎嘎叫

	//每个鸭子长相都不同,因此将display设置为纯虚函数
	virtual void display()=0;
	virtual void fly();//设置成虚函数
	//其他鸭子的方法
};
class RubberDuck:public Duck{
public:
	void display() override{//橡皮鸭}
	
	void fly() override{//什么也不做}
};

现在问题解决了,不会出现不应该飞的鸭子到处飞来飞去了。但是当我们给程序添加木质诱饵鸭时,又会发生什么?诱饵鸭不应该飞也不应该嘎嘎叫,于是再把Duck中的quack设置虚函数,再让诱饵鸭覆盖Duck中的quack使之啥也不干

class Duck{
public:
	virtual void quack();//设置成虚函数
	//每个鸭子长相都不同,因此将display设置为纯虚函数
	virtual void display()=0;
	virtual void fly();//设置成虚函数
	//其他鸭子的方法
};
class DecoyDuck:public Duck{
public:
	void displsy() override{//诱饵鸭}
	void fly() override{//什么也不做}
	void quack() override{//什么也不做}
};

5.接口如何?

这样设计接口如何?
若是产品需要持续更新(大概率),每一次约束都会变化,每一次在程序中添加新的Duck子类,你都需要被迫检查,有可能还要覆盖fly和quack,直到永远。
因此,我们需要用更干净的方式来处理这些问题,只让某些鸭子飞或嘎嘎叫。于是我们可以把Duck中的fly和quack抽离出来分别成为单独的类,只让会飞或会嘎嘎叫的鸭子继承这些类。问题似乎就解决了。
但是代码复用就被摧毁了,当你考虑有72种鸭子的时候,做这些改变只会让你的代码变得难以维护,更别论飞行方式和教的声音有多种的时候,这简直就是噩梦。
那么应该怎么办?

6.解决方案——抽离变化部分封装,针对接口编程

我们需要把会变化的部分抽离出来,并将其封装,这样以后你修改或者扩展这个部分时,就不会影响其他不需要变化的部分。于是我们抽离出鸭子飞和嘎嘎叫的行为,创建两组类,一组类是flyBehavior,一组类是quackBehavior。那么问题来了,我们如何设计者两组类?
我们希望保持各种东西的弹性,为了使系统变得易于维护和更新,我们需要针对接口编程,而不是针对实现编程,即需要给flyBehavior和quackBehavior这两组类设计统一的接口。

class FlyBehavior{
public:
	virtual void fly() = 0;
};

class FlyWithWings:public FlyBehavior{
public:
	void fly() override{//实现鸭子飞}
};

class FlyNoWay:public FlyBehavior{
public:
	void fly() override{//什么都不做,即不会飞}
};
class QuackBehavior{
public:
	virtual void quack() = 0;
};

class Quack:public QuackBehavior{
public:
	void quack() override{//实现鸭子嘎嘎叫}
};

class Squeak:public QuackBehavior{
public:
	void quack() override{//橡皮鸭吱吱叫}
};

class MuteQuack: public QuackBehavior{
public:
	void quack() override{//诱饵鸭不出声}
};

通过这种设计,其他类型的对象可以复用飞行和嘎嘎叫的行为,我们也可以增加新的行为,而不用修改任何已有行为类或任何使用了飞行行为类的Duck类。简直就是完美!
接下来重新实现Duck及其子类即可

class Duck{
public:
	Duck(FlyBehavior *fly=nullptr,QuackBehavior *quack=nullptr):fly_behavior(fly),quack_behavior(quack){}
	
	virtual void display() = 0;
	void performFly(){fly_behavior->fly();}
	void performQuack(){quack_behavior->quack();}
protected:
	FlyBehavior *fly_behavior;
	QuackBehavior *quack_behavior;
};

class MallardDuck:public Duck{
public:
	MallarDuck(FlyBehavior *fly =new FlyWithWings(),QuackBehavior *quack=new Quack()):Duck(fly,quack){}
	void display() override{std::cout<<"我是绿头鸭"<<std::endl;}
};
class RubberDuck:public Duck{
public:
	RubberDuck(FlyBehavior *fly=new FlyNoway(),QuackBehavior *quack=new Squeak()):Duck(fly,quack){}
	
	void display() override{std::cout<<"我是橡皮鸭"<<std::endl;}
};

class DecoyDuck:public Duck{
public:
	void display() override{std::cout<<"我是诱饵鸭"<<std::endl;}
	DecoyDuck(FlyBehavior *fly=new FlyNoWay(),QuackBehavior *quack=new MuteQuack()):Duck(fly,quack);
};

此外,我们还可以为Duck类增加一个可以在运行时改变鸭子飞行或嘎嘎叫的方法,使Duck类更加灵活

class Duck{
public:
	void setFlyBehavior(FlyBehavior *fly){delete fly_behavior;fly_behavior = fly;}
	void setQuackBehavior(QuackBehavior *quack){delete quack_behavior;quack_behavior = quack;}
	//其他方法...
};

7.使用组合而不是继承

OK,现在我们已经深入研究了鸭子模拟器的设计,是时候浮出水面看看全貌。以下是重新设计后的整个类结构:鸭子扩展Duck,飞行行为类FlyBehavior,嘎嘎叫行为类QuackBehavior。我们现在把鸭子的行为看做一个算法家族,分别封装起来,注意,这里是Duck类组合了FlyBehavior和QuackBehavior,即每个鸭子都有一个FlyBehavior和QuackBehavior,以便委托飞行和嘎嘎叫,这里又用到了一个设计原则:
优先使用组合而不是继承,即HAS-A比IS-A更好。正如你看到的,使用组合创建系统给于我们更大的弹性,不仅是把一个算法家族封装进它们自己的一组类,而且可以在运行时改变行为,只要组合的对象实现正确的行为接口。

8.总结

到这里,就已经本文主题中的设计模式–策略模式
现在正式给出策略模式的定义:策略模式定义了一个算法家族,分别封装起来,使得他们之间可以互相变换,策略让算法的变化独立于使用他们的客户。
虽然使用策略模式不仅可以提高代码的灵活性和可扩展性(策略模式将每个算法封装成一个类,可以随时切换算法而不影响代码的其他部分),而且可以提高代码的复用性,同时可以简化复杂的条件判断,避免了使用大量的if-else语句判断不同的情况。但是使用策略模式也有其缺点:首先是增加了代码的复杂度。其次需要创建大量的策略类。如果策略较多,可能会导致创建大量的策略类,进而增加代码量和维护成本。此外客户端需要了解所有的策略类。客户端需要了解每个策略类的作用和使用方式,很可能会增加客户端的复杂度。

9.思考题

最后这里给出一个思考题:
鸭鸣器是一种猎人用来模拟鸭子嘎嘎叫的装置,你如何实现自己的鸭鸣器,而不用从Duck类继承?(提示:组合)

标签:fly,quack,策略,void,模式,鸭子,Duck,行为,public
From: https://blog.csdn.net/weixin_72044440/article/details/139393591

相关文章

  • CentOS7单用户模式,救援模式操作记录
    CentOS7单用户模式,救援模式操作记录1.单用户模式单用户模式进入不需要密码,无网络连接,拥有root权限,禁止远程登陆。一般用于用于系统维护,例如忘记root密码后可以通过进入单用户模式进行重置。开机启动,在出现内核选项时按"键盘e键"进行编辑,找到linux16行并在行尾添加内核参数rd.......
  • 常用设计模式总结,附完整图解
    UML类图类图定义规则属性和方法前加上(+、-、#、留空)分别代表:公开(public)、私有(private)、保护(protected)、缺省(default)方法括号内为参数类型,冒号后为返回值类型下划线表示静态(static),斜体表示抽象(abstract) 类图关系表示法其中关联、聚合、组合,比较容易混淆,它们的区别:关......
  • this,构造器,static,final,单例模式
    this,构造器,static,final,单例模式this关键字在java中this是一个引用变量,即指向当前对象地址的引用(指针),→可以把this当作当前对象,便于更好的索引.this()实际是调用了当前对象的构造器1.引用当前对象的属性当在方法中要访问当前对象的属性时,可以用this来区分局......
  • VMMap工具的基本功能和使用方法,包括如何分析内存分配情况、监控内存使用模式等;包括深
    VMMap初级应用的大纲:1.介绍VMMap简要介绍VMMap是什么,以及其在Windows系统中的作用和用途。解释VMMap能够提供的信息类型,如内存分配情况、内存使用模式等。2.VMMap的基本功能演示如何使用VMMap打开目标进程,并查看其内存映射和分配情况。介绍VMMap提供的基本过滤器和查看......
  • Java高并发核心编程.卷2,多线程、锁、JMM、JUC、高并发设计模式 (尼恩)电子版百度云
    书获取链接:python33  。c o  m我的阅读笔记:多线程:介绍Java多线程的基础概念,如线程的创建、启动、状态转换、线程间通信等。锁:深入探讨Java中的各种锁机制,包括内置锁(synchronized)、ReentrantLock、ReadWriteLock等,以及它们的使用场景和性能特点。Java内存模型(JMM):解释J......
  • 设计模式之原型模式
    问题背景在开发一个图形设计软件时,我们面临一个常见的需求:用户需要频繁地创建和编辑各种图形,如圆形、矩形和多边形。其中,许多图形元素在属性上非常相似,比如颜色、大小或样式可能只有细微的差别。用户希望能够快速复制一个已有图形,然后对其进行小的修改,而不是每次都从零开始......
  • 开源多企业AI智能名片小程序源码中的市场细分策略分析
    摘要:在数字化营销的新时代,开源多企业AI智能名片小程序源码为众多企业提供了快速构建智能名片系统的能力。其中,市场细分作为营销策略的重要组成部分,对于提高营销效果、满足消费者需求具有重要意义。本文将以开源多企业AI智能名片小程序源码为背景,探讨市场细分中的行为细分和心......
  • 【Vue】深入理解MVVM模式的魔力
    目录前言一、MVVM模式是什么?二、具体示例总结前言    Vue.js是一种基于JavaScript的前端框架,它采用了MVVM(Model-View-ViewModel)模式来实现数据的双向绑定。在本篇博客中,我将介绍MVVM模式的基本概念,并演示如何使用Vue.js来实现这种模式。一、MVVM模式是什么? ......
  • 抽象工厂模式
    抽象工厂模式解决的是一系列相互依赖的对象的创建。classMyConnect{};classMyDatabse{};classMyCommand{};//sqlServer的连接classSqlServerConnect:publicMyConnect{};classSqlServerDatabse:publicMyDatabse{};classSqlServerCommand:publicMyComm......
  • Dota2刀塔找不到mfc140u.dll无法继续执行问题的全面分析与解决策略
    最近很多玩家玩Dota2刀塔遇到了找不到mfc140u.dll无法继续的问题,其中mfc140u.dll是一个动态链接库(DynamicLinkLibrary,DLL)文件,它是专为MicrosoftWindows操作系统设计的。那么应该如何解决呢?下面一起来看看具体的解决方法介绍吧!方法一:从回收站还原检查回收站中是否有误删......