《图解设计模式》笔记(四)分开考虑
九、Bridge模式:将类的功能层次结构与实现层次结构分离
类的两个层次结构和作用
类的功能层次结构:希望增加新功能时
父类有基本功能,在子类中增加新功能
Something父类
…├─SomethingGood子类
想要再增加新功能
Something父类
…├─SomethingGood子类
… …├─SomethingBetter子类
注:通常,类的层次结构关系不应过深
类的实现层次结构:希望增加新的实现时
回顾 Template Method模式,定义了抽象类,有多个子类实现。
父类通过 声明抽象方法 来 定义 接口(API)
子类通过 实现具体方法 来 实现 接口(API)
AbstractClass抽象类
…├─ConcreteClass具体实现类
… …├─AnotherConcreteClass具体实现类
当类的层次结构只有一层时,功能层次结构与实现层次结构是混杂在一个层次结构中的。
这样很容易使类的层次结构变得复杂,难理解。因为自己难确定应该在类的哪一个层次结构中去增加子类。
因此,我们需要将“类的功能层次结构”与“类的实现层次结构”分离为两个独立的类层次结构。
如果只是简单地将它们分开,两者之间必然会缺少联系。所以我们需要Bridge模式在它们之间搭建一座桥梁。
示例程序类图
Display
public class Display {private DisplayImpl impl;public Display(DisplayImpl impl) {this.impl = impl;}// 注意这3个方法的实现,都调用了impl字段的实现方法。// 这样,Display的接口(API)就被转换成为了 DisplayImpl的接口(API)。public void open() {impl.rawOpen();}public void print() {impl.rawPrint();}public void close() {impl.rawClose();}// display方法调用 open、print、Close这3个Display类的接口(API)进行了“显示”处理。public final void display() {open();print();close();}
}
CountDisplay
public class CountDisplay extends Display {public CountDisplay(DisplayImpl impl) {super(impl);}// 循环显示times次public void multiDisplay(int times) {open();for (int i = 0; i < times; i++) {print();}close();}
}
StringDisplayImpl
public class StringDisplayImpl extends DisplayImpl {private String string; // 要显示的字符串private int width; // 以字节单位计算出的字符串的宽度public StringDisplayImpl(String string) { // 构造函数接收要显示的字符串stringthis.string = string; // 将它保存在字段中this.width = string.getBytes().length; // 把字符串的宽度也保存在字段中,以供使用。}public void rawOpen() {printLine();}public void rawPrint() {System.out.println("|" + string + "|"); // 前后加上"|"并显示}public void rawClose() {printLine();}private void printLine() {System.out.print("+"); // 显示用来表示方框的角的"+"for (int i = 0; i < width; i++) { // 显示width个"-"System.out.print("-"); // 将其用作方框的边框}System.out.println("+"); // 显示用来表示方框的角的"+"}
}
Main
public class Main {public static void main(String[] args) {// 虽然变量d1中保存的是Display类的实例,而变量d2和d3中保存的是CountDisplay类的实例// 但它们内部都保存着StringDisplayImp1类的实例。Display d1 = new Display(new StringDisplayImpl("Hello, China."));Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));d1.display();d2.display();d3.display();d3.multiDisplay(5);}
}
角色
-
Abstraction(抽象化)
位于“类的功能层次结构”的最上层。它使用Implementor角色的方法定义了基本的功能。该角色中保存了Implementor角色的实例。
示例中是Display类。 -
RefinedAbstraction(改善后的抽象化)
在 Abstraction角色的基础上增加了新功能的角色。
示例中是CountDisplay类。 -
Implementor(实现者)
位于“类的实现层次结构”的最上层。它定义了用于实现Abstraction角色的接口(API)的方法。
示例中是DisplayImpl类。 -
Concretelmplementor(具体实现者)
负责实现在Implementor角色中定义的接口(API)。
示例中是StringDisplayImpl类。
扩展思路的要点
分开后更容易扩展
Bridge 模式的特征:将“类的功能层次结构”与“类的实现层次结构”分离开。
将类的这两个层次结构分离开有利于独立地对它们进行扩展。
当想要增加功能时,只需要在“类的功能层次结构”一侧增加类,不必对“类的实现层次结构”做任何修改。
而且,增加后的功能可被“所有的实现”使用。
继承是强关联,委托是弱关联
继承是强关联关系,委托是弱关联关系。
虽然使用“继承”很容易扩展类,但是类之间也形成了一种强关联关系,可使用“委托”来代替“继承”关系。
示例程序的Display类中使用了“委托”,Display类的impl字段保存了实现的实例,类的任务就发生了转移。
调用open 方法会调用impl.rawOpen()方法
调用print方法会调用impl.rawPrint()方法
调用close方法会调用impl.rawClose()方法
也就是说,当其他类要求 Display类“工作”的时候,Display类并非自己工作,而是将工作“交给impl”。这就是“委托”。
在Template Method模式(第3章)中也讨论了继承和委托的关系,可以再回顾下。
相关的设计模式
-
Template Method模式(第3章)
在 Template Method 模式中使用了“类的实现层次结构”。父类调用抽象方法,而子类实现抽象方法。
-
Abstract Factory 模式(第8章)
为了能根据需求设计出良好的Concretelmplementor角色,有时我们会使用Abstract Factory 模式。
-
Adapter模式(第2章)
使用 Bridge模式可以将类的功能层次结构与类的实现层次结构分离,并在此基础上使这些层次结构结合起来。
而使用 Adapter 模式则可以结合那些功能上相似但是接口(API)不同的类。
十、Strategy模式:整体地替换算法
Strategy 的意思是“策略”,指的是与敌军对垒时行军作战的方法。在编程中,可以将它理解为“算法”。
使用Strategy模式可以整体地替换算法的实现部分。
能够整体地替换算法,可以方便地以不同的算法去解决同一个问题。
示例程序的功能是让电脑玩“猜拳”游戏。
考虑了两种猜拳的策略。
第一种策略是“如果这局猜拳获胜,那么下一局也出一样的手势”(WinningStrategy),这是一种稍微有些笨的策略;
第二种策略是“根据上一局的手势从概率上计算出下一局的手势”(ProbStrategy)。
示例程序类图
Hand
Hand表示猜拳游戏中的“手势”的类
虽然Hand类会被其他类(Player类、WinningStrategy类、Probstrategy类)使用,
但它并非 Strategy 模式中的角色。
public class Hand {public static final int HANDVALUE_GUU = 0; // 表示石头的值public static final int HANDVALUE_CHO = 1; // 表示剪刀的值public static final int HANDVALUE_PAA = 2; // 表示布的值public static final Hand[] hand = { // 表示猜拳中3种手势的实例new Hand(HANDVALUE_GUU),new Hand(HANDVALUE_CHO),new Hand(HANDVALUE_PAA),};private static final String[] name = { // 表示猜拳中手势所对应的字符串"石头", "剪刀", "布",};private int handvalue; // 表示猜拳中出的手势的值private Hand(int handvalue) {this.handvalue = handvalue;}public static Hand getHand(int handvalue) { // 根据手势的值获取其对应的实例return hand[handvalue];}public boolean isStrongerThan(Hand h) { // 如果this胜了h则返回truereturn fight(h) == 1;}public boolean isWeakerThan(Hand h) { // 如果this输给了h则返回truereturn fight(h) == -1;}private int fight(Hand h) { // 计分:平0, 胜1, 负-1if (this == h) {return 0;} else if ((this.handvalue + 1) % 3 == h.handvalue) {return 1;} else {return -1;}}public String toString() { // 转换为手势值所对应的字符串return name[handvalue];}
}
Strategy
public interface Strategy {// 获取下一局要出的手势。调用该方法后,实现了strategy接口的类会绞尽脑汁想出下一局出什么手势。public abstract Hand nextHand();// 学习“上一局的手势是否获胜了”,Strategy接口的实现类就会根据参数改变自己的内部状态public abstract void study(boolean win);
}
WinningStrategy
import java.util.Random;public class WinningStrategy implements Strategy {private Random random;private boolean won = false;// 上一局出的手势private Hand prevHand;public WinningStrategy(int seed) {random = new Random(seed);}public Hand nextHand() {if (!won) {prevHand = Hand.getHand(random.nextInt(3));}return prevHand;}public void study(boolean win) {won = win;}
}
ProbStrategy
import java.util.Random;public class ProbStrategy implements Strategy {private Random random;private int prevHandValue = 0;private int currentHandValue = 0;// history[上一局出的手势][这一局所出的手势],值越大表示过去的胜率越高private int[][] history = {{ 1, 1, 1, },{ 1, 1, 1, },{ 1, 1, 1, },};public ProbStrategy(int seed) {random = new Random(seed);}public Hand nextHand() {int bet = random.nextInt(getSum(currentHandValue));int handvalue = 0;if (bet < history[currentHandValue][0]) {handvalue = 0;} else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {handvalue = 1;} else {handvalue = 2;}prevHandValue = currentHandValue;currentHandValue = handvalue;return Hand.getHand(handvalue);}private int getSum(int hv) {int sum = 0;for (int i = 0; i < 3; i++) {sum += history[hv][i];}return sum;}// study方法会根据nextHand方法返回的手势的胜负结果来更新history字段中的值。public void study(boolean win) {if (win) {history[prevHandValue][currentHandValue]++;} else {history[prevHandValue][(currentHandValue + 1) % 3]++;history[prevHandValue][(currentHandValue + 2) % 3]++;}}
}
Player
public class Player {private String name;private Strategy strategy;// wincount、losecount 和 gamecount 用于记录选手的猜拳结果。private int wincount;private int losecount;private int gamecount;public Player(String name, Strategy strategy) { // 赋予姓名和策略this.name = name;this.strategy = strategy;}// 获取下一局手势的方法,不过实际上决定下一局手势的是各个策略。// nextHand方法将自己的工作委托给了 Strategy,这就形成了一种委托关系。public Hand nextHand() { // 策略决定下一局要出的手势return strategy.nextHand();}// Player类会通过strategy字段调用 study方法,然后study 方法会改变策略的内部状态。public void win() { // 胜strategy.study(true);wincount++;gamecount++;}public void lose() { // 负strategy.study(false);losecount++;gamecount++;}public void even() { // 平gamecount++;}public String toString() {return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";}
}
Main
public class Main {public static void main(String[] args) {if (args.length != 2) {System.out.println("Usage: java Main randomseed1 randomseed2");System.out.println("Example: java Main 314 15");System.exit(0);}int seed1 = Integer.parseInt(args[0]);int seed2 = Integer.parseInt(args[1]);// 在生成Player类的实例时,需要向其传递“姓名”和“策略”。Player player1 = new Player("Taro", new WinningStrategy(seed1));Player player2 = new Player("Hana", new ProbStrategy(seed2));for (int i = 0; i < 10000; i++) {Hand nextHand1 = player1.nextHand();Hand nextHand2 = player2.nextHand();if (nextHand1.isStrongerThan(nextHand2)) {System.out.println("Winner:" + player1);player1.win();player2.lose();} else if (nextHand2.isStrongerThan(nextHand1)) {System.out.println("Winner:" + player2);player1.lose();player2.win();} else {System.out.println("Even...");player1.even();player2.even();}}System.out.println("Total result:");System.out.println(player1.toString());System.out.println(player2.toString());}
}
角色
-
Strategy (策略)
负责决定实现策略所必需的接口(API)。
示例中是:Strategy接口。
-
ConcreteStrategy (具体的策略)
负责实现 Strategy角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)。
示例中是:WinningStrategy类、ProbStrategy类。
-
Context(上下文)
负责使用 Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用 Strategy角色的接口(API))。
示例中是:Player类。
拓展思路的要点
为什么需要特意编写 Strategy角色
当想通过改善算法来提高算法的处理速度时,如果使用了 Strategy模式,仅修改ConcreteStrategy角色即可,就不必修改Strategy角色的接口(API)了。
而且,使用委托这种弱关联关系可以很方便地整体替换算法。
例如,如果想比较原来的算法与改进后的算法的处理速度有多大区别,简单地替换下算法即可进行测试。
使用 Strategy模式编写象棋程序时,可以方便地根据棋手的选择切换AI例程的水平。
程序运行中也可以切换策略
如果使用 Strategy模式,在程序运行中也可以切换 ConcreteStrategy角色。
例如,在内存容量少的运行环境中可以使用 SlowBut LessMemoryStrategy(速度慢但省内存的策略),而在内存容量多的运行环境中则可以使用 FastButMoreMemoryStrategy(速度快但耗内存的策略)。
此外,还可以用某种算法去“验算”另外一种算法。
例如,假设要在某个表格计算软件的开发版本中进行复杂的计算。这时,我们可以准备两种算法,即“高速但计算上可能有 Bug的算法”和“低速但计算准确的算法”,然后让后者去验算前者的计算结果。
相关的设计模式
-
Flyweight 模式(第20章)
有时会使用 Flyweight模式让多个地方可以共用 ConcreteStrategy 角色。
-
Abstract Factory 模式(第8章)
使用 Strategy模式可以整体地替换算法。
使用 Abstract Factory 模式则可以整体地替换具体工厂、零件和产品。 -
State 模式(第19章)
使用 Strategy模式和 State模式都可以替换被委托对象,而且它们的类之间的关系也很相似,但是两种模式的目的不同。
在 Strategy模式中,ConcreteStrategy角色是表示算法的类,并且可以替换被委托对象的类(非必要也可不替换)。
而在 State 模式中,ConcreteState角色是表示“状态”的类,并且每次状态变化时,被委托对象的类都必定会被替换。
相关文章:

《图解设计模式》笔记(四)分开考虑
九、Bridge模式:将类的功能层次结构与实现层次结构分离 类的两个层次结构和作用 类的功能层次结构:希望增加新功能时 父类有基本功能,在子类中增加新功能 Something父类 …├─SomethingGood子类 想要再增加新功能 Something父类 …├─So…...

Linux shell编程学习笔记74:sed命令——沧海横流任我行(中)
0 前言 自 60 年代末以来,sed 一直是 Unix 标准工具箱的一部分。 Sed在以下三种情况下特别有用: 编辑太大的文件,无法进行舒适的交互式编辑; 当编辑命令序列过于复杂而无法在交互模式下轻松键入时,可以编辑任何大小的…...

[数据集][目标检测]道路积水检测数据集VOC+YOLO格式2699张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):2699 标注数量(xml文件个数):2699 标注数量(txt文件个数):2699 标注…...

不同路径
不同路径 思路: 法一:动态规划 const int N 110; class Solution { int dp[N][N];//dp[i][j]:从起点走到 i j的路径个数。 public:int uniquePaths(int m, int n) {for(int i1;i<n;i){dp[1][i]1;} for(int i1;i<m;i) dp[i][1]1;f…...

【HTML】HTML学习之引入CSS样式表
1、CSS样式规则 选择器{属性1:属性值1; 属性2:属性值2; 属性3:属性值3;}2、HTML引入CSS样式表 2.1、行内式 行内式也称为内联样式,是通过标签的style属性来设置元素的样式,其基本语法格式如下: <标签名 style"属性1:属性值1; 属性2:属性值2;…...

shaushaushau1
CVE-2023-7130 靶标介绍: College Notes Gallery 2.0 允许通过“/notes/login.php”中的参数‘user’进行 SQL 注入。利用这个问题可能会使攻击者有机会破坏应用程序,访问或修改数据. 已经告诉你在哪里存在sql注入了,一般上来应该先目录扫…...

揭秘面试必备:高频算法与面试题全面解析
干货分享,感谢您的阅读! (暂存篇---后续会删除,完整版和持续更新见高频面试题基本总结回顾(含笔试高频算法整理)) 备注:引用请标注出处,同时存在的问题请在相关博客留言…...
设计模式-visit模式-在语法树的实践
文章目录 背景示例代码分析灵活性双重分派 总结 背景 很多项目代码有accept()用法,在calcite 里也看到了这种,深入了解一下 语法树遍历:编译器通常会将源代码解析成抽象语法树(AST)。为了实现不同的编译阶段ÿ…...

ZK-Rollups测评
1. 引言 Matter Labs团队和多个高校研究人员一起,发布2024年论文《Analyzing and Benchmarking ZK-Rollups》,开源代码见: https://github.com/StefanosChaliasos/zkrollup-benchmarking(Python) 其中: …...
redis生产使用场景(一):并行流+二级缓存
本文主要介绍 redis 缓存在线上的使用场景 由于业务的特殊性,在生产库用户表中,大概有 50 多万的测试用户,在真实业务计算中,要把测试用户给筛选掉,所以在计算前,需要把测试用户加载到 redis 缓存中&#x…...
EXCEL跨文件查询,指定条件列,返回满足条件的指定列
EXCEL跨文件查询,指定条件列,返回满足条件的指定列 Private Sub cmd_find_from_workbooks_Click() Dim S_Cols As String, thePath As String, Sor_Col As Integer, sz_Cols As Variant S_Cols T_jieguo_cols.Text sz_Cols Split(S_Cols, ",&quo…...

[数据集][目标检测]流水线物件检测数据集VOC+YOLO格式9255张26类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):9255 标注数量(xml文件个数):9255 标注数量(txt文件个数):9255 标注…...

StarRocks 存算分离 Compaction 原理
前言 StarRocks 中每次数据摄入都会生成一个新的数据版本,而查询时需要将所有版本数据进行合并才能获得一个正确的结果,如果历史数据版本太多,那么查询时需要读取的文件数也会很多,造成查询效率低下。因而 StarRocks 存在内部任务…...

搭建ELK日志采集与分析系统
SpringCloud微服务实战——企业级开发框架 💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您…...
java集合中自动排序的treeset和treemap
底层 TreeSet 和 TreeMap 的底层架构都是基于红黑树实现的。红黑树是一种自平衡的二叉搜索树,其特性保证了插入、删除和查找操作的时间复杂度为 (O( log n \log n logn)),无论数据量多大,操作性能都能保持在合理的范围内。 1. 红黑树概述 红黑树是一种平衡二叉搜索树(…...

Android 修改SystemUI 音量条的声音进度条样式
一、前言 Android System UI 开发经常会遇到修改音量进度条样式的需求,主要涉及的类有VolumeDialogImpl与xml文件,接下来会逐步实现流程。先看看效果。 修改前 修改后 二、找到对应类 通过aidegen 打断点调试对应代码类VolumeDialogImpl定位到volume_d…...
电商场景的视频生成的prompt测评集合
1.收集的一些提示词 一台写着Vidu的赛车在路上飞驰,赛车上面坐着一只乌龟 一个宇航员在太空中骑单车 两个巨大的机器人在打架,电影风格,史诗感,高细节 在科幻电影风格中,两个巨大的机器人在城市废墟中激烈战斗。使用高角度俯拍,展现机器人的宏伟和战斗的史诗感。机器人…...

day34
1 非阻塞型IO 让我们的read函数不再阻塞,无论是否读取到消息,立刻返回 1.1 fcntl函数 原型:int fcntl(int fd, int cmd, ... /* arg */ ); 调用:int flag fcntl(描述符,F_GETFL) fcntl(描述符,F_SETFL&am…...

无缝融入,即刻智能[三]:Dify-LLM平台知识库构建(多路召回、精排重排),43K+星标见证专属智能方案
无缝融入,即刻智能[三]:Dify-LLM平台知识库构建(多路召回、精排重排),43K+星标见证专属智能方案 大语言模型的训练数据一般基于公开的数据,且每一次训练需要消耗大量算力,这意味着模型的知识一般不会包含私有领域的知识,同时在公开知识领域存在一定的滞后性。为了解决这一…...
AWS服务WAF
在 AWS 中使用 Web Application Firewall (WAF) 来防御常见的攻击手段,如 DDoS 攻击和 SQL 注入攻击,可以通过创建和配置规则来实现。下面是如何使用 AWS WAF 阻止这些常见攻击的详细操作步骤。 1. 登录到 AWS 管理控制台 打开 AWS 管理控制台。使用你…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...