SOLID:DIP 是否意味着禁止对某个对象 composition/aggreation?

SOLID: Does DIP mean that composition/aggreation to an object is forbidden?

我正在尝试理解和应用 SOLID 原则。 关于依赖倒置原则,这是否意味着 composition/aggregation 禁止对象? 因此必须始终使用一个接口来访问另一个 class 方法?

我的意思是:

class ServiceClass {
  void serviceClasshelper();
}

class MainClass {
  void MainClass(ServiceClass service); // To use serviceClasshelper
}

必须改为:

class ServiceInterface {
  virtual void interfaceHelper() =0;
}

class ServiceClass : public ServiceInterface {
  void serviceClasshelper();
  void interfaceHelper() { serviceClasshelper(); };
}

class MainClass {
  void MainClass(ServiceInterface service); // Uses interfaceHelper
}

我认为(或者至少我希望)我理解原理。 但是想知道是否可以这样改写。 事实上,我读到的关于 DIP 的东西建议使用接口。

谢谢!

是的,没错,DIP 说你需要依赖抽象(接口)而不是具体(class 与实际实现)。

这个想法是,如果你依赖 ServiceClass,你就依赖于这个特定的实现,你不能轻易地用另一个实现替换它。例如,您有 AnotherServiceClass,但要使用它而不是 ServiceClass,您必须从 ServiceClass 继承它,这可能是不可取的,甚至是不可能的。在有接口依赖的情况下,你可以轻松做到这一点。

更新:这里有更具体的例子来说明上面的想法

// Service class does something useful (sendNetworkRequest)
// and is able to report the results
class ServiceClass {
  void reportResults();
  void sendNetworkRequest();
}

// Main logger collects results
class MainLogger {
  void registerService(ServiceClass service);
}

我们将 ServiceClass 传递给 MainLogger::registerService,记录器(例如)定期调用 service->reportResults() 并保存到文件。

现在,假设我们有另一个服务:

// Service class does something useful (calculateYearlyReport)
// and is able to report the results
class AnotherServiceClass {
  void reportResults();
  void calculateYearlyReport();
}

如果我们在这里使用具体的 classes 并从 ServiceClass 继承 AnotherServiceClass,我们将能够将它传递给 MainLogger::registerServcie,但除此之外违反DIP,我们也会违反LSP(因为subclass不能代替BaseClass)和ISP(因为我们这里显然有不同的接口- 一个报告结果,另一个做有用的工作)并且你还会违反另一个好的规则来选择 composition over inheritance.

直接传递 ServiceClass 的实例的另一个缺点是现在你不能保证 MainLogger 不依赖于内部结构(访问其他方法/成员等)。

还有一点就是代码维护的方便性——当你看到接口通过时,你只需要查看两个对象之间通信的"protocol"。使用具体 class 时,您实际上需要通过实现来了解如何使用传递的对象。

因此,一般来说,最好尽可能遵循 SOLID 和其他 OOP 原则,这样可以使代码更清晰,更易于支持,并让您避免以后难以修复的错误。

基本上DIP的主要思想是:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

如您所见,它说的是 should 而不是 must。它不禁止你做任何事情。

如果你的class是composite of / aggregate to其他特定的classes(不是interfaces / abstract classes),没关系!您的代码仍会编译并且 运行,不会显示任何警告告诉您:"hey, you are violating DIP"。所以我认为你的问题的答案是:不,不是

假设您的系统由一千个 classes 组成,您可以将 DIP 应用于 2 classes 并且其中之一取决于第三个特定的 class (不是interfaces / abstract classes)。只要你的问题解决了,什么都不重要。因此,请尽量使您的解决方案简短明了 -> 易于理解。相信我,当您在 1 个月后回顾您的解决方案时,您会发现它很有价值。

DIP 是一个指南,它告诉您在遇到一组特定问题时该怎么做才能解决它们。这不是一个神奇的指南,它是有代价的:复杂性。您应用 DIP 的次数越多,您的系统就会越复杂。所以明智地使用它。为了进一步支持这一点,我建议你看看这个参考资料(摘自 Head First: Design Patterns 书)。访问此 link(它是一个 PDF 文件)并在顶部栏导航至页面 635 / 681。或者,如果你足够懒惰,只需阅读下面的引用:

Your Mind on Patterns

The Beginner uses patterns everywhere. This is good: the beginner gets lots of experience with and practice using patterns. The beginner also thinks, “The more patterns I use, the better the design.” The beginner will learn this is not so, that all designs should be as simple as possible. Complexity and patterns should only be used where they are needed for practical extensibility.

As learning progresses, the Intermediate mind starts to see where patterns are needed and where they aren’t. The intermediate mind still tries to fit too many square patterns into round holes, but also begins to see that patterns can be adapted to fit situations where the canonical pattern doesn’t fit.

The Zen mind is able to see patterns where they fit naturally. The Zen mind is not obsessed with using patterns; rather it looks for simple solutions that best solve the problem. The Zen mind thinks in terms of the object principles and their trade-offs. When a need for a pattern naturally arises, the Zen mind applies it knowing well that it may require adaptation. The Zen mind also sees relationships to similar patterns and understands the subtleties of differences in the intent of related patterns. The Zen mind is also a Beginner mind — it doesn’t let all that pattern knowledge overly influence design decisions.

最后,我将向您介绍一个使用 DIP 的四人组设计模式:Strategy

示例问题:一个Character可以使用3种武器:HandSwordGun。他(Character)可以随时更换他当前的武器。

分析:这是一个很典型的问题。棘手的部分是如何在 运行 时间处理武器交换。

候选解决方案 策略:(只是草图):

weapon = new Hand();
weapon.Attack(); // Implementation of Hand class

weapon = new Sword();
weapon.Attack(); // Implementation of Sword class

weapon = new Gun();
weapon.Attack(); // Implementation of Gun class

其他使用 DIP 的设计模式和框架:

  • Inversion of Control (a.k.a IoC)
  • Dependency Injection
  • Autofac framework(您可以下载并使用它)