设计模式 Template Method Pattern(Inheritance) vs Strategy Pattern(Delegation)

news2024/11/23 12:57:43

Template Method Pattern 和 Strategy Pattern 是两种常用的行为设计模式。他们分别用了继承inheritance委托delegation两种不同的实现方法,因为上篇文章讲过了UML图,所以这篇顺便可以把两种不同模式的UML图都带出来一起说明。

Template Method Pattern

模板方法模式(Template Method Pattern)定义了算法的骨架,我们通过继承inheritance的方式在子类(subtypes)中写具体的实现细节。这种方法允许我们在不修改模板方法的情况下,通过子类来改变算法的某些部分。

Behavioral Subtyping

在讲继承之前,首先要提到一个概念Behavioral subtyping ,这是面向对象编程中一个重要的原则,它提供了一种更正式的方法来确定何时应该使用继承(inheritance)和扩展(extension)。Behavioral subtyping 的一个核心观点是:当使用子类型替换父类型时,客户端代码不应受到影响。也就是说,子类型应该能够在语法和行为上满足其父类型的要求。这种思想与里氏替换原则(Liskov Substitution Principle, LSP)密切相关。实现这一观点的三个重要条件如下:

  1. Same or stronger invariants than super class: 子类型应具有与父类型相同或更强的不变式。不变式是类的属性在整个对象生命周期中需要满足的条件。这意味着子类型需要保持和父类型一致的属性约束,或者增加更严格的约束。这有助于确保子类型对象在行为上与父类型兼容,同时满足更特定的需求。这一点有些抽象,我们举个例子:比如有一个父类叫做 Bird,其中有一个属性叫做 speedBird 类的不变式可能是速度永远不为负数,即 speed >= 0。现在,我们创建一个子类 Penguin 来扩展 Bird 类。在这种情况下,Penguin 类需要满足与 Bird 类相同或更强的不变式。这意味着 Penguin 类也需要确保速度永远不为负数(相同的不变式),或者可以增加更严格的约束,例如速度上限(更强的不变式),如 0 <= speed <= 10

  2. Same or weaker preconditions for all methods in super class: 子类型的方法应具有与父类型相同或更弱的前置条件。前置条件是方法调用之前需要满足的条件,通常涉及方法输入参数的约束。这意味着子类型的方法应该对输入参数的限制更宽松或与父类型相同。这样,在使用子类型替换父类型时,原有的调用不会因为输入限制而出现错误。

  3. Same or stronger postconditions for all methods in super class: 子类型的方法应具有与父类型相同或更强的后置条件。后置条件是方法调用之后必须满足的条件,通常涉及方法输出结果的约束。这意味着子类型方法的输出结果应该满足父类型的要求或具有更强的保证。这样,当子类替换父类时,程序依赖于父类的输出结果的正确性不会受到影响。

class Car extends Vehicle {
    int fuel;
    boolean engineOn;
    //@ invariant fuel >= 0;
    //@ requires fuel > 0 && !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }
    //@ requires speed != 0;
    //@ ensures speed < old(speed)
    void brake() { … }
}

class Hybrid extends Car {
    int charge;
    //@ invariant charge >= 0;
    //@ requires (charge > 0 || fuel > 0) 
    &&
    !engineOn;
    //@ ensures engineOn;
    void start() { … }
    void accelerate() { … }
    //@ requires speed != 0;
    //@ ensures speed < \old(speed)
    //@ ensures charge > \old(charge)
void brake() { … }

在上面的例子中,Hybrid 就是 Car的behavioral subtyping。在这个例子中,Car 类有一个不变式://@ invariant fuel >= 0;,表示汽车的燃料量不能为负数。Hybrid 类作为 Car 类的子类,也应当满足这个不变式。同时,Hybrid 类有一个额外的不变式://@ invariant charge >= 0;,表示混合动力汽车的电量不能为负数。因此,Hybrid 类作为子类,满足了 Car 类的不变式(关于燃料量的限制)以及它自己的额外不变式(关于电量的限制)。这就是第一个条件。

对于另外两个条件,我们可以很轻易地知道它满足被覆写的方法 start 具有较弱的前置条件,以及被覆写的方法 brake 具有较强的后置条件。这两个条件能够让我们以后在任何地方把父类替换为子类都不会让程序出错。

一般满足behavioral subtyping时,我们就可以用template method pattern,下面template method pattern的UML图以及实例代码:

 这里UML中的空心实线箭头表示的是继承关系,我们通过具体的类class1和class2来实现抽象类并实现在抽象类中定义好的功能。

abstract class AbstractOrder {
    public abstract boolean lessThan(int i, int j);
}

class AscendingOrder extends AbstractOrder {
    public boolean lessThan(int i, int j) {
        return i < j;
    }
}

class DescendingOrder extends AbstractOrder {
    public boolean lessThan(int i, int j) {
        return i > j;
    }
}

// ...

static void sort(int[] list, AbstractOrder order) {
    // ...
    boolean mustSwap = order.lessThan(list[j], list[i]);
    // ...
}

上面是一个用template method pattern实现的扩展排序方式的代码示例。我们使用了一个抽象基类 AbstractOrderAscendingOrderDescendingOrder 分别继承自 AbstractOrder 类,并覆写了其中的 lessThan 方法。通过继承,AscendingOrderDescendingOrder 类隐式地获得了 AbstractOrder 类的所有方法和属性。这里,我们主要关注的是类型层次结构和代码重用。

Strategy Pattern

策略模式(Strategy Pattern)是一种行为设计模式,它允许在运行时根据需要切换不同的算法或策略。策略模式是通过委托delegation实线的,将算法的实现与使用算法的对象分离,从而提高了代码的灵活性和可扩展性。一般来说,能用template method pattern实现的,我们都可以用strategy pattern来实现,并且往往有更多的好处。

在strategy pattern中,我们会把策略抽象出一个接口来,然后用具体不同的策略去实现这个策略接口,在这里空心箭头加虚线表示类之间的实现关系,这里要和上面实线表示的继承关系做区分。在strategy pattern中,我们需要用哪个策略,就直接把哪个策略的具体对象放进Navigator里使用,我们使用了routeStrategy类来定义了这个策略,通过Java的dynamic dispatch可以自由地将指针指向不同的具体策略。

 上图是strategy pattern的interaction diagram,系统基于不同的策略去调用不同具体策略对象中的方法。这样做的好处是可以轻松地将策略引入到系统中,而无需修改客户端代码。策略模式允许将算法的实现与使用它们的客户端代码分离,提高了代码的可维护性和灵活性。

一个strategy pattern的代码例子:

interface Order {
boolean lessThan(int i, int j);
}
class AscendingOrder implements Order {
public boolean lessThan(int i, int j) { return i < j; }
}
class DescendingOrder implements Order {
public boolean lessThan(int i, int j) { return i > j; }
}
…
static void sort(int[] list, Order order) {
…
boolean mustSwap =
order.lessThan(list[j], list[i]);
…
}

其实看起来这个代码和上面的很像,但在委托的写法中,我们使用了一个接口 OrderAscendingOrderDescendingOrder 分别实现了 Order 接口。然后我们直接把要使用的策略(AscendingOrder或DescendingOrder )传给sort中的Order类型的变量,这样我们就以非常灵活且低耦合的方式实现了策略的自由切换。相当于我们把排序的具体算法“委托”给了这个具体的order对象,直接把人家叫过来使用,而不在乎他们具体的实现细节。在delegation中,我们关注的是定义一组行为(通过 Order 接口)并将这些行为分别实现。委托强调的是行为和组合,而不是类型层次结构。

关于这两种方法的总结:

继承(Inheritance)组合+委托(Composition + Delegation)都是面向对象设计中的关键概念。继承在强耦合关系中可以实现大量代码重用,但使用时应谨慎。而良好的设计通常更倾向于使用组合和委托,因为它们支持编程接口的重用和封装,有助于信息隐藏,并产生更易于测试的代码(而使用继承的话因为需要重写父类的代码, 往往需要知道父类的具体实现信息,不利于信息隐藏)。虽然继承在某些情况下用起来更顺手,但在设计时应优先考虑delegation。

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

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

相关文章

Mybatis的PageHepler用法

分页原理 分页在使用时的分类 物理分页: 在操作数据库中的表时,sql语句中使用了limit ?,?,此时sql语句返回的结果是分页结果 逻辑分页: 依赖程序的代码,其原理为:通过sql语句将数据库表中的所有数据都查询出,之后将数据保存在内存中,最终要显示的数据若涉及到分页,到内存中…

Java企业级信息系统开发01—采用spring配置文件管理bean

文章目录 一、Web开发技术二、spring框架&#xff08;一&#xff09;spring官网&#xff08;二&#xff09;spring框架优点&#xff08;三&#xff09;Spring框架核心概念1、IoC&#xff08;Inversion of Control&#xff09;和容器2、AOP&#xff08;Aspect-Oriented Programm…

Golang 包使用注意事项

1&#xff09;在给一个文件打包时&#xff0c;该包对应一个文件夹&#xff0c;比如这里的utils文件夹对应的包名就是utils&#xff0c;文件的包名通常和文件所在的文件夹名一致&#xff0c;一般为小写字母。 2&#xff09;当一个文件要使用其它包函数或变量时&#xff0c;需要…

【AI聊天 | GPT4教学】 —— 微软 New Bing GPT4 申请与使用保姆级教程(免魔法)

目录 认识 New Bing 1. 下载 Microsoft Edge 浏览器 2. 注册并登录 Microsoft 账号 3. 如何免科学上网使用 New Bing&#xff1f; 4. 加入 WaitList 候补名单 5. 使用 New Bing&#xff01; 6. 使用 Skype 免科学上网访问 New Bing&#xff01; 7. 在 Chrome 浏览器中使…

gpt人工智能详细介绍

chatgpt人工智能怎么下载 OpenAI ChatGPT不是一款普通的软件&#xff0c;它是由OpenAI开发的一款基于人工智能技术的自然语言生成器。因此&#xff0c;它并不需要像普通软件一样下载和安装在您的计算机上。 作为一个云端服务&#xff0c;OpenAI ChatGPT可以通过您的浏览器直接…

HBASE入门 基本shell命令(一)

一、登录连接shell $HBASE_HOME/bin/hbase shell二、基本命令 2.1help命令 help创建命名空间 create_namespace bigdata;查看命名空间 list_namespace命名空间default和habase是系统自带的 三、DDL 3.1创建表 create bigdata:student, {NAME > name, VERSIONS> 5}…

每日学术速递5.6

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.AG3D: Learning to Generate 3D Avatars from 2D Image Collections 标题&#xff1a;AG3D&#xff1a;学习从 2D 图像集合生成 3D 头像 作者&#xff1a;Zijian Dong, Xu Chen, …

Amper Music:AI创意音乐工具

【产品介绍】 Amper Music 是一家位于美国纽约的人工智能音乐技术公司&#xff0c;成立于2014年。 Amper Music是一个AI创意音乐工具&#xff0c;能让任何人为自己的内容制作原创音乐。无论你需要为视频、播客或互动内容配乐&#xff0c;Amper Music都能提供一个简单而强大的解…

【PHP在线定制商城网站源码V3.0】开源的DIY在线定制商城系统+在线礼品定制

源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/87637177 PHP在线定制商城网站源码&#xff0c;免费开源、免费下载。本商城基于mycncart开发。安装成功后即可浏览&#xff0c;你可以在后台->安装扩展功能上传安装插件&#xff0c;在代码调整中点击刷…

【边缘设备】yolov5训练与rknn模型导出并在RK3588部署(亲测有效)

保姆级教程&#xff0c;看这一篇就够用了 环境准备 将宿主机和开发板接入同一个局域网&#xff0c;方便开发。 宿主机 PC电脑&#xff0c;x86_64, 带显卡, 配置不表, 能训练和开发即可。系统&#xff1a; ubuntu 22.04 LTS 版本( ubuntu 18.04 LTS 以上)自带的远程软件&…

windeployqt工具打包C++ QT项目

目录 前言方法TIP 前言 使用VS编写好QT项目后&#xff0c;有时需要发送给他人进行测试。在此情况下&#xff0c;发送所有项目文件显然不可取&#xff0c;因为exe文件不能独立运行&#xff0c;故在测试前需要先配置项目环境&#xff0c;以确保运行所需的库文件能够完全。 因此&…

《操作系统》——计算机系统概述

前言&#xff1a; 在之前的【Linux】学习中&#xff0c;我们已经对常见指令已经开发工具等进行了详细的了解。紧接着&#xff0c;我们将要学习的便是关于【Linux进程】的基本知识。但是为了帮助大家更好的理解相关的知识概念&#xff0c;我先带领大家来学习关于《操作系统》这…

【SpringMVC】| 拦截器 | 跨域请求 | 原理详解 | 代码实操

目录 一. &#x1f981; 前言二. &#x1f981; 拦截器 & 跨域请求1. 拦截器Ⅰ. 拦截器和过滤器(Filter)的区别Ⅱ. 拦截器的使用步骤1. 创建SpringBoot项目&#xff0c;添加Spring Web依赖2. 创建控制器方法3. 创建拦截器类4. 编写JSP页面5. 配置Interceptor核心配置类6. 拦…

《理想国》读书感悟

第一卷 话题从年老谈到财富&#xff0c;引出了雪蒙拿的正义观&#xff1a;欠债还钱是正义&#xff0c;从而提出了“正义是什么”的探讨。 辩论中正义定义的演变&#xff1a; 实话实说欠债还债是正义&#xff08;商人的视角&#xff09;。报朋友以善&#xff0c;报敌人以恶是…

Springboot JSR303校验是怎么回事?

概述&#xff1a; 在做项目的过程中&#xff0c;除了要在前端进行数据校验外&#xff0c;服务端也必须做相应的校验&#xff0c;因为高手可绕过前端的校验&#xff0c;直接进入服务端调用相关的方法&#xff0c;进行资料的盗取或破坏。在前端如果使用VueElementUI的方式&#…

基于SpringBoot3从零配置SpringDoc

为了方便调试&#xff0c;更好的服务于前后端分离式的工作模式&#xff0c;我们给项目引入Swagger。 文章目录 1. SpringFox2. SpringDoc2.1 引入依赖2.2 配置文件2.3 语法2.4 使用示例Tag 用于标识controllerOperation 用于标识方法Schema 用于标识实体类和实体类的属性ApiRes…

Unity Nsight Graphcis 使用

前言 在渲染Profile中&#xff0c;大家经常喜欢使用Renderdoc软件, 之前我的一篇博客也介绍Renderdoc Profile渲染的流程 RenderDoc Debug UE4 Shader_ue4 debug shader_带帯大师兄的博客-CSDN博客 Renderdoc适合查看Draw哪一步出差了&#xff0c;导致效果不符合理想&#xf…

webpack : 无法加载文件 D:\...\node-v18.16.0-win-x64\webpack.ps1,因为在此系统上禁止运行脚本

用idea打开项目时&#xff0c;安装webpack打包的包之后&#xff0c;由于组策略问题拒绝执行脚本 解决方法 1、cmd打开命令行。输入&#xff1a;powershell 出现 PS 证明已经进入组策略模式 2、输入&#xff1a;get-executionpolicy&#xff0c;查看策略 ​ 输入&#xff1a…

浅谈线程池

浅谈线程池 1、线程池 1.1、线程池介绍 线程池是一种多线程处理形式&#xff0c;处理过程中将任务添加到队列&#xff0c;然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小&#xff0c;以默认的优先级运行&#xff0c;并处于多线程…

Golang每日一练(leetDay0057) 缺失区间、最大间距

目录 163. 缺失的区间 Missing Ranges &#x1f31f;&#x1f31f; 164. 最大间距 Maximum Gap &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏…