多态与切换案例之间的权衡 列出选项按照代码写更少的代码主机托管结论

如何解决多态与切换案例之间的权衡 列出选项按照代码写更少的代码主机托管结论

关于这一点,我没有找到任何清晰的文章,但是我想知道为什么多态性是推荐的设计模式,而不是详尽的开关案例/模式匹配。我之所以这样问是因为,由于不使用多态类,我从经验丰富的开发人员那里得到了很多帮助,这一直困扰着我。我个人经历过多态,经历了一段糟糕的时光,而使用开关箱则经历了一段美好的时光,我认为减少抽象性和间接性使代码的可读性变得如此容易。这与通常被视为行业标准的“干净代码”之类的书形成鲜明对比。

注意:我使用TypeScript,因此以下示例可能不适用于其他语言,但是我认为只要您具有详尽的模式匹配/切换用例,该原理通常适用。

列出选项

如果您想知道一个动作的可能值(带有枚举,请切换大小写),这很简单。对于课程,这需要一些反射魔术

// definitely two actions here,I could even loop over them programmatically with basic primitives
enum Action {
  A = 'a',B = 'b',}

按照代码

依赖注入和抽象类意味着跳转到定义永远不会到您想要的地方

function doLetterThing(myEnum: Action) {
  switch (myEnum) {
    case Action.A:
      return;
    case Action.B;
      return;
    default:
      exhaustiveCheck(myEnum);
  }
}

function doLetterThing(action: BaseAction) {
  action.doAction();
}

如果我跳到BaseActiondoAction的定义,我将进入抽象类,这对调试功能或实现没有帮助。如果您的依赖项注入模式只有一个类,那么这意味着您可以“猜测”,方法是转到主类/函数并查找如何实例化“ BaseAction”,然后将其键入该位置并滚动查找实施。对于开发人员来说,这似乎通常是糟糕的UX。

(关于依赖注入是否良好的小注解,特征在必要的情况下似乎可以做得很好(尽管通常做得过早而不是必要,似乎会使代码难以遵循) )

写更少的代码

这取决于,但是如果必须为您的基本类型定义一个额外的抽象类,并覆盖所有函数类型,那么与单行切换的情况相比,这又少了多少代码?如果您在枚举中添加了一个选项,那么这里的类型很好,您的类型检查器将标记您需要处理的所有位置,这通常需要为案例添加1行,为实现添加1+行。将其与需要定义一个新类的多态类进行比较,该类需要具有正确参数以及打开和关闭括号的新函数语法。在大多数情况下,切换案例的代码更少,行数也更少。

主机托管

类型的所有内容都放在一个不错的地方,但是通常只要实现这样的功能,我都会寻找类似实现的功能。使用开关盒时,它非常接近,需要使用派生类在另一个文件或目录中查找和定位。

如果我实现了一种功能更改,例如在一种类型的字符串的末尾修剪空格,则需要打开所有的类文件,以确保它们是否实现了在所有类文件中都正确实现的类似功能。而且,如果我忘记了,可能会在不知道的情况下针对不同类型使用不同的行为。通过开关,co位置使这一点非常明显(尽管并非万无一失)

结论

我错过了什么吗?我们拥有这些明确的设计原则是没有道理的,我基本上只能找到有关的肯定性文章,却看不到任何明显的好处,与某些基本的模式匹配样式开发相比,还有严重的弊端

解决方法

我认为您错过了重点。编写干净的代码的主要目的不是在实现当前功能时使您的生活更轻松,而是在将来扩展或维护代码时使您的生活更轻松。

在您的示例中,您可能会感觉使用开关盒来实现两个动作。但是,如果将来需要添加更多操作,会发生什么情况?使用抽象类,您可以轻松地创建新的操作类型,并且不需要修改调用方。但是,如果您继续使用开关盒,则会变得更加混乱,尤其是在复杂的情况下。

此外,遵循更好的设计模式(在这种情况下为DI)将使代码更易于测试。仅考虑简单情况时,可能找不到使用适当设计模式的用处。但是,如果您考虑更广泛的方面,那确实会有所收获。

,

考虑,尤其是OCP和DI。

  • 要扩展开关盒或枚举并在将来添加新功能,必须修改现有代码。修改遗留代码既冒险又昂贵。冒险,因为您可能会无意中引入回归。昂贵,因为您必须学习(或重新学习)实施细节,然后重新测试旧版代码(在修改之前,旧版代码可能一直在工作)。

  • 对具体实现的依赖会导致紧密耦合并抑制模块化。这使代码变得僵化和脆弱,因为一个地方的更改会影响许多依赖项。

此外,请考虑可伸缩性。抽象支持多种实现,其中许多在创建抽象时可能是未知的。开发人员无需了解或关心其他实现。一个开发人员可以在10个开关中完成多少案例? 100?

请注意,这并不意味着多态性(或OOP)适用于每个类或应用程序。例如,Should every class implement an interface?中存在一些对策,在考虑可扩展性和可伸缩性时,假设代码库会随着时间增长。如果您要处理几千行代码,那么“企业级”标准将变得很沉重。同样,在只有几个类的情况下将几个类耦合在一起也不是很明显。

当代码能够向新的方向发展时,好的设计的好处会在几年后实现。

,

“基类”违反了清洁代码。不应该有一个“基类”,不仅是为了不好的命名,还是为了继承规则的组成。因此,从现在开始,我将假定它是其他类在其中实现而不是扩展的接口(这对我的示例很重要)。首先,我希望看到您的担忧:

关注问题的答案

这取决于,但是否必须为您定义一个额外的抽象类 基本类型,再加上覆盖所有功能类型,那么更少的代码 比单线开关盒

我认为“写更少的代码”不应该是字符数。然后Ruby或GoLang甚至Python击败了Java,显然不是吗?因此,我不会计算行数,括号等,而是应该测试/维护的代码。

类型的所有内容都放在一个不错的地方,但通常 每当我实现这样的功能时,我都会寻找类似的东西 实现的功能。

如果“寻找相似的东西”意味着,将实现放在一起可以从相似的功能中复制某些部分,那么我们在这里也有一些线索可以进行重构。具有不同的实现类有其自己的原因。它们的实现是完全不同的。他们可能会遵循某种模式,从交流的角度来看;如果我们有Letter和Phone实现,则无需查看它们的实现即可实现其中之一。因此,这里的假设是错误的,如果您希望他们的代码实现新功能,则您的界面不会为您提供新功能的指导。让我们更具体些吧;

interface Communication {
   sendMessage()
}

Letter implements Communication {

   sendMessage() {
     // get receiver
     // get sender
     // set message
     // send message
  }

}

现在我们需要Phone,所以如果我们去实现Letter并获得如何实现Phone的想法,那么我们的界面不足以指导我们的实现。从技术上讲,电话和信函在发送消息方面有所不同。然后我们在这里需要一个设计模式,也许是模板模式?让我们看看;

interface Communication {
   default sendMessage() {
     getMessageFactory().sendMessage(getSender(),getReceiver(),getBody())
   }

   getSender()
   getReceiver()
   getBody()
}

Letter implements Communication {

   getSender() { returns sender }
   getReceiver() {returns receiver }
   getBody() {returns body}
   getMessageFactory {returns LetterMessageFactory}
}

现在,当我们需要实现Phone时,我们无需查看其他实现的细节。现在,我们恰好需要返回的内容以及通讯接口的默认方法可以处理如何发送消息。

如果我实现了功能更改,例如在末端修剪空格 一种类型的字符串,我需要打开所有的类文件 确保他们是否实现了类似的实现 正确地在所有这些人中...

因此,如果存在“功能更改”,则应该仅是其实现的类,而不是所有类。您不应该更改所有实现。还是如果所有这些都是相同的实现,那么为什么每个实现都不同?应将其保留为其界面中的默认方法。然后,如果需要更改功能,则仅更改默认方法,并且应在一个位置更新实现并进行测试。

这些是我想回答您的问题的要点。但我认为主要要点是您没有得到好处。在从事大型项目之前,我还在努力工作,其他团队需要扩展我的功能。我将通过一些极端的例子将好处划分为主题,这可能会更有助于理解:

易于阅读

通常,当您看到一个函数时,您不应感到去执行它来了解那里正在发生的事情。它应该是不言自明的。基于这个事实; action.doAction();->或说communication.sendMessage()(如果它们实现了Communicate接口)。我不需要去寻找它的基类,搜索实现等等进行调试。即使实现类是“ Letter”或“ Phone”,我也知道他们发送消息,我也不需要它们的实现细节。因此,我不想看到所有已实现的类,例如您的示例“ switch Letter; Phone ..”等。在您的示例中,doLetterThing负责一件事情(doAction),因为它们都执行相同的事情,那么您为什么要展示您的开发人员所有这些案例?他们只是使代码更难阅读。

易于扩展

想象一下,您正在扩展一个大型项目,而您无法访问其源代码(我想举一个极端的例子,以更轻松地展示其优势)。在Java世界中,我可以说您正在实现SPI(服务提供商接口)。我可以为您展示2个示例,https://github.com/apereo/cashttps://github.com/keycloak/keycloak,在这里您可以看到接口和实现是分开的,只需在需要时实现新的行为,而无需接触原始源代码。为什么这很重要?再次想象以下情况;

  • 让我们假设Keycloak调用了communication.sendMessage()。他们不知道构建时的实现。如果在这种情况下扩展Keycloak,则可以拥有自己的实现Communication接口的类,我们称其为“计算机”。知道类路径中是否有SPI时,Keycloak会读取它并调用您的computer.sendMessage()。我们没有涉及源代码,但是扩展了Message Handler类的功能。如果我们在不接触源代码的情况下针对转换案例进行编码,就无法实现这一目标。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-