关于设计模式——策略模式-Strategy Pattern

文章目录

  • 1 策略模式
    • 1.1 模拟鸭子
    • 1.2 设计原则
    • 1.3 整合行为
    • 1.4 模拟鸭子代码的代码
    • 1.5 动态设定行为
    • 1.6 重新查看整体
    • 1.7 继承和组合
    • 1.8 总结
    • 1.9 优劣期间应用场景
    • 2.0 参照资料

1 策略模式

在我们什么都不会的情况下,我们先无需知道什么是策略模式,我们要做的是:在了解什么是设计模式之前,让我们先引入一个例子。

1.1 模拟鸭子

如果要用java做一个模拟鸭子的游戏,那么对于刚学完JavaSE的人来说,它会想的就是设计一个鸭子的超类(Duck),然后让其他的鸭子继承此类。其中,所有的鸭子都会叫(Quack)游泳(Swin)。而每一种鸭子的外观不同,所以display方法是抽象的,每个鸭子子类要在自己的类上实现display的具体外观。

后面主管下来要求,说这个鸭子必须得会飞了。所以为了速度,我们直接在超类Duck上加上fly方法,这样,所有的鸭子都会飞了。

但是问题出现了,模拟鸭子的游戏中有一种橡皮鸭子,这种鸭子作为玩具是不会飞的,如果在Duck上面直接加fly就会导致橡皮鸭子也会飞,而如果不在Duck上面加则需要去其他会飞的鸭子类上一个一个写fly方法。而且对于橡皮鸭来说,其并不像真鸭一样会嘎嘎叫,而是吱吱叫(squeak)

或许有一种方法可以解决上述问题,可以在Duck上加fly方法,然后在橡皮鸭子类上重写该方法,使它不会飞即可。虽然这种做法能够解决一时的问题,但是后面如果又加入其他有特色的鸭,那就会导致代码复用性不高,代码冗余。

还有一种办法是,fly方法不写在超类里,而是写在接口里,哪只鸭子会飞就去调用。明显地,这种做法对于鸭子多数会飞的情况简直是噩梦。

1.2 设计原则

为此,我们引入第一个设计原则:

如果应用中有可能变化的地方,把他找出来并且独立,不要和那些不需要变化的代码混在一起。

既然fly和quack都有可能变化,那我们干脆把它们独立出来。我们可以建立两组类,一组是实现fly的,一组实现quack。在每组类中实现各自的动作。我们把这种独立出来具有多种选择的类叫做策略类,对应策略类中算法的不同,每组类都含有许多算法,我们把某组类叫做算法族

image-20220216091525790

我们在前面做的一切都是为了我们设计的鸭子有弹性,即可以各式各样。而各式各样的原因大多是因为行为不一。既然行为不一样,那我们在Duck类中就应该包含设定行为的方法,使其可以动态地改变鸭子的行为。

为此,我们引入第二个设计原则:

针对接口编程,而不是针对实现编程。

但是与前面提到的不一样的是,我们设计的接口并不是接在实例类上的,而是接在行为类上的。这样的话,鸭子的行为将被放在分开的类中,此类专门提供某行为的实现。这样的话鸭子类就不需要知道行为的实现细节。

我们现在来体会一个例子,假如现在有一个抽象类Animal,有两个具体的实现Dog和Cat,这两个类都是基础Animal。

//Animal
make Sound(); //指定抽象类叫声//Dog
makeSound(){bark(); //汪汪叫
}
bark();//Cat
makeSound(){meow(); //喵喵叫
}
meow();

如果是针对实现编程,那我们会做如下所示的事:

Dog d = new Dog();
d.bark();

如果是针对接口/超类型编程,那我们会做如下所示的事:

Animal animal = new Dog();
animal.makeSound();

也就是说,我们并不关心子类是什么,只关心子类能否正确的实现其对应的父类行为即可。

现在让我们回到鸭子。如果我们有两个接口,一个接口是飞接口(FlyBehavior),还有一个叫接口(QuackBehavior)。我们还有两个对应的行为类,其负责实现具体的行为。如图所示:

image-20220216093249362

这样的设计使得飞行类组合叫声类组都已经和鸭子类无关了,甚至于,我们还可以设计多种新的行为,比如鸭子会跳舞。

或许这样新奇的设计会让人感到奇怪,用类代表行为而不是代表东西,这样是否不太恰当。实际上,行为也是可以理解为东西的,看具体情况而定,例如在鸭子的问题上,鸭子的飞行类可以有翅膀每秒拍动几下、飞的最大高度、飞的速度等行为属性。

1.3 整合行为

现在我们已经设计好了,该整合了。鸭子不太可能变化的行为处于自身超类Duck,而鸭子变化的类叫声类和飞行类被我们分离出两组类来,一个类对应一个情况。

接下来,我们需要动态控制鸭子的飞行和叫声行为。我们把Duck类中和其子类中所有的fly和quack移除,然后用peerformFly和performQuack来取代Duck类中的fly和quack。

也就是说,Duck类里面现在有如下的东西:

//Duck
FlyBehavior flyBehavior //接口
QuackBehavior quackBehavior //接口performQuack()
swin()
display()
performFly()

我们在performFly和performQuack两个方法中委托两个行为接口来帮我们做叫和飞这两件事,如下:

public void performQuack()
{quackBehavior.quack();
}

整合完成后。我们来尝试创建一个实例类,比如绿头鸭(MallardDuck)。

public class MallardDuck extends Duck
{public MallardDuck(){quackBehavior = new Quack();  //会呱呱叫flyBehavior = new FlyWithWings(); //会飞}public void display(){System.out.println("i'm a real Mallard duck");}
}

1.4 模拟鸭子代码的代码

/*鸭子超类*/
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 interface FlyBehavior {void fly();
}/*鸭子叫声接口*/
public interface QuackBehavior {void quack();
}/*鸭子飞行类*/
public class FlyWithWings implements FlyBehavior {@Overridepublic void fly() {System.out.println("I'm flying!");}
}/*鸭子不能飞行类*/
public class FlyNoWay implements FlyBehavior{@Overridepublic void fly() {System.out.println("I can't fly");}
}/*鸭子呱呱叫类*/
public class Quack implements QuackBehavior{@Overridepublic void quack() {System.out.println("Quack");}
}/*鸭子吱吱叫类*/
public class Squeak implements QuackBehavior{@Overridepublic void quack() {System.out.println("Squeak");}
}/*鸭子不会叫类*/
public class MuteQuack implements QuackBehavior{@Overridepublic void quack() {System.out.println("<<Silence>>");}
}/*实例绿头鸭*/
public class MallardDuck extends Duck{@Overridepublic void display() {System.out.println("I'm a real Mallard duck");}public MallardDuck(){quackBehavior = new Quack();flyBehavior = new FlyWithWings();}
}/*测试类*/
public class Main {public static void main(String[] args) {Duck mallard = new MallardDuck();mallard.performQuack();mallard.performFly();}
}

1.5 动态设定行为

在Duck类中,我们加入两个新方法,这两个方法的好处是,通过调用这两个方法传入一个匿名内部类,该内部类实现了飞行接口,这样通过了接口多态的形式改变了实例类内部提前设定好的特性,从而提高了动态设定行为的动态性。

public void setFlyBehavior(FlyBehavior fb)
{flyBehavior = fb;
}public void setQuackBehavior(QuackBehavior qb)
{quackBehavior = qb;
}

如果会看前面的绿头鸭,我们会发现我们前面设定行为是在实例类里面的,而结果上面代码的操作,我们可以通过插入一个新的功能类来改变原先设定好的功能,这样耦合性提高了。

public class MallardDuck extends Duck
{
public MallardDuck(){
quackBehavior = new Quack(); //会呱呱叫
flyBehavior = new FlyWithWings(); //会飞
}

​ public void display(){
​ System.out.println(“i’m a real Mallard duck”);
​ }

}

现在我们可以随意调动上面的两个方法来控制新的实例鸭子的行为了,我们建立一个模型鸭来验证我们的说法。

public class ModelDuck extends Duck{public ModelDuck() {flyBehavior = new FlyNoWay();quackBehavior = new Quack();}@Overridepublic void display() {System.out.println("I'm a model duck");}
}

建立一个新的行为,这种行为是给鸭子绑定一个火箭让其飞行。

public class FlyRocketPowered implements FlyBehavior{@Overridepublic void fly() {System.out.println("I'm flying with a rocket!");}
}

在上面模型鸭的设定中,它原本是不会飞的,但是我们可以在设置飞行的方法中传入参数,使其能够飞。

Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();

1.6 重新查看整体

image-20220217154935641

1.7 继承和组合

实际上,在开发中应该转变一些思维方式。比如鸭子是一个会飞的生物(继承),我们重新看做鸭子有一个会飞的技能会更好(组合)。这也是我们第三个设计原则:

多用组合,少用继承。

是的,很多人都会有疑惑,实际上用继承写起代码来更加地简洁,但是后期维护起来确是组合要更加地方便。

1.8 总结

是的,对于不熟悉上面流程的同学,可以从头到尾再看一遍,上面的鸭子代码中,用到了我们学的第一个设计模式:策略模式。不仅如此,我们还用到了三个设计原则,让我们重新来认识一下它们吧。

  • 单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
  • 依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
  • 合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。

当然,在这里我们不细讲这些原则,只是了解了个大概,在后面的学习中我们会详细阐述,现在重要的问题是,我们应该回过头重新审视一下策略模式。

在软件开发中,我们常常会遇到类似的情况,实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径,这种模式我们叫做策略模式

策略模式实用性强,扩展性好,在软件开发中得以广泛使用,是使用频率较高的设计模式之一。其典型的应用莫过于Sun公司开发JavaSE中的容器布局管理了。其基本结构示意图如下所示:

image-20220217165621105

对于容器环境来说,容器环境该有的组件基本不变,所以将JPanel放在Container下没什么毛病,而对于布局Layout来说,布局有各式各样的布局,根据对应的软件开发来选择合适的布局,其中LayoutManger作为接口充当了抽象策略角色,当我们需要哪类具体的情况,只需要调用其子类即可。

1.9 优劣期间应用场景

从上面的一系列场景可以看出,策略模式适用处理较为灵活问题。譬如想要方便扩展、更换、修改某类功能,那么你就可以把功能对应的算法封装起来,然后采用策略模式。

  1. 主要优点

    策略模式提供了对开闭原则的完美支持。代码书写人员在不修改以前代码的情况下,也能扩展新的功能。

    策略模式可以管理相关的算法族。如上所述,策略模式可以定义一个接口来管理旗下的算法族,从而达到动态控制的效果。

    策略模式提供了一种可以替换继承关系的办法。如果随时变化的功能和不太可能变化的功能混写于一个类中,那么这不符合单一职责原则。但是使用策略模式可以提供一种动态的切换效果。

    使用策略模式可以避免多重条件选择语句。回想起前面的模拟鸭子游戏,如果不使用算法族,那么我们需要使用大量的控制语句来判断鸭子到底应该有什么功能。

  2. 主要缺点

    代码书写人必须知道所有的策略类。如上模拟鸭子的代码中,如果你不知道模型鸭有绑定火箭飞行这个策略类,那你就无法调用。

    策略类容易造成控制粒度过细。如果每个变化就要写一个策略类,那么写的代码就会过多。

    无法同时使用同个功能下的多个策略类。如果你的飞行接口下只规定了三种情况,飞行、不会飞、绑定火箭飞行。那么你的实例类是不可以兼具一个接口下管理的多个策略类的,换言之,你的鸭不可能不会飞又会飞。

  3. 适用场景

    一个功能需要动态地选择不同的策略。如果是这种情况,你可以把某个功能对应的不同情况写入策略类,然后根据面向对象的多态性来选择具体的策略算法。

    一个对象由很多行为。比如博主开发的Galgame游戏就是因为游戏功能过多,每个功能的行为也有很多,从而使用了策略模式。

    不希望使用者知道复杂的数据结构。在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性和安全性。

2.0 参照资料

算法的封装与切换——策略模式(四)_刘伟技术博客-CSDN博客_算法切换

HeadFirst设计模式——策略模式

本人技术有限,如果博客有语法或者文字错误,欢迎指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/398763.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python (3):wxPython打包app,报错

1&#xff0c;打包app报错 如图&#xff1a; 使用py2app&#xff0c;mac下打包成app。异常。程序直接退出。 没有详细的错误信息&#xff0c;client程序直接崩溃了。 2。原因 代码没有几行&#xff1a; #!/usr/bin/python # -*- coding: utf-8 -*- import wxapp wx.App(False…

.NET Framework 工具

您可以使用 .NET Framework 工具轻松创建、部署和管理面向 .NET Framework 的应用程序和组件。 此节中介绍的大部分 .NET Framework 工具将自动随 Visual Studio 一起安装。 &#xff08;有关安装信息&#xff0c;请参阅 Visual Studio 下载。&#xff09; 可以从命令行运行除程…

并发无锁队列学习(概念介绍)

1、前言 队列在计算机中很重要的一种数据结构&#xff0c;尤其在操作系统中。队列典型的特征是先进先出&#xff08;FIFO&#xff09;&#xff0c;符合流水线业务流程。在进程间通信、网络通信之间常常採用队列做缓存&#xff0c;缓解数据处理压力。结合自己在工作中遇到的队列…

王道操作系统考研笔记——2.1.5 线程概念与多线程模型

文章目录2.1.5 线程概念与多线程模型2.1.5.1 线程的来源2.1.5.2 线程机制带来的变化2.1.5.3 线程的属性2.1.5.4 线程的实现方式2.1.5.5 多线程模型2.1.5.6 小结2.1.5 线程概念与多线程模型 2.1.5.1 线程的来源 在很久以前还没有引入进程之前&#xff0c;系统中的各个程序只能…

mybatis处理集合、循环、数组和in等语句的使用

2019独角兽企业重金招聘Python工程师标准>>> 在Mybatis的xml配置中使用集合&#xff0c;主要是用到了foreach动态语句。 foreach的参数&#xff1a;foreach元素的属性主要有 item&#xff0c;index&#xff0c;collection&#xff0c;open&#xff0c;separator&…

王道操作系统考研笔记——2.1.6 处理机调度的概念和层次

文章目录2.1.6 处理机调度的概念和层次2.1.6.1 调度的基本概念2.1.6.2 高级调度2.1.6.3 中级调度2.1.6.4 进程的挂起态和七状态模型2.1.6.5 低级调度2.1.6.6 三层调度的联系、对比2.1.6.7 小结2.1.6 处理机调度的概念和层次 知识总览 2.1.6.1 调度的基本概念 实际上&#xff…

CocoaPods详解之----使用篇

作者&#xff1a;wangzz 原文地址&#xff1a;http://blog.csdn.net/wzzvictory/article/details/18737437 转载请注明出处 如果觉得文章对你有所帮助&#xff0c;请通过留言或关注微信公众帐号wangzzstrive来支持我&#xff0c;谢谢&#xff01; 一、什么是CocoaPods 1、为什…

数据结构杂谈(五)——栈

本文的所有代码均由C编写 引用及参考资料&#xff1a; 王道数据结构大话数据结构超硬核十万字&#xff01;全网最全 数据结构 代码&#xff0c;随便秒杀老师/面试官&#xff0c;我说的_hebtu666-CSDN博客 5 栈 5.1 引入 在前面学习线性表的时候&#xff0c;我们给出了线性表的…

每日一题——Leetcode203 移除链表元素

如果您是第一次看我写的博客&#xff0c;可以给我点个赞并关注我吗&#xff0c;我会持续分享更多有意思的干货。 文章目录1 题目2 思路3 代码4 小结1 题目 Leetcode203 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val …

数据结构杂谈(六)——队列

本文的所有代码均由C编写 六 队列 文章目录六 队列6.1 队列的定义6.2 队列的抽象数据类型6.3 顺序队列&#xff08;循环队列&#xff09;6.3.1 顺序队列的定义及初始化6.3.2 入队6.3.3 出队6.3.4 获取队头元素6.3.5 获取队列长度6.3.6 回过头来6.4 链式队列6.4.1 链式队列的定义…

js 事件学习与总结

一&#xff0e;&#xff08;1&#xff09;事件源&#xff08;事件目标&#xff09;&#xff1a;在哪个组件上发生的事件&#xff0c;也就是发生事件的作用体或与之相关的对象。任何元素&#xff0c;body,html元素等等&#xff0c;都可以作为事件源。&#xff08;2&#xff09;事…

量化投资培训大纲

转载于:https://www.cnblogs.com/fangbei/p/8393211.html

生成验证码

2019独角兽企业重金招聘Python工程师标准>>> //1:声明图片的大小int width 60;int height 30;//2:声明内存中的图片BufferedImage img new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);//3:获取gGraphics g img.getGraphics();//4:设置背景g.se…

.NET面试题解析(02)-拆箱与装箱

转自: http://www.cnblogs.com/anding/p/5236739.html 常见面试题目: 1.什么是拆箱和装箱&#xff1f; 2.什么是箱子&#xff1f; 3.箱子放在哪里&#xff1f; 4.装箱和拆箱有什么性能影响&#xff1f; 5.如何避免隐身装箱&#xff1f; 6.箱子的基本结构&#xff1f; 7.装箱的…

数据结构杂谈番外篇——搞懂递归的小文章

文章目录1 难题2 递归2.1 n的阶层2.2 斐波那契数列的第n项2.3 逆序打印数组3 反转链表4 回顾递归1 难题 如果不想听我谈学习的过程而注重怎么学习&#xff0c;可以直接跳到第二小节 这个递归的问题是在我刷题的时候遇到的。事实上&#xff0c;我对递归是一窍不通的&#xff0c;…

副本机制

1.首先说一下函数的副本机制 看一段简单的代码 1 # include<stdio.h>2 # include<stdlib.h>3 4 int add(int n, int m)5 {6 int z n m;7 return z;8 }9 10 int main() 11 { 12 printf("%d\n", add(1, 2)); 13 } View Code当函数返回后&am…

王道操作系统考研笔记——2.1.7 进程调度的时机、切换与过程、方式

文章目录2.1.7 进程调度的时机、切换与过程、方式2.1.7.1 进程调度的时机2.1.7.2 进程调度的方式2.1.7.3 进程的切换和过程2.1.7.4 小结2.1.7 进程调度的时机、切换与过程、方式 知识总览 2.1.7.1 进程调度的时机 进程调度&#xff08;低级调度&#xff09;&#xff0c;就是按…

王道操作系统考研笔记——2.1.8 调度算法的评价指标

文章目录2.1.8 调度算法的评价指标2.1.8.1 CPU利用率2.1.8.2 系统吞吐量2.1.8.3 周转时间2.1.8.4 等待时间2.1.8.5 响应时间2.1.8.6 小结2.1.8 调度算法的评价指标 知识总览 需要注意的是&#xff0c;这一部分的知识要学会计算 2.1.8.1 CPU利用率 在早期&#xff0c;由于CPU造…

jenkins自动化构建iOS应用配置过程中遇到的问题

最近配置jenkins来自动构建iOS应用&#xff0c;期间遇上不少问题。在这里分享给大家,也给自己留个底&#xff0c;方便下次解决问题。 首先说明下基本情况&#xff0c;我们因为部署jenkins的机器不是Mac,所以不能安装Xcode插件&#xff0c;我不知道这个插件能给我们带来多少便捷…

用户体验设计的五个原则(转)

【编者按】本文作者&#xff0c;现Aura Marker Studio创始人路意Louis&#xff0c;曾带过五年多的用户体验设计团队。这篇文章是他带团队这几年沉淀下来的一些经验和思考。 “设计&#xff0c;必须是充满诗意的。” 带过五年多的用户体验设计团队&#xff0c;从几个人到二十几个…