《图解设计模式》笔记(四)分开考虑

九、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角色是表示“状态”的类,并且每次状态变化时,被委托对象的类都必定会被替换。

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

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

相关文章

LeetCode.55.跳跃游戏(贪心算法思路)

题目描述&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 输…

Docker容器镜像及其打包

容器镜像分类 1. 系统类镜像 2. 应⽤镜像 搜索镜像 # 默认docker.hub docker search centos 下载镜像 docker pull centos 默认下载最新版本 1. 打包 [rootdocker001 ~]# systemctl start docker.service [rootdocker001 ~]# docker save -o centos.tar centos:latest [root…

基于SpringBoot的线上教学平台系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言 Java 数据库 MySQL 技术 SpringBoot框架&#xff0c;Java语言 工具 IDEA/Eclipse、Navicat、Maven 系统展示 首页 管理员功能模块 学员功能模块 前台首页…

Ozon在奥伦堡州开设首个配送中心,Ozon还有机会赚钱吗?

Ozon平台成立于1998年&#xff0c;是俄罗斯唯一上市的B2C电商平台&#xff0c;在俄罗斯电商市场中占据着到达62%的市场份额&#xff0c;具有强大的市场影响力和吸引力。Ozon拥有数千万的活跃用户&#xff0c;覆盖了俄罗斯各个年龄段和消费层次的群体&#xff0c;而且Ozon拥有俄…

“精准学”官宣将公布中国首个语音端到端大模型

教育科技公司“精准学”宣布&#xff0c;公司已在AI语音交互技术上取得领先性的突破&#xff0c;成功训练了中国首个语音端到端大模型“心流知镜-s(V02)”&#xff0c;可直接实现语音输入-语音输出的交互&#xff0c;使其更适配辅学场景&#xff0c;使大模型达到“真人老师”级…

当《黑神话:悟空》遇上openKylin,国产力量的极致碰撞!

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 万众瞩目的国产3A游戏巨作《黑神话&#xff1a;悟…

【人工智能】如何在白嫖的阿里云PAI平台上跑模型?

在“交互式建模&#xff08;DSW&#xff09;”中新建实例&#xff0c;阿里云自带的示例镜像是很少的&#xff0c;所以我们只需要筛选出适合你的项目的CUDA版本就好。DSW实例可以看作是一个Linux虚拟机&#xff0c;之后我们在实例中新建另一个Python环境使用即可。 新建完实例后…

DevExpress中Blazor部分学习

DevExpress中Blazor学习 1 DevExpress版本2 学习步骤2.1 查看Dev相应的Demo2.2 创建第一个相关应用2.3 使用XPO进行相关数据操作2.4 Dev Blazor使用XPO操作 3 学习中遇到问题及解决方案3.1 打开Dev相关Demo报错 1 DevExpress版本 安装较新的DevExpress&#xff0c;我这边使用的…

基于FreeRTOS的STM32多功能手表

前言 项目背景 项目演示 使用到的硬件 项目原理图 目前版本实现的功能 设计到的freertos知识 实现思路 代码讲解 初始化GPIO引脚、配置时钟 蜂鸣器初始化以及软件定时器创建 系统默认创建的defaultTaskHandle 创建七个Task&#xff0c;代表七个功能 ShowTimeTask …

京东软件测试岗面试题(干货)含答案+文档

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 前面看到了一些面试题&#xff0c;总感觉会用得到&#xff0c;但是看一遍又记不住&#xff0c;所以我把面试题都整合在一起&#xff0c;都是来自各路大佬的分享&am…

自然语言处理系列三十三》 语义相似度》同义词词林》算法原理

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 自然语言处理系列三十三同义词词林算法原理代码实战 总结 自然语…

LLama 3 跨各种 GPU 类型的基准测试

2024 年 4 月 18 日&#xff0c;AI 社区对 Llama 3 70B 的发布表示欢迎&#xff0c;这是一款最先进的大型语言模型 &#xff08;LLM&#xff09;。该型号是 Llama 系列的下一代产品&#xff0c;支持广泛的用例。该模型 istelf 在广泛的行业平台上表现良好&#xff0c;并提供了新…

基于STM32开发的智能室内照明系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化光照强度监测与处理照明控制与状态指示Wi-Fi通信与远程控制应用场景 智能家居照明管理办公室和公共场所的智能照明常见问题及解决方案 常见问题解决方案结论 1. 引言 随着智能家居…

探索地理空间分析的新世界:Geopandas的魔力

文章目录 探索地理空间分析的新世界&#xff1a;Geopandas的魔力背景&#xff1a;为何选择Geopandas&#xff1f;这个库是什么&#xff1f;如何安装这个库&#xff1f;五个简单的库函数使用方法场景应用&#xff1a;Geopandas在实际工作中的应用常见bug及解决方案总结 探索地理…

【HarmonyOS NEXT星河版开发学习】综合测试案例-各平台评论部分

目录 前言 功能展示 整体页面布局 最新和最热 写评论 点赞功能 界面构建 初始数据的准备 列表项部分的渲染 底部区域 index部分 知识点概述 List组件 List组件简介 ListItem组件详解 ListItemGroup组件介绍 ForEach循环渲染 列表分割线设置 列表排列方向设…

“游戏开发效率革命:AI绘画案例分享,大专生如何实现工作效率十倍提升与副业拓展“

一、游戏开发者的日常 我叫李明&#xff0c;是一名计算机专业的大专生。自从毕业以来&#xff0c;我就一直在一家游戏开发公司工作&#xff0c;转眼间&#xff0c;已经五年了。五年的时光&#xff0c;我从一个职场小白成长为了一名熟练的游戏开发者。但随之而来的&#xff0c;是…

GROUP_CONCAT 用法详解(Mysql)

GROUP_CONCAT GROUP_CONCAT 是 MySQL 中的一个聚合函数&#xff0c;用于将分组后的多行数据连接成一个单一的字符串。 通常用于将某个列的多个值合并到一个字符串中&#xff0c;以便更方便地显示或处理数据。 GROUP_CONCAT([DISTINCT] column_name[ORDER BY column_name [ASC…

Android SDK 遇到的坑之 AIUI(星火大模型)

目录 一、AIUI 二、常见错误 2.1 唤醒无效 2.2 错误码:600103 1、存放唤醒词等资源的路径 2、aiui_phone.cfg 文件配置 3、vtn.ini 文件配置 2.3 错误码:600022 相关推荐 一、AIUI 需要给桌面机器人(医康养)应用做语音指引/控制/健康咨询等功能&#xff0c;根据调研选择A…

书生大模型实战营第三期基础岛第二课——8G 显存玩转书生大模型 Demo

8G 显存玩转书生大模型 Demo 基础任务进阶作业一&#xff1a;进阶作业二&#xff1a; 基础任务 使用 Cli Demo 完成 InternLM2-Chat-1.8B 模型的部署&#xff0c;并生成 300 字小故事&#xff0c;记录复现过程并截图。 创建conda环境 # 创建环境 conda create -n demo pytho…

golang实现一个简单的rpc框架

前言 RPC在分布式系统中经常使用&#xff0c;这里写一个简单的demo实践一下。 code 先生成 go.mod 文件 go mod init rpc-try01定义方法 package model// Args 是 RPC 方法的参数结构体 type Args struct {A, B int }// Arith 定义了一个简单的算术服务 type Arith struct{…