SOLID 开放/封闭原则如何适应依赖注入和依赖倒转

2024-01-19

我开始应用 SOLID 原则,但发现它们有点矛盾。我的问题如下:

我对依赖倒置原则的理解是类应该依赖于抽象。实际上,这意味着类应该从接口派生。到目前为止一切都很好。

接下来我对开放/封闭原则的理解是,在某个截止点之后,您不应该更改类的内容,而应该扩展和覆盖。到目前为止这对我来说是有意义的。

因此,考虑到上述情况,我最终会得到这样的结果:

public interface IAbstraction
{
    string method1(int example);
}

public Class Abstraction : IAbstraction
{
   public virtual string method1(int example)
   {
       return example.toString();
   }
}

然后在时间 T,method1 现在需要将“ExtraInfo”添加到其返回值上。我不会改变当前的实现,而是创建一个扩展的新类Abstraction并让它做我需要的事情,如下所示。

public Class AbstractionV2 : Abstraction 
{
   public override string method1(int example)
   {
       return example.toString() + " ExtraInfo";
   }
}

我可以看到这样做的原因是只有我想调用这个更新方法的代码才会调用它,其余的代码将调用旧方法。

一切对我来说都是有意义的 - 我认为我的理解是正确的?

然而,我也使用依赖注入(简单注入器),所以我的实现从来不是通过具体的类,而是通过我的 DI 配置,如下所示:

container.Register<IAbstraction, Abstraction>();

这里的问题是,在此设置下,我可以将我的 DI 配置更新为:

container.Register<IAbstraction, AbstractionV2>();

在这种情况下,所有实例现在都将调用新方法,这意味着我未能实现不更改原始方法。

OR

我创建一个新界面IAbstractionV2并在那里实现更新的功能 - 意味着接口声明的重复。

我看不到任何解决这个问题的方法 - 这让我想知道依赖注入和 SOLID 是否兼容?或者我在这里遗漏了什么?


TL;DR

  • 当我们说代码“可用于扩展”时,并不自动意味着我们继承它或向现有接口添加新方法。继承只是“扩展”行为的一种方式。
  • 当我们应用依赖倒置原则时,我们不直接依赖于其他具体类,因此如果我们需要它们做不同的事情,我们不需要更改这些实现。依赖于抽象的类是可扩展的,因为替换抽象的实现可以从现有类中获取新的行为,而无需修改它们。

(我有点倾向于删除其余部分,因为它用更多的文字表达了同样的意思。)


检查这句话可能有助于阐明这个问题:

然后在时间 T,method1 现在需要将“ExtraInfo”添加到其返回值上。

这听起来像是吹毛求疵,但从来没有一种方法needs返回任何东西。方法不像人有话要说并且需要说。 “需要”取决于方法的调用者。调用者需要该方法返回的内容。

如果呼叫者经过int example并接收example.ToString(),但现在需要接收example.ToString() + " ExtraInfo",那么改变的是调用者的需求,而不是被调用方法的需求。

如果调用者的需求发生了变化,是否就意味着所有调用者的需求都发生了变化?如果您更改该方法返回的内容以满足一个调用者的需求,其他调用者可能会受到不利影响。这就是为什么您可能会创建一些新的东西来满足特定调用者的需求,同时保持现有的方法或类不变。从这个意义上说,现有代码是“封闭的”,而同时其行为是开放扩展的。

此外,扩展现有代码并不一定意味着修改类、向接口添加方法或继承。它只是意味着它合并了现有的代码,同时提供了一些额外的东西。

让我们回到刚开始的课程。

public Class Abstraction : IAbstraction
{
     public virtual string method1(int example)
     {
         return example.toString();
     }
}

现在您需要一个包含该类的功能但执行不同操作的类。它可能看起来像这样。 (在这个例子中,它看起来有点矫枉过正,但在现实世界的例子中却并非如此。)

public class SomethingDifferent : IAbstraction
{
     private readonly IAbstraction _inner;

     public SomethingDifferent(IAbstraction inner)
     {
         _inner = inner;
     }

     public string method1(int example)
     {
         return _inner.method1 + " ExtraInfo";
     }
}

在这种情况下,新类碰巧实现了相同的接口,因此现在您有了同一接口的两个实现。但它不需要。可能是这样的:

public class SomethingDifferent
{
     private readonly IAbstraction _inner;

     public SomethingDifferent(IAbstraction inner)
     {
         _inner = inner;
     }

     public string DoMyOwnThing(int example)
     {
         return _inner.method1 + " ExtraInfo";
     }
}

您还可以通过继承“扩展”原始类的行为:

public Class AbstractionTwo : Abstraction
{
     public overrride string method1(int example)
     {
         return base.method1(example) + " ExtraInfo";
     }
}

所有这些示例都扩展了现有代码而不对其进行修改。在实践中,有时将现有属性和方法添加到新类中可能是有益的,但即使如此,我们也希望避免修改已经完成其工作的部分。如果我们编写具有单一职责的简单类,那么我们就不太可能发现自己把厨房水槽扔进现有的类中。


这与依赖倒置原则或依赖于抽象有什么关系?没有什么直接的,但是应用依赖倒置原则可以帮助我们应用开闭原则。

在实际情况下,我们的类所依赖的抽象应该为这些类的使用而设计。我们不只是采用其他人创建的任何接口并将其粘贴到我们的中心类中。我们正在设计满足我们需求的界面,然后调整其他类来满足这些需求。

例如,假设Abstraction and IAbstraction在你的类库中,我碰巧需要一些以某种方式格式化数字的东西,而你的类看起来可以满足我的需要。我不只是要注射IAbstraction进入我的班级。我将编写一个接口来实现我想要的功能:

public interface IFormatsNumbersTheWayIWant
{
    string FormatNumber(int number);
}

然后我将编写使用您的类的该接口的实现,例如:

public class YourAbstractionNumberFormatter : IFormatsNumbersTheWayIWant
{
    public string FormatNumber(int number)
    {
        return new Abstraction().method1 + " my string";
    }
}

(或者它可能取决于IAbstraction使用构造函数注入,无论如何。)

如果我没有应用依赖倒置原则并且我直接依赖于Abstraction然后我必须弄清楚如何改变你的班级来做什么 我需要。但是因为我依赖于为满足我的需求而创建的抽象,所以我会自动考虑如何合并类的行为,而不是更改它。一旦我这样做了,我显然不希望你的班级的行为发生意外的改变。

我还可以依赖你的界面 -IAbstraction- 并创建我自己的实现。但创建我自己的也有助于我遵守接口隔离原则。我所依赖的接口是为我创建的,所以它不会有我不需要的东西。你的可能还有其他我不需要的东西,或者你可以稍后添加更多。

实际上,我们有时只是使用提供给我们的抽象,例如IDataReader。但希望这是稍后我们编写具体实现细节时的事情。当涉及到应用程序的主要行为时(如果您正在执行 DDD,即“域”),最好定义我们的类将依赖的接口,然后使外部类适应它们。

最后,依赖于抽象的类也更具可扩展性,因为我们可以替换它们的依赖项 - 实际上改变(扩展)它们的行为,而无需对类本身进行任何更改。我们可以扩展它们而不是修改它们。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SOLID 开放/封闭原则如何适应依赖注入和依赖倒转 的相关文章

  • Windows 上使用 g++ 的 Makefile,链接库

    我已经厌倦了 MSVC 6 以及每个人总是告诉我它是一个蹩脚的编译器等等 所以现在我决定尝试使用 vim 加 g 和 makefile 这是我的问题 我有以下 makefile This is supposed to be a commen
  • 以相反的顺序迭代可变参数模板参数

    如果我手动反转传递给它的模板参数的顺序 以下代码将起作用 template
  • MVC 重定向到没有控制器的视图

    希望应该是一个简单的 我创建了一个通用错误视图 当整个站点的操作方法内发生异常时 我想显示该视图 我创建了一个部分页面 所有导航都位于其中 因此我不需要在此视图上使用控制器 那么如何从控制器内的操作方法重定向到它 像这样的东西 HttpPo
  • 为什么opencv videowriter这么慢?

    你好 stackoverflow 社区 我有一个棘手的问题 我需要你的帮助来了解这里发生了什么 我的程序从视频采集卡 Blackmagic 捕获帧 到目前为止 它工作得很好 同时我用 opencv cv imshow 显示捕获的图像 它也工
  • 切换图片框可见性 C#

    为什么图片框控件的可见性属性在这里不起作用 我最初将它们设置为 false 以便在屏幕加载时它们不可见 但后来我想切换这个 我已完成以下操作 但似乎不起作用 这是一个 Windows 窗体应用程序 private void Action w
  • Android NDK C++“wstring”支持

    我有用 C 编写的源代码 lib 现在我想在 Android NDK 项目 NDK 6 中编译并使用相同的源代码 lib 我能够编译大多数 C 文件 除了基于 std wstring 的功能 在 Application mk 中 当我指定时
  • 如何将pdf页面设置设置为打印属性对话框?

    大家好 我想知道如何设置 pdf 页面设置到打印属性对话框 例如 如果我的 PDF 页面设置为横向 则布局会自动显示横向而不是纵向 如果我的 PDF 页面设置为纵向 则布局会自动显示纵向 我在这个主题上做了很多研发 但没有找到任何满意的链接
  • 在运行时设置 DataGridView 上的 DataFormatString?

    是否可以在运行时设置 ASP NET DataGridView 中的列或单元格的 DataFormatString 属性 这应该有效 BoundField priceField grid Columns 0 as BoundField pr
  • rand() 播种与 time() 问题

    我很难弄清楚如何使用 rand 并使用 Xcode 用 time 为其播种 我想生成 0 到 1 之间的随机十进制数 该代码为我提供了元素 1 和 2 看似随机的数字 但元素 0 始终在 0 077 左右 有什么想法吗 我的代码是 incl
  • 将 C# 反射代码移植到 Metro-Ui

    我正在尝试移植使用反射的现有 C 类 通用工厂 但我无法编译这段代码 Type types Assembly GetAssembly typeof TProduct GetTypes foreach Type type in types i
  • 在通过网络发送之前压缩位图

    我正在尝试通过网络发送位图屏幕截图 因此我需要在发送之前对其进行压缩 有一个库或方法可以做到这一点吗 当您将图像保存到流时 您have选择一种格式 几乎所有位图格式 bmp gif jpg png 都使用一种或多种压缩形式 因此 只需选择适
  • 导出到 CSV 时 Gridview 出现空行

    这个问题是由进一步讨论引发的这个问题 https stackoverflow com questions 6674555 export gridview data into csv file 6674589 noredirect 1 com
  • QThread - 使用槽 quit() 退出线程

    我想在线程完成运行时通知对象 但是 我无法让线程正确退出 我有以下代码 处理器 cpp thread new QThread tw new ThreadWorker connect tw SIGNAL updateStatus QStrin
  • realloc():重新分配为 char * 上的 strcat 腾出空间时下一个大小无效 [重复]

    这个问题在这里已经有答案了 我在以下代码中收到无效内存错误 printf s n FINE 5 printf s LENGTH IS d n FINE 6 strlen buffer char realloc buffer strlen b
  • 有没有更好的方法来获取每个项目与谓词匹配的子序列?

    假设我有一个 IEnumerable 例如 2 1 42 0 9 6 5 3 8 我需要获得与谓词匹配的项目的 运行 例如 如果我的谓词是 bool isSmallerThanSix int number 我想得到以下输出 2 1 0 5
  • C++ 标准中短语“构造函数没有名称”的含义

    在尝试理解 C 标准中的 构造函数没有名称 这句话时 我似乎在 clang 中发现了一个错误 有人可以证实这一点吗 VS2015 and gcc rejects this code and I think they it are is co
  • 通过 cmake 链接作为外部项目包含的 opencv 库[重复]

    这个问题在这里已经有答案了 我对 cmake 比较陌生 经过几天的努力无法弄清楚以下事情 我有一个依赖于 opencv 的项目 它本身就是一个 cmake 项目 我想静态链接 opencv 库 我正在做的是我的项目中有一份 opencv 源
  • 创建带有部分的选项卡式侧边栏 WPF

    我正在尝试创建一个带有部分的选项卡式侧边栏 如 WPF 中的以下内容 我考虑过几种方法 但是有没有更简单 更优雅的方法呢 方法一 列表框 Using a ListBox并将 SelectedItem 绑定到右侧内容控件所绑定的值 为了区分标
  • 将文本从文本文件添加到 PDF 文件[重复]

    这个问题在这里已经有答案了 这是我的代码 using FileStream msReport new FileStream pdfPath FileMode Create step 1 using Document pdfDoc new D
  • 如何确定给定方法可以抛出哪些异常?

    我的问题和这个真的一样 找出 C 中方法可能抛出的异常 https stackoverflow com questions 264747 finding out what exceptions a method might throw in

随机推荐

  • 当我使用快捷键时获取我的ContextMenuStrip的SourceControl

    我有单身ContextMenuStrip连接到两个控件 DataGridView In the ToolStripMenuItem单击事件 我设法得到原始调用者 DataGridView 用这个代码 var menu ToolStripDr
  • 查找 Excel 电子表格的模板路径

    我有一个带有 VBA 代码的 Excel 电子表格模板 xltm 文件 我想在模板打开的电子表格中找到模板的路径 问题示例 用户通过双击 C My Stuff 中的模板文件打开一个新电子表格 他们填写单元格 然后在询问他们的名称后单击一个按
  • MySQL所有父子关系

    我有一个名为table 它有一个名为id与类型INT 11 代表行的标识符 它还有其他字段 但我认为它们与这个问题无关 我有另一个表名为table children 它有一个名为parent与类型INT 11 指的是table id作为外键
  • 使用 1 位 ALU 制作 16 位 ALU

    你好 我正在尝试从几个 1 位 ALU 创建一个 16 位 ALU 我创建了一个名为 basic alu1 的包 其中包含 1 位 ALU 的组件 其代码是 library ieee use ieee std logic 1164 all
  • Arduino 中的字符串比较

    我正在开发基于网络的家庭自动化系统 因此我的Arduino向服务器发送请求并在串行监视器中获得以下响应以及 loneOn 这是由于Serial println r 陈述 HTTP 1 1 200 OK Date Mon 13 Oct 201
  • 使用 Boost.python 将 Python 列表传递给 C++ 向量

    如何传递对象类型的 Python 列表ClassName到一个接受a的C 函数vector
  • 如何防止我的应用程序在 iPhone 上后台运行

    有什么方法可以让应用程序在按下主页按钮时退出而不是进入后台吗 出于安全原因 如果应用程序不在后台运行 而是在按下主页时实际关闭 那就更好了 这是not为了用户的安全 而是为了应用程序上的公司数据 所以这不是用户的选择 除了强行退出之外 我找
  • Nodejs/V8 是否将编译后的机器代码存储在磁盘上的任何位置?

    Edit Node 从 Node 8 3 开始使用字节码 在此之前 源代码直接编译为机器代码 我进行了大量的 Python 编码 并且 pyc 文件中总是存在字节码 我想知道节点是否将其机器代码存储在类似的文件中 例如 将机器代码表示保留
  • 网络摄像机RTSP地址

    我有 IP 摄像头 但我不知道它是 RTSP 流的完整正确 URL 地址 仅 RTSP 设置中有端口 但据我了解 192 168 1 132 554 还不够 VLC 说找不到 rtsp 流 如何找出正确的 url 地址 如何找出正确的 ur
  • 复合主键是否为N-M关系?

    假设我们有 3 个表 实际上我现在有 2 个表 但这个例子可能会更好地说明这个想法 Person ID int 主键 名称 nvarchar xx Group ID int 主键 名称 nvarchar xx Role ID int 主键
  • Android Java:关闭屏幕

    我正在制作一个使用接近传感器打开和关闭屏幕的应用程序 接近代码已完成 但我在使用屏幕控件时遇到了麻烦 我读过我应该使用 PowerManager manager PowerManager getSystemService Context P
  • 为什么 MFMailComposeViewController 返回 MFMailComposeResultFailed?

    我的应用程序遇到一个奇怪的问题 我需要您的帮助 我正在使用 MFMailComposeViewController 发送带有附件数据的电子邮件 附件是 PDF CSV 或 XLS 文件 还可以将 ZIP 文件添加到邮件中 在大多数情况下一切
  • 关闭窗口前Socket.IO断开连接问题

    我试图阻止客户端与服务器断开连接 因此 在用户关闭打开应用程序的窗口之前 我会执行以下操作 window bind beforeunload function return Close the app 但问题是 无论用户选择离开还是留在应用
  • Groovy Node.depthFirst() 返回节点和字符串列表?

    我希望有人能指出我在这里遗漏的一些明显的东西 我觉得我已经这样做了一百次了 出于某种原因 今晚 由此产生的行为让我陷入了困境 我正在从公共 API 读取一些 XML 我想从某个节点中提取所有文本 body 内的所有内容 其中还包括各种子节点
  • Java 并发收集少写多读

    我想使用基于比较器的键值映射 这将进行读取和罕见的写入操作 通过调度程序每 3 个月一次 集合的初始加载将在应用程序启动时完成 另请注意 写入将 将单个条目添加到地图 不会修改地图的任何现有条目 ConcurrentSkipListMap
  • xcode 5 语法高亮不适用于单元测试文件

    我已经为我的项目设置了单元测试 最初 我在编译测试文件期间遇到了错误 最后我解决了它 我的测试现在正在工作 现在的问题是语法突出显示不适用于测试文件 仅适用于此 我尝试按照这个answer https stackoverflow com a
  • RegExp 中的混乱 不情愿的量词?爪哇

    为什么我得到输出ab对于以下带有 Relucutant 量词的正则表达式代码 Pattern p Pattern compile abc Matcher m p matcher abcfoo while m find System out
  • 测试内部使用 MySQL 特定查询的代码的正确方法是什么

    我正在收集数据并使用 Java 将这些数据存储在 MySQL 数据库中 此外 我使用 Maven 来构建项目 使用 TestNG 作为测试框架 使用 Spring Jdbc 来访问数据库 我实现了一个 DAO 层 它封装了对数据库的访问 除
  • 使用 JDBC 创建 PostgreSQL 触发器

    我正在尝试创建一个 PostgreSQL 触发器Play2 0 http www playframework org 数据库演化脚本 sql代码相对简单并且在pgAdminIII中运行良好 CREATE OR REPLACE FUNCTIO
  • SOLID 开放/封闭原则如何适应依赖注入和依赖倒转

    我开始应用 SOLID 原则 但发现它们有点矛盾 我的问题如下 我对依赖倒置原则的理解是类应该依赖于抽象 实际上 这意味着类应该从接口派生 到目前为止一切都很好 接下来我对开放 封闭原则的理解是 在某个截止点之后 您不应该更改类的内容 而应