Java设计模式(1 / 23):策略模式

定义

策略(Strategy)模式定义了算法族,分别封装起来,让它们之间可以互相替换 ,此模式让算法的变化独立于使用算法的客户。

在这里插入图片描述

案例:模拟鸭子应用

一开始

在这里插入图片描述

新需求:模拟程序需要会飞的鸭子

在父类新添加fly()方法。
在这里插入图片描述


这样做的弊端:并非Duck所有的子类都会飞,如橡皮鸭。

在这里插入图片描述

当涉及维护时,为了复用(reuse)目的而使用继承,结局并不完美。


一种补救的方法把橡皮鸭类中的fly()方法覆盖掉。

在这里插入图片描述


新麻烦:加入诱饵鸭(DecoyDuck)类,它是假鸭,不会飞也不会叫。

在这里插入图片描述

利用继承来提供Duck的行为,这会导致下列缺点:

  1. 代码在多个子类中重复。
  2. 运行时的行为不容易改变。
  3. 难以得知所有鸭子的全部行为。
  4. 改变会牵一发动全身,造成其他鸭子不想要的改变。

每当有新的鸭子子类出现,就要被迫检查并可能需要覆盖fly()和quark()这简直是无穷尽的恶梦


一种改进:可以把fly()取出来,放进一个Flyable接口中。这么一来,只有会飞的鸭子才实现此接口。同样的方式 ,也可以用来设计一个Quackable接口,因为不是所有的鸭子都会叫。

在这里插入图片描述

虽然Flyable与Quackable可以解决一部分的问题(不会再有会飞的橡皮鸭 ),但是却造成代码无法复用,这只能算是从一个恶梦跳进另一个恶梦。甚至,在会飞的鸭子中,飞行的动作可能还有多种变化…

这意味着:无论何时你需要修改某个行为,你必须得往下追踪并修改每一个定义此行为的类,一不小心,可能造成新的错误。

(MyNote:Java8的default关键可在接口类具有实现代码。)

美好的愿景:如果能有一种建立软件的方法,好让我们需要改变软件时,可以在对既有的代码影响最小的情况下,轻易达到花较少时间重做代码,而多让程序去做更酷的事。该有多好…

软件开发的一个不变真理:变化永存

不管当初软件设计得多好,一阵子之后,总是需要成长与改变,否则软件就会死亡

驱动改变的因素很多如:

  • 顾客或用户需要别的东西,或者想要新功能。
  • 公司决定采用别的数据库产品,也从另一家厂商买了数据,这造成数据格式不兼容。

设计原则:变定分离

找出应用中可能需要变化之处,把它们独立出来 ,不要和那些不需要变化的代码混在一起。

把会变化的部分取出并封装起来,好让其他部分不会受到影响。代码变化之后,出其不意的部分变得很少,系统变得更有弹性

(MyNote:把精力集中在变化上。)

换句话说,如果每次新的需求一来,都会变化到某方面的代码,那么你就可以确定,这部分的代码需要被抽出来,和其他闻风不动的代码有所区隔。

下面是这个原则的另一种思考方式:把会变化的部分取出并封装起来,以便以后可以轻易地扩充此部分,而不影响不需要变化的其他部分

这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供了一套方法让系统中的某部分改变不会影响其他部分

模拟鸭子程序中的变定分离

Duck类内的fly()和quack()会随着鸭子的不同而改变。

为了要把这两个行为从Duck类中分开 ,我们将把它们自Duck类中取出,建立一组新类代表每个行为。

在这里插入图片描述

设计鸭子两个行为

我们希望一切能有弹性,我们还想能够指定行为到鸭子的实例,比方说,想要产生绿头鸭实例,并指定特定类型的飞行行为给它。

干脆顺便让鸭子的行为可以动态地改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,就可以在运行时动态地改变绿头鸭的飞行行为。

设计原则:针对接口编程 ,而不是针对实现编程。

我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都必须实现这些接口之一。所以这次鸭子类不会负责实现这接口,反而是由其他类专门实现FlyBehavior与QuackBehavior,这其他类就称为行为类。由行为类实现行为接口,而不是由Duck类实现行为接口。

这样的作法迥异于以往,以前的作法是:

  1. 行为是继承Duck超类的具体实现而来,

  2. 继承某个接口并由子类自行实现而来。

这两种作法都是依赖于实现,我们被实现绑得死死的,没办法更改行为(除非写更多代码)。

在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的实现不会被绑死在鸭子的子类中。(换句话说,特定的实现代码位于实现FlyBehavior与QuakcBehavior的特定类中)。

在这里插入图片描述

设计原则:针对接口编程 ,而不是针对实现编程

针对接口编程真正的意思是针对超类型(supertype)编程

这里所谓的接口有多个含意,接口是

  • 一个概念,
  • 一种Java的interface构造。

你可以在不涉及Java interface的情况下,针对接口编程,关键就在多态

利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。

针对超类型编程这句话,可以更明确地说成:变量的声明类型,应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量;这也意味着,声明类时,不用理会以后执行时的真正对象类型!


看看下面这个简单的多态例子:假设有一个抽象类Animal,有两个具体的实现(Dog与Cat)继承Animal。

在这里插入图片描述

针对实现编程,作法如下:

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

声明变量d为Dog类型(是Animal的具体实现),会造成我们必须针对实现编码。

但是针对接口/超类型编程,作法会如同下面:

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

我们知道该对象是狗 , 但是我们现在利用animal进行多态的调用。

更棒的是,子类型实例化的动作不再需要在代码中硬编码,例如new Dog(),而是在运行时才指定具体实现的对象。

a = getAnimal();
a.makeSound();

我们不知道实际的子类型是什么,我们只关心它知道如何正确地进行makeSound()的动作就够了。

(MyNote:一开始编码时写具体实现没有错,中途有需求变化就积极重构吧!)

实现鸭子的行为

我们有两个接口,FlyBehavior和QuackBehavior,还有它们对应的类,负责实现具体的行为:

在这里插入图片描述

这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。

而我们可以新增一些行为 ,不会影响到既有的行为类 ,也不会影响有使用到飞行行为的鸭子类。

这么一来,有了继承的复用好处,却没有继承所带来的包袱。(MyNote:顺得哥情亦不失嫂意)

整合鸭子的行为

关键在于,鸭子现在会将飞行和呱呱叫的动作,委托(delegate)别人处理,而不是使用定义在自己类(或子类)内的方法。

在这里插入图片描述

然后实现performQuack() (performFly()类似)

public class Duck {QuackBehavior quackBehavior;// 还有更多public void performQuack() {//不亲自处理呱呱叫行为,而是委托给quackBehavior对象。quackBehavior.quack();}
}

Duck的子类在构造器中赋值行为变量

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 abstract class Duck {FlyBehavior flyBehavior;QuackBehavior quackBehavior;public Duck() {}public void setFlyBehavior(FlyBehavior fb) {flyBehavior = fb;}public void setQuackBehavior(QuackBehavior qb) {quackBehavior = qb;}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 class DecoyDuck extends Duck {public DecoyDuck() {setFlyBehavior(new FlyNoWay());setQuackBehavior(new MuteQuack());}public void display() {System.out.println("I'm a duck Decoy");}
}
绿头鸭
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 RedHeadDuck extends Duck {public RedHeadDuck() {flyBehavior = new FlyWithWings();quackBehavior = new Quack();}public void display() {System.out.println("I'm a real Red Headed duck");}
}
橡皮鸭
public class RubberDuck extends Duck {public RubberDuck() {flyBehavior = new FlyNoWay();quackBehavior = new Squeak();
//		quackBehavior = () -> System.out.println("Squeak");}public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {this.flyBehavior = flyBehavior;this.quackBehavior = quackBehavior; }public void display() {System.out.println("I'm a rubber duckie");}
}

飞行行为接口

public interface FlyBehavior {public void fly();
}
不能飞行行为类
public class FlyNoWay implements FlyBehavior {public void fly() {System.out.println("I can't fly");}
}
能飞行行为类
public class FlyWithWings implements FlyBehavior {public void fly() {System.out.println("I'm flying!!");}
}

叫声行为接口

public interface QuackBehavior {public void quack();
}
不叫行为类
public class MuteQuack implements QuackBehavior {public void quack() {System.out.println("<< Silence >>");}
}
呱呱叫行为类
public class Quack implements QuackBehavior {public void quack() {System.out.println("Quack");}
}
吱吱叫行为类
public class Squeak implements QuackBehavior {public void quack() {System.out.println("Squeak");}
}

运行测试类

public class MiniDuckSimulator {public static void main(String[] args) {Duck mallard = new MallardDuck();mallard.performQuack();mallard.performFly();System.out.println("---");Duck model = new RedHeadDuck();model.performFly();//改变飞行行为model.setFlyBehavior(new FlyNoWay());model.performFly();}
}

运行结果:

I'm Quack
I'm flying!!
---
I'm flying!!
I can't fly

本例子类与接口全家福

在这里插入图片描述

设计原则:多用组合,少用继承

根据本例,使用组合建立系统具有很大的弹性,不仅可将算法族封装成类 ,更可以在运行时动态地改变行为,只要组合的行为对象,符合正确的接口标准即可。

参考资料

  1. Head First 设计模式

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

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

相关文章

Java设计模式(3 / 23):装饰者模式

文章目录定义案例1&#xff1a;三点几啦首次尝试再次尝试设计原则&#xff1a;类应该对扩展开放&#xff0c;对修改关闭尝用装饰者模式装饰者模式特征本例的类图放码过来饮料类HouseBlendDarkRoastEspressoDecaf调料装饰类MilkMochaSoyWhip运行测试类案例2&#xff1a;编写自己…

c语言知识体系

原文&#xff1a;https://blog.csdn.net/lf_2016/article/details/80126296#comments

《游戏编程入门 4th》笔记(1 / 14):Windows初步

文章目录Windows编程概述获取Windows理解Windows消息机制多任务多线程事件处理DirectX快速概览Direct3D是什么Window程序基础创建第一个Win32项目理解WinMainWinMain函数调用完整的WinMainGetMessage函数调用寻求帮助Windows编程概述 DirectX&#xff0c;流行的游戏编程库。它…

17校招真题题集(1)1-5

注&#xff1a;本系列题目全是按照通过率降序来排列的&#xff0c;基本保证题目难度递增。 1、 题目名称&#xff1a;游戏任务标记 来源&#xff1a;腾讯 题目描述 游戏里面有很多各式各样的任务&#xff0c;其中有一种任务玩家只能做一次&#xff0c;这类任务一共有1024个…

《游戏编程入门 4th》笔记(2 / 14):监听Windows消息

文章目录编写一个Windows程序理解InitInstanceInitInstance函数调用InitInstance的结构理解MyRegisterClassMyRegisterClass函数调用MyRegisterClass的作用揭露WinProc的秘密WinProc函数调用WinProc的大秘密什么是游戏循环The Old WinMain对持续性的需要实时终止器WinMain和循环…

17校招真题题集(2)6-10

注&#xff1a;本系列题目全是按照通过率降序来排列的&#xff0c;基本保证题目难度递增。 6、 题目名称&#xff1a;Fibonacci数列 来源&#xff1a;网易 题目描述 Fibonacci数列是这样定义的&#xff1a; F[0] 0 F[1] 1 for each i ≥ 2: F[i] F[i-1] F[i-2] 因此&am…

QT5的数据库

#include <QtSql> QT sql QSqlDatabase类实现了数据库连接的操作 QSqlQuery类执行SQL语句 QSqlRecord类封装数据库所有记录 QSqlDatabase类 [cpp] view plaincopy print?QSqlDatabase db QSqlDatabase::addDatabase("QOCI"); db.setHostName("localh…

数据结构课上笔记6

本节课介绍了单链表的操作实现细节&#xff0c;介绍了静态链表。 链表带头的作用&#xff1a;对链表进行操作时&#xff0c;可以对空表、非空表的情况以及 对首元结点进行统一处理&#xff0c;编程更方便。 下面给出带头的单链表实现思路&#xff1a; 按下标查找&#xff1a; …

《Unity2018入门与实战》笔记(9 / 9):个人总结

个人总结 脚本语言学习的窍门 尽可能多读、多写、多说脚本语言&#xff01; Link 游戏制作步骤 设计游戏时一般会遵循5个步骤&#xff1a; 罗列出画面上所有的对象。确定游戏对象运行需要哪些控制器脚本。确定自动生成游戏对象需要哪些生成器脚本。准备好用于更新UI的调度…

17校招真题题集(3)11-15

注&#xff1a;本系列题目全是按照通过率降序来排列的&#xff0c;基本保证题目难度递增。 11、 题目名称&#xff1a;买苹果 来源&#xff1a;网易 题目描述 小易去附近的商店买苹果&#xff0c;奸诈的商贩使用了捆绑交易&#xff0c;只提供6个每袋和8个每袋的包装(包装不…

Qt学习:QDomDocument

QDomDocument类代表了一个XML文件 QDomDocument类代表整个的XML文件。概念上讲&#xff1a;它是文档树的根节点&#xff0c;并提供了文档数据的基本访问方法。由于元素、文本节点、注释、指令执行等等不可能脱离一个文档的上下文&#xff0c;所以文档类也包含了需要用来创建这些…

《事实:用数据思考,避免情绪化决策》笔记

文章目录一分为二负面思维直线思维恐惧本能规模错觉以偏概全命中注定单一视角归咎他人情急生乱一分为二 要做到实事求是&#xff0c; 就要做到当你听到一分为二的说法时&#xff0c; 你就能迅速认识到这种说法描述的是一种两极分化的图画&#xff0c; 而两极之间存在一道巨大的…

顺序存储线性表实现

在计算机中用一组地址连续的存储单元依次存储线性表的各个数据元素,称作线性表的顺序存储结构。 顺序存储结构的主要优点是节省存储空间&#xff0c;因为分配给数据的存储单元全用存放结点的数据&#xff08;不考虑c/c语言中数组需指定大小的情况&#xff09;&#xff0c;结点之…

QT5生成.exe文件时,出现缺少QT5core.dll文件解决方法

在 http://qt-project.org/downloads 下载Qt SDK安装需要Qt版本。在QtCreator下&#xff0c;程序可以正常运行&#xff0c;但是当关闭QtCreator后&#xff0c;在DeBug目录下再运行相应的*.exe程序时&#xff0c;会提示缺少Qt5Core.dll错误。解决方法&#xff1a;添加电脑环境变…

《基于Java实现的遗传算法》笔记(7 / 7):个人总结

文章目录为何采用遗传算法哪些问题适合用遗传算法解决遗传算法基本术语一般遗传算法的过程基本遗传算法的伪代码为何采用遗传算法 遗传算法是机器学习的子集。在实践中&#xff0c;遗传算法通常不是用来解决单一的、特定问题的最好算法。对任何一个问题&#xff0c;几乎总有更…

单链表不带头标准c语言实现

链表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点&#xff08;链表中每一个元素称为结点&#xff09;组成&#xff0c;结点可以在运行时动态生成。每个结点包括两个部分&#xff1a;一个是…

Java设计模式(4 / 23):单例模式

文章目录单例模式的应用场景饿汉式单例模式懒汉式单例模式改进&#xff1a;synchronized改进&#xff1a;双重检查锁改进&#xff1a;静态内部类破坏单例用反射破坏单例用序列化破坏单例解密注册式单例模式枚举式单例模式解密容器式单例线程单例实现ThreadLocal单例模式小结参考…

约瑟夫环-(数组、循环链表、数学)

约瑟夫环&#xff08;约瑟夫问题&#xff09;是一个数学的应用问题&#xff1a;已知n个人&#xff08;以编号1&#xff0c;2&#xff0c;3...n分别表示&#xff09;围坐在一张圆桌周围。从编号为k的人开始报数&#xff0c;数到m的那个人出列&#xff1b;他的下一个人又从1开始报…

Ubuntu麒麟下搭建FTP服务

一.怎么搭建FTP服务&#xff1a; 第一步>>更新库 linuxidclinuxidc:~$ sudo apt-get update 第二步>>采用如下命令安装VSFTPD的包 linuxidclinuxidc:~$ sudo apt-get install vsftpd 第三步>>安装完成后打开 /etc/vsftpd.conf 文件&#xff0c;按如下所述…

《数据结构上机实验(C语言实现)》笔记(1 / 12):绪论

文章目录验证性实验求1~n的连续整数和说明放码结果常见算法时间函数的增长趋势分析说明放码结果设计性实验求素数个数说明放码结果求连续整数阶乘的和说明放码结果验证性实验 求1~n的连续整数和 说明 对于给定的正整数n&#xff0c;求12…n12…n12…n&#xff0c;采用逐个累…