Bruce Eckel:再聊设计模式(篇四)函数对象模式

2024-02-20 20:10

本文主要是介绍Bruce Eckel:再聊设计模式(篇四)函数对象模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Bruce Eckel

读完需要

10

分钟

速读仅需 1 分钟

布鲁斯 • 埃克尔(Bruce Eckel),C++ 标准委员会的创始成员之一,知名技术顾问,专注于编程语言和软件系统设计方面的研究,常活跃于世界各大顶级技术研讨会。
他自 1986 年以来,累计出版 Thinking in C++、Thinking in Java、On Java 等十余部经典计算机著作,曾多次荣获 Jolt 最佳图书奖(被誉为“软件业界的奥斯卡”),其代表作 Thinking in Java 被译为中文、日文、俄文、意大利文、波兰文、韩文等十几种语言,在世界范围内产生了广泛影响。

Bruce Eckel:再聊设计模式(篇一)设计模式分类

Bruce Eckel:再聊设计模式(篇二)封装实现

Bruce Eckel:再聊设计模式(篇三)工厂模式

7

   

函数对象模式

一个函数对象(Function Object)封装一个方法,其目的是将函数的选择和调用函数的位置解耦。

《设计模式》中对“函数对象”这个术语有所提及,但并未使用。不过函数对象的核心思想在该书的若干模式中反复出现过。

7.1

   

命令模式

这种模式是函数对象最纯粹的形式:一个身为对象的方法。将一个函数对象作为参数传递,它便会生成不同的行为。

命令(Command)模式在本书前面的若干地方描述过,主要包括以下几章:

  • 基础卷第 11 章

  • 基础卷第 19 章

  • 进阶卷第 1 章

在下面这个示例中,show()的代码保持不变,改变的是传给 show()的 Comand 对象:

// patterns/CommandPattern.java
import java.util.*;class Command {public final String msg;public Command(String msg) {this.msg = msg;}
}public class CommandPattern {public static void show(Command cmd) {System.out.println(cmd.msg);}public static void main(String[] args) {show(new Command("First Command"));show(new Command("Second Command"));}
}
/* 输出:
First Command
Second Command
*/

下面是另一个示例,创建了一个命令对象的列表,形成了一个“宏指令”。在Java 8之前的版本中,如果要实现独立函数的效果,就需要额外的繁文缛节,显式地将方法包装到一个对象中。而Java 8的lambda表达式可以创建函数对象,因此命令模式就变得相当简单了:

// patterns/Macro.java
import java.util.*;public class Macro {public static void main(String[] args) {List<Runnable> macro = new ArrayList<>(Arrays.asList(() -> System.out.print("Hello "),() -> System.out.println("World! ")));macro.forEach(Runnable::run);macro.add(() -> System.out.print("I'm the command pattern!"));macro.forEach(Runnable::run);}
}
/* 输出:
Hello World!
Hello World!
I'm the command pattern!
*/

命令模式最主要的一点在于,可以让我们将想要的行为传给一个方法或对象。macro会将一组要集体执行的行为排成一队,这样就可以动态地创建行为了,而这本来一般都需要编写新的代码来实现。

Macro.java经过修改后,还可以用来解释脚本,参见解释器(Interpreter)模式

《设计模式》中指出:“命令模式是回调的面向对象形式的替代品。”然而我认为“回”字是回调概念的核心本质。也就是说,回调实际上会指回到回调的创建者,命令模式则一般只将命令对象创建出来,然后传给某个方法或对象,而不会随着时间的推移(往回)连接到命令对象上。不过这只是我的个人看法。稍后我会将一组设计模式结合起来,放到本章8.10节中。

7.2

   

策略模式

策略模式(Strategy)看起来就像命令模式的同胞兄弟,都是从同一个基类继承出来的一组函数对象。区别在于这些对象的使用方式。命令模式用于解决一类特定的问题——例如,通过传入一个描述所需文件的命令对象,从文件列表中选择一个文件。所调用的方法体即“保持不变的事物”,而变化的部分则被隔离在命令对象中。

策略模式包含一段可作为代理类的“上下文”,该代理类用于控制文件的选择以及对特定策略对象的使用——和策略模式很像。它看起来就像下面这样:

// patterns/strategy/StrategyPattern.java
// {java patterns.strategy.StrategyPattern}
package patterns.strategy;
import java.util.function.*;
import java.util.*;// 公共策略基类:
class FindMinima {protectedFunction<List<Double>, List<Double>> algorithm;
}// 各种策略,每一个都会生成无意义的数据:
class LeastSquares extends FindMinima {LeastSquares() {// Line is a sequence of points:// 直线是点的序列:algorithm = (line) -> Arrays.asList(1.1, 2.2);}
}class Perturbation extends FindMinima {Perturbation() {algorithm = (line) -> Arrays.asList(3.3, 4.4);}
}class Bisection extends FindMinima {Bisection() {algorithm = (line) -> Arrays.asList(5.5, 6.6);}
}// “上下文”控制着策略:
class MinimaSolver {private FindMinima strategy;MinimaSolver(FindMinima strategy) {this.strategy = strategy;}List<Double> minima(List<Double> line) {return strategy.algorithm.apply(line);}void changeAlgorithm(FindMinima newAlgorithm) {strategy = newAlgorithm;}
}public class StrategyPattern {public static void main(String[] args) {MinimaSolver solver =new MinimaSolver(new LeastSquares());List<Double> line = Arrays.asList(1.0, 2.0, 1.0, 2.0, -1.0,3.0, 4.0, 5.0, 4.0 );System.out.println(solver.minima(line));solver.changeAlgorithm(new Perturbation());System.out.println(solver.minima(line));solver.changeAlgorithm(new Bisection());System.out.println(solver.minima(line));}
}
/* 输出:
[1.1, 2.2]
[3.3, 4.4]
[5.5, 6.6]
*/

MinimaSolver中的changeAlgorithm()将一个不同的策略插入strategy字段,然后minima()就会使用不同的算法了。

可以通过将上下文合并到FindMinima中来简化该方案:

// patterns/strategy/StrategyPattern2.java
// {java patterns.strategy.StrategyPattern2}
package patterns.strategy;
import java.util.function.*;
import java.util.*;// “上下文”现在合并了:
class FindMinima2 {privateFunction<List<Double>, List<Double>> algorithm;FindMinima2() { leastSquares(); } // 默认// 各种策略:void leastSquares() {algorithm = (line) -> Arrays.asList(1.1, 2.2);}void perturbation() {algorithm = (line) -> Arrays.asList(3.3, 4.4);}void bisection() {algorithm = (line) -> Arrays.asList(5.5, 6.6);}List<Double> minima(List<Double> line) {return algorithm.apply(line);}
}public class StrategyPattern2 {public static void main(String[] args) {FindMinima2 solver = new FindMinima2();List<Double> line = Arrays.asList(1.0, 2.0, 1.0, 2.0, -1.0,3.0, 4.0, 5.0, 4.0 );System.out.println(solver.minima(line));solver.perturbation();System.out.println(solver.minima(line));solver.bisection();System.out.println(solver.minima(line));}
}
/* 输出:
[1.1, 2.2]
[3.3, 4.4]
[5.5, 6.6]
*/

FindMinima2封装了各种不同的算法,现在还同时包含了上下文,因此可以在单个类中控制算法的选择了。

7.3

   

职责链模式

这种模式在本书第 1 章中介绍过。

职责链模式(Chain of Responsibility)大概可以被看作用策略对象实现的动态泛化版本的递归。先产生一个调用,然后一系列策略逐个尝试处理该调用。当某个策略处理成功,或者试过所有策略后(都不成功),整个过程结束。在递归过程中,一个方法不断重复调用自身,直到满足某个终结条件。而在职责链模式中,一个方法会调用相同基类的方法的不同实现,后者又会调用该基类方法的另一个实现,以此类推,直到满足终结条件。

不同于通过调用单个方法来处理请求,职责链中的多个方法都有机会处理该请求,因此职责链模式很适用于实现专家系统。职责链是高效的链表结构,因此可以动态地进行创建或修改。也可以将它看作一种更通用的、动态构建的 switch 语句。

你可能想让 StrategyPattern.java 自动尝试不同的算法,直到命中合适的那个。职责链模式提供了实现该功能的途径。Result 是包含一个 success 标签的信使,这样收件人就可以知道该算法是否成功,line 则用来承载实际的数据。如果某个算法失败,则返回 Result.fail:

// patterns/chain/Result.java
// 承载结果或表示失败
package patterns.chain;
import java.util.*;public class Result {public final boolean success;public final List<Double> line;public Result(List<Double> data) {success = true;line = data;}private Result() {success = false;line = Collections.<Double>emptyList();}public static final Result fail = new Result();
}

由于失败是预期结果,因此在某个策略失败时,返回Result.fail比抛出异常更加合适。

// patterns/chain/ChainOfResponsibility.java
// {java patterns.chain.ChainOfResponsibility}
package patterns.chain;
import java.util.*;
import java.util.function.*;interface Algorithm {Result algorithm(List<Double> line);
}class FindMinima {public static Result test(boolean success, String id, double d1, double d2) {System.out.println(id);if(success) // 实际的测试/计算:return new Result(Arrays.asList(d1, d2));else // 尝试职责链中的下一个:return Result.fail;}public static Result leastSquares(List<Double> line) {return test(false, "LeastSquares", 1.1, 2.2);}public static Result perturbation(List<Double> line) {return test(false, "Perturbation", 3.3, 4.4);}public static Result bisection(List<Double> line) {return test(true, "Bisection", 5.5, 6.6);}static List<Function<List<Double>, Result>>algorithms = Arrays.asList(FindMinima::leastSquares,FindMinima::perturbation,FindMinima::bisection);public static Result minima(List<Double> line) {for(Function<List<Double>, Result> alg :algorithms) {Result result = alg.apply(line);if(result.success)return result;}return Result.fail;}
}public class ChainOfResponsibility {public static void main(String[] args) {FindMinima solver = new FindMinima();List<Double> line = Arrays.asList(1.0, 2.0, 1.0, 2.0, -1.0,3.0, 4.0, 5.0, 4.0);Result result = solver.minima(line);if(result.success)System.out.println(result.line);elseSystem.out.println("No algorithm found");}
}
/* 输出:
LeastSquares
Perturbation
Bisection
[5.5, 6.6]
*/

每个Algorithm的实现针对algorithm()方法都有不同的方法。在FindMinima中,创建了一组algorithm的List(这便是职责链的那条“链”),而minima()方法会遍历该List,并找出执行成功的算法。


0f6905d7ac4f88b28a15a4e52379cfdb.png

4fdaabb5815d0471e0c5d2aa799b796b.jpeg

本书特色

  • 查漏宝典:涵盖Java关键特性的设计原理和应用方法

  • 避坑指南:以产业实践的得失为鉴,指明Java开发者不可不知的设计陷阱

  • 经典普适:值得不同层次的Java开发者反复研读

  • 专家领读:4位一线业务专家、知名作译者帮你拆解书中难点,总结Java开发精要

值得一提的是,为了帮助新手加深理解,出版方邀请了4位从业10年以上知名作译者DDD 专家张逸、服务端专家梁桂钊、软件系统架构专家王前明、译者陈德伟)为本书录制【精讲视频】和【导读指南】,该视频已在B站和图灵社区发布,感兴趣的朋友可以去看看。

c9e5cc8fc938076fa8ff08289867db5b.png

往期推荐

Bruce Eckel:再聊设计模式(篇一)

Bruce Eckel:再聊设计模式(篇二)封装实现

Bruce Eckel:再聊设计模式(篇三)工厂模式

Bruce Eckel - 详解函数式编程(卷一)

Bruce Eckel - 详解函数式编程(卷二)

Bruce Eckel - 详解函数式编程(卷三)

知明:技术 Leader 的思考法

史海峰:在时代节点上顺势而为是一种幸运

我,程序员,马上35岁...

后Kubernetes时代的微服务

怎样才能持续输出技术原创文章?

同事多线程使用不当导致OOM,被我怒怼了

如何在 SpringBoot 项目中控制 RocketMQ消费线程数量

聊聊 8 种架构模式

Google工程师是怎么写设计文档的?

聊聊技术人员如何做好团队管理

ade661da459e3d5378b81f73bdc52819.jpeg

这篇关于Bruce Eckel:再聊设计模式(篇四)函数对象模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/729405

相关文章

C++类和对象之初始化列表的使用方式

《C++类和对象之初始化列表的使用方式》:本文主要介绍C++类和对象之初始化列表的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C++初始化列表详解:性能优化与正确实践什么是初始化列表?初始化列表的三大核心作用1. 性能优化:避免不必要的赋值操作2. 强

Redis高可用-主从复制、哨兵模式与集群模式详解

《Redis高可用-主从复制、哨兵模式与集群模式详解》:本文主要介绍Redis高可用-主从复制、哨兵模式与集群模式的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录Redis高可用-主从复制、哨兵模式与集群模式概要一、主从复制(Master-Slave Repli

Kotlin运算符重载函数及作用场景

《Kotlin运算符重载函数及作用场景》在Kotlin里,运算符重载函数允许为自定义类型重新定义现有的运算符(如+-…)行为,从而让自定义类型能像内置类型那样使用运算符,本文给大家介绍Kotlin运算... 目录基本语法作用场景类对象数据类型接口注意事项在 Kotlin 里,运算符重载函数允许为自定义类型重

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Pandas中统计汇总可视化函数plot()的使用

《Pandas中统计汇总可视化函数plot()的使用》Pandas提供了许多强大的数据处理和分析功能,其中plot()函数就是其可视化功能的一个重要组成部分,本文主要介绍了Pandas中统计汇总可视化... 目录一、plot()函数简介二、plot()函数的基本用法三、plot()函数的参数详解四、使用pl

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

Python的time模块一些常用功能(各种与时间相关的函数)

《Python的time模块一些常用功能(各种与时间相关的函数)》Python的time模块提供了各种与时间相关的函数,包括获取当前时间、处理时间间隔、执行时间测量等,:本文主要介绍Python的... 目录1. 获取当前时间2. 时间格式化3. 延时执行4. 时间戳运算5. 计算代码执行时间6. 转换为指

Python正则表达式语法及re模块中的常用函数详解

《Python正则表达式语法及re模块中的常用函数详解》这篇文章主要给大家介绍了关于Python正则表达式语法及re模块中常用函数的相关资料,正则表达式是一种强大的字符串处理工具,可以用于匹配、切分、... 目录概念、作用和步骤语法re模块中的常用函数总结 概念、作用和步骤概念: 本身也是一个字符串,其中

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化