面向对象的设计模式 (Object-Oriented Design Patterns)
Understanding the concept and the problems that it addresses
了解概念及其解决的问题
DISCLAIMER: this article is the first part of a seriers on Objected-Oriented programming & design patterns.
免责声明 :本文是有关面向对象的编程和设计模式的系列文章的第一部分。
介绍 (Introduction)
Among all approaches to software development, the Object-Oriented paradigm has always seemed the most natural and logical to me. To share my opinion on why I believe it to be so, I’m writing a series of articles dedicated to OOP design thinking and development patterns.
在所有软件开发方法中,面向对象范例对我而言似乎一直是最自然和合乎逻辑的。 为了分享我为什么会这样的观点,我正在撰写一系列有关OOP设计思维和开发模式的文章。
And what could be a better starting point, than the principles that form the core not only of the OOP paradigm, but most modern software development methodologies as well?
而且,除了构成OOP范式核心以及大多数现代软件开发方法学核心的原理之外,还有什么是更好的起点?
One could argue that to describe the topic thoroughly, each of the principles should get a separate article. However, I would like to keep the description concise and on-point, as there are many more topics that I plan on covering in the series. Nevertheless, this is going to be a long article, so make sure to get a cup of tea or coffee to go with it :)
有人可能会争辩说,要全面描述该主题,每个原则都应该单独撰写一篇文章。 但是,我想使描述简洁明了,因为我计划在本系列中讨论更多的主题。 不过,这将是一篇长篇文章,因此请确保将一杯茶或咖啡与之搭配使用:)
We will set the scene with a brief introduction, then proceed to describe each principle and supplement the descriptions with illustrative examples.
我们将通过简要介绍来介绍现场情况,然后继续描述每个原理并通过说明性示例补充说明。
To abstract unnecessary details, examples in this article are illustrated with UML (Unified Modelling Language). Examples are simple and general on purpose, so as to be easily applicable to any programming language.
为了抽象出不必要的细节,本文中的示例使用UML(统一建模语言)进行了说明。 示例简单易懂,用途广泛,可轻松应用于任何编程语言。
Finally, we will close with a list of essential literature and study materials that can provide a more in-depth insight into the matter and that played a big part in the preparation of this article.
最后,我们将列出一些重要的文献和研究材料,这些材料和文献可以提供对该问题的更深入了解,并且在本文的准备工作中发挥了重要作用。
背景 (Background)
Before jumping headfirst into the topic, an excellent question to ask would be — why care about the SOLID principles in the first place? Which issues are we trying to address, and what do we expect to achieve?
在直接进入该主题之前,有一个很好的问题要问-为什么首先要关注SOLID原则? 我们正在尝试解决哪些问题,我们期望实现什么?
Finally, do these principles exclusively apply to Object-Oriented Programming, or is it something that can be extrapolated to other programming paradigms?
最后,这些原理是否仅适用于面向对象的编程,还是可以推论到其他编程范例?
不可重用的代码 (Non-reusable code)
Without further ado, here is the pitch: the goal of the SOLID principles is to address the critical issues of non-reusable code.
事不宜迟,这里有个重点:SOLID原则的目标是解决不可重用代码的关键问题。
The underlying ideas were combined into one concept by Robert C. Martin (the SOLID acronym itself was later proposed by Michael Feathers) with the aim of addressing the following characteristics of “bad” software design:
罗伯特·C·马丁(Robert C. Martin)将这些基本思想合并为一个概念(SOLID的首字母缩写词本身由迈克尔·费瑟斯(Michael Feathers)后来提出),旨在解决“不良”软件设计的以下特征:
Rigidity: describes a situation when changing a part of the code leads to breaking another part that is dependent on it.
刚性 :描述了更改一部分代码导致破坏依赖它的另一部分时的情况。
Fragility: describes a situation when changing a part of the code leads to breaking another part that is independent of it.
易碎性 :描述了更改部分代码导致破坏与它无关的另一部分时的情况。
Inseparability: describes a situation when the code in question can only be used in the exact context in which it was written.
不可分割性 :描述了一种情况,其中所讨论的代码只能在其编写的确切上下文中使用。
Viscosity: gives an indication of how easy or hard it is to stick to the original design decisions when introducing changes to an existing codebase.
粘度:表明对现有代码库进行更改时坚持原始设计决策的难易程度。
As Robert Martin states himself, the idea behind the SOLID principles is to “make software designs more understandable, flexible and maintainable”.
正如罗伯特·马丁(Robert Martin)所言,SOLID原理的思想是“使软件设计更易于理解,灵活和可维护”。
While at first glance, some of these points might not seem so crucial, their importance grows in proportion to the size and complexity of an application. By addressing these points, we can reduce the complexity of the code, increase long-term stability and make the software more robust and flexible when handling changes.
乍一看,其中一些观点似乎并不那么关键,但它们的重要性与应用程序的大小和复杂性成正比。 通过解决这些问题,我们可以减少代码的复杂性,提高长期稳定性,并使软件在处理更改时更加强大和灵活。
What’s great about the SOLID principles is that even though they introduce a way to design and structure object-oriented code, the underlying ideas can be applied to any other paradigm, be it functional or structured programming.
SOLID原则的优点在于,即使它们引入了一种设计和结构化面向对象代码的方法,其基本思想也可以应用于任何其他范式,无论是函数式编程还是结构化编程。
SRP:单一责任原则 (SRP: Single Responsibility Principle)
According to Robert Martin, out of all the SOLID principles, “the Single Responsibility Principle (SRP) might be the least well understood”. Having misinterpreted this principle myself and even misquoted it several times, I can personally relate to this sentiment.
根据罗伯特·马丁(Robert Martin)所说,在所有SOLID原则中,“单一责任原则(SRP)可能是最难理解的”。 我本人误解了该原则,甚至多次误解了该原则,我个人都可以与这种观点联系起来。
In particular, I always assumed that it was all about the “single responsibility” part — and therefore, each class should only perform one task or be responsible for one thing.
特别是,我一直以为这仅是“单一责任”部分,因此,每个班级只能执行一项任务或对一件事情负责。
And while this principle applies (especially when referring to methods/functions and provided we can all agree on the definition of the task or the thing) — this is not all that the SRP stands for.
尽管适用了该原则(尤其是在提及方法/功能时,并且只要我们都能就任务或事物的定义达成一致),但这并不是SRP所代表的全部。
The real idea behind SRP is that a module should have one, and only one, reason to change.
SRP背后的真正想法是,一个模块应该只有一个更改理由。
Initially, this can seem a bit confusing, so try to think of it in the following way: software is there to satisfy the needs of users and related stakeholders. For our specific example, we are interested in the stakeholders that can — in one way or another — influence/introduce changes to the software. Robert Martin calls those influencers “Actors”.
最初,这似乎有点令人困惑,所以请尝试通过以下方式考虑它:软件可以满足用户和相关利益相关者的需求。 对于我们的特定示例,我们对可能以一种或另一种方式影响/引入软件更改的利益相关者感兴趣。 罗伯特·马丁(Robert Martin)将这些影响者称为“ 演员 ”。
Our initial statement can, thus, be rephrased as: “A module should be responsible to one, and only one, actor”, which means that only one group of people can propose a reason for the module to change.
因此,我们的初始陈述可以改写为:“ 一个模块应该对一个角色负责,而只有一个角色对参与者负责 ”,这意味着只有一群人可以提出更改模块的理由。
In his book, “Clean Architecture”, Martin provides a great example to illustrate this point:
马丁在他的《清洁建筑》一书中提供了一个很好的例子来说明这一点:
Imagine that we have an “Employee” class, the output of which is being used by three different departments within the company: Tech, Finance and HR. The class has three methods:
想象一下,我们有一个“雇员”类,公司的三个不同部门(技术,财务和人力资源)正在使用该类的输出。 该类具有三种方法:
calculate_pay(): used by the Finance department to determine employee salary.
compute_pay():由财务部门用来确定员工工资。
report_hours(): used by the HR department to calculate working hours.
report_hours() :由人力资源部门用于计算工作时间。
save(): used by the database admins that work in the technical department to save outputs to the database.
save() :由技术部门的数据库管理员用来将输出保存到数据库。
Since three different “Actor” roles use this class, the probability that their business needs and requests might come into conflict with each other is higher.
由于三个不同的“演员”角色使用此类,因此他们的业务需求和请求可能相互冲突的可能性更高。
Imagine that the HR department asks us to implement changes to the report_hours() method. To satisfy their request, we change a related util function that is also used in time calculation algorithm of the calculate_pay() method. Such a change could lead to unexpected consequences for both departments and even “break” the class.
想象一下,人力资源部门要求我们对report_hours()方法进行更改。 为了满足他们的要求,我们更改了一个相关的util函数,该函数也用在calculate_pay()方法的时间计算算法中。 这种变化可能会给两个部门带来意想不到的后果,甚至会“破坏”课堂。
The bottom line is — we shouldn’t combine functionalities that change due to different reasons in the same class. To be more specific, each class or software component should be responsible for a particular feature and serve as a complete encapsulation for it.
最重要的是-我们不应在同一类中结合由于不同原因而改变的功能。 更具体地说,每个类或软件组件都应对特定功能负责,并充当其完整封装。
Solutions to this specific problem could include either splitting the described methods into multiple classes or using the Facade design pattern. Both of these options will be discussed in details in the upcoming articles.
解决此特定问题的方法可能包括将描述的方法分为多个类或使用Facade设计模式。 这两个选项将在以后的文章中详细讨论。
OCP:开放/封闭原则 (OCP: Open/Closed Principle)
First formulated by Bertrand Meyer in 1988, in its classic definition the OCP is known as:
OCP由Bertrand Meyer于1988年首次提出,其经典定义是:
A software artifact should be open for extension but closed for modification.
应打开软件工件进行扩展,但应关闭软件进行修改。
Simply put, our goal is to organize the code in such a way that it could be easily extended without modification. It is here that any sane person would probably ask — “Wait a minute, how can you extend a piece of code without modifying it?”.
简而言之,我们的目标是组织代码,使其无需修改即可轻松扩展。 任何理智的人都可能在这里问到“等一下,如何在不修改代码的情况下扩展一段代码?”。
To better visualize this idea, let’s take a look at the following example that violates the OCP:
为了更好地形象化此想法,让我们看一下下面的违反OCP的示例:
We have created three classes:
我们创建了三个类:
The Rectangle class that describes the corresponding shape and defines two attributes of a rectangle — length and width.
Rectangle类,它描述相应的形状并定义矩形的两个属性-长度和宽度。
The CalculateArea class that calculates the area of a rectangle based on its length and width dimensions.
CalculateArea类,根据其长度和宽度尺寸计算矩形的面积。
The DisplayResult class that instantiates the other two classes and displays the result of the area calculation.
实例化其他两个类并显示面积计算结果的DisplayResult类。
Even though this design accomplishes a specific task — namely, calculating the area of a rectangle and displaying the results — this code has scalability issues.
即使此设计完成了一项特定任务(即计算矩形的面积并显示结果),此代码也存在可伸缩性问题。
What if we would like to calculate an area of a different shape, e.g. a circle? The key parameters and the calculation would differ, and we would, thus, have to modify our initial code (e.g. the CalculateArea method) to accommodate the change. Such modifications would have to be done for each additional shape that we would like to introduce.
如果我们想计算不同形状的面积(例如圆形)怎么办? 关键参数和计算将有所不同,因此,我们必须修改我们的初始代码(例如CalculateArea方法)以适应更改。 对于我们要引入的每个其他形状,都必须进行此类修改。
Most likely you have already realised what the OCP stands for: coming up with a design that is both scalable and easily extendable.
您很可能已经意识到OCP的含义:提出可扩展且易于扩展的设计。
As per our example, a better approach would be to introduce a Shape class with an empty method for calculating the area of a shape (depending on your implementation language, this class could either be an interface or an abstract class).
根据我们的示例,更好的方法是引入Shape类,并使用空方法来计算形状的面积(取决于您的实现语言,该类可以是接口或抽象类)。
Adding a new shape would then be as simple as inheriting from the Shape class and defining the custom area calculation method on the subclass level.
然后,添加新形状就像从Shape类继承并在子类级别定义自定义区域计算方法一样简单。
Our CalculateArea class could then call the area calculation method of the respective child class, and we wouldn’t have to modify it when adding a new shape.
然后,我们的CalculateArea类可以调用相应子类的面积计算方法,并且在添加新形状时不必修改它。
On the fundamental level, you could think of OCP as using inheritance and subclasses for extending the code. The obvious benefit is that our code would scale better, and we wouldn’t have to worry about modifying the legacy codebase.
从根本上讲,您可以将OCP视为使用继承和子类来扩展代码。 明显的好处是我们的代码可以更好地扩展,并且我们不必担心修改遗留代码库。
LSP:李斯科夫替代原则 (LSP: Liskov Substitution Principle)
Barbara Liskov introduced LSP in the late 80s via a definition of subtypes:
Barbara Liskov在80年代后期通过对子类型的定义引入了LSP:
“What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behaviour of P is unchanged when o1 is substituted for o2 then S is a subtype of T”.
“这里想要的是类似以下替换属性的内容:如果对于类型S的每个对象o1,都有一个类型T的对象o2,使得对于根据T定义的所有程序P,当o1为时,P的行为不变。取代o2,则S是T”的子类型。
Every time I’m re-reading this quote, I am slightly confused by it. What it says is that a good design implies an ability to replace a parent class with its subclass. In other words, a child class must be able to perform the same thing that the parent class does.
每次我重新阅读此报价时,都会对此感到有些困惑。 它的意思是, 好的设计意味着可以用其子类替换父类 。 换句话说,子类必须能够执行与父类相同的操作。
For me, this is probably the hardest principle to grasp, and in my opinion, it truly deserves a separate article. Robert Martin provides several excellent examples that illustrate the key ideas behind LSP, and I would like to describe them both briefly.
对我来说,这可能是最难掌握的原则,而且我认为,这确实值得单独撰写。 罗伯特·马丁(Robert Martin)提供了几个出色的示例,这些示例说明了LSP背后的关键思想,我想对它们进行简要描述。
情况1:符合LSP (Case 1: Conforming to LSP)
Our first use case describes a design that fits to the LSP. In this design, we come up with a base License class that defines a method for calculating a license fee. The Billing application is invoking this method.
我们的第一个用例描述了适合LSP的设计。 在此设计中,我们提出了一个基本的许可证类,该类定义了一种计算许可证费用的方法。 计费应用程序正在调用此方法。
The parent License class has two subclasses: PersonalLicense and BusinessLicense. Both of those also calculate the license fee, but use different calculation methods.
父许可类有两个子类:PersonalLicense和BusinessLicense。 两者都计算许可费,但是使用不同的计算方法。
You could say that this use case conforms with the LSP, since the Billing application — next to calling the License class — could theoretically also use any of its subclasses. The calculation might be different, but the behaviour of calculating the fee itself is similar.
您可以说此用例符合LSP,因为Billing应用程序(在调用License类的旁边)理论上也可以使用其任何子类。 计算方法可能有所不同,但是计算费用本身的行为是相似的。
情况2:违反LSP (Case 2: Violating the LSP)
The next example is a classic use case for illustrating the violation of LSP, and I have seen it mentioned numerous times across multiple resources.
下一个示例是一个经典的用例,用于说明LSP的违反情况,我已经看到它在多种资源中多次提到。
Here, the Square class cannot be considered a proper subtype of the Rectangle class and, thus, cannot substitute it. Why? Even though both classes set their width and height — they behave differently.
在此,Square类不能视为Rectangle类的适当子类型,因此不能替代它。 为什么? 即使两个类都设置了宽度和高度,它们的行为也有所不同。
While the Rectangle class can set its height and width independently, the sides of the square — when the method is invoked — will change at the same time. Not taking this into account could produce unexpected results for the User that is interacting with the class.
虽然Rectangle类可以独立设置其高度和宽度,但正方形的边(在调用该方法时)将同时更改。 不考虑这一点可能会对与类进行交互的用户产生意外的结果。
And here is — in my belief — the key to understanding the LSP: it is more about the behavior of a class, rather than its structure or organization.
在我看来,这是理解LSP的关键:它更多是关于类的行为,而不是其结构或组织。
As you might expect, checking for specific behaviours and — most importantly — enforcing them is not straightforward. Enforcing the LSP requires additional checks within the code or individual test cases specifically designed for it.
如您所料,检查特定行为并(最重要的是)执行这些行为并非易事。 实施LSP需要在代码中进行额外的检查,或者为它专门设计单个测试用例。
ISP:接口隔离原理 (ISP: Interface Segregation Principle)
The concept of an interface is an important one to grasp to understand the Interface Segregation and Dependency Inversion principles. Let’s take a quick detour to take a look at it.
接口的概念是理解接口隔离和依赖反转原理的重要知识。 让我们快速绕道看看。
接口 (Interface)
The term can be especially confusing, as some languages, like C++ and Python (not counting abstract classes), don’t have built-in concepts of an interface and some, like Java, have specific implementations of it.
这个术语可能会特别令人困惑,因为某些语言(例如C ++和Python)(不包括抽象类)没有内置的接口概念,而某些语言(例如Java)具有接口的特定实现。
An interface is a concept that deals with data hiding, abstraction and encapsulation. It specifies the way objects can communicate between themselves, e.g. methods that each object must contain.
接口是处理数据隐藏,抽象和封装的概念。 它指定对象之间的通信方式,例如每个对象必须包含的方法。
An interface itself doesn’t provide the implementation details for any of the methods. Instead, they are abstracted away and only defined in individual classes that implement this specific interface.
接口本身不提供任何方法的实现细节。 取而代之的是,将它们抽象化,仅在实现此特定接口的单个类中定义。
To illustrate this idea, think of a standard wristwatch. When you look at the watch, you know how to identify current time via its interface, without actually having to understand how the watch works.
为了说明这个想法,请考虑使用标准手表。 观看手表时,您知道如何通过其界面识别当前时间,而不必真正了解手表的工作原理。
All of the engineering magic is abstracted away from you, and you could use a wristwatch of any model, without ever having to look into the underlying implementation of the mechanism.
所有的工程魔术都从您身上抽象了出来,您可以使用任何型号的手表,而无需研究该机制的基本实现。
If you would like to read up more on the concept — I recommend picking up “The Object-Oriented Thought Process” book by Matt Weisfeld for a more in-depth answer and this StackOverflow post for a short overview: https://stackoverflow.com/questions/2866987/what-is-the-definition-of-interface-in-object-oriented-programming
如果您想阅读更多有关该概念的内容,我建议您阅读Matt Matt.Wesfeld撰写的“面向对象的思想过程”一书,以获得更深入的答案,而这篇StackOverflow帖子则简要介绍一下: https:// stackoverflow。 com / questions / 2866987 /什么是面向对象编程中接口的定义
返回接口隔离原理。 (Back to the Interface Segregation Principle.)
What it tells us is that it is better to have smaller interfaces for your classes, rather than fewer larger ones. Consider the following situation:
它告诉我们的是,最好为您的类使用较小的接口,而不是较少的较大接口。 请考虑以下情况:
As game developers, we define an Entity class — which is an interface — with three methods:
作为游戏开发人员,我们使用以下三种方法定义Entity类(即接口):
- move() 移动()
- jump() 跳()
- attack() 攻击()
Any class that implements this interface would have to define these methods as well. In the first case — that of an in-game character — this implementation makes sense contextually: we want our in-game character to move, jump and attack. Above all, it is realistic that the character could perform all three of these actions.
任何实现此接口的类都必须定义这些方法。 在第一种情况下(游戏角色),这种实现在上下文上有意义:我们希望我们的游戏角色能够移动,跳跃和攻击。 最重要的是,角色可以执行所有这三个动作。
This, however, is not the case in the second case — that of the Turret class. A Turret can neither move nor jump, and we would like it to have just one method — attack.
但是,第二种情况并非如此-炮塔级。 炮塔既不能移动也不能跳跃,我们希望它只有一种方法-攻击。
According to the ISP, we need to make sure that every class that implements that specific interface actually uses all of its methods.
根据ISP的说法,我们需要确保实现该特定接口的每个类实际上都使用其所有方法。
Aside from obvious logical benefits of separation, in statically typed languages, like Java, ISP would have an added benefit of reducing the amount of code compiles and deployments that would have to be done.
除了分离带来的明显逻辑好处外,在诸如Java之类的静态类型语言中,ISP还具有减少必须完成的代码编译和部署数量的附加好处。
If our Turret class would depend on interface methods that it doesn’t need — any change to those methods would lead to us recompiling and redeploying the code.
如果我们的Turret类依赖于不需要的接口方法,那么对这些方法的任何更改都将导致我们重新编译和重新部署代码。
Distinction between statically and dynamically typed languages might lead you to think that ISP is purely language-dependent, especially since we started the discussion of the concept of an interface by pointing out that some languages don’t have a built-in implementation of it.
静态和动态类型语言之间的区别可能使您认为ISP纯粹是依赖于语言的,特别是因为我们通过指出某些语言没有内置的实现来开始讨论接口的概念时。
This, however, is not entirely accurate. Consider the following example that clearly demonstrates how this principle could be related to architecture patterns & design thinking in general:
但是,这并不完全准确。 考虑下面的示例,该示例清楚地说明了该原理如何与总体上的架构模式和设计思想相关联:
As system architects, we are responsible for System A and have decided to integrate Framework B into our system architecture. As it turns out, Framework B depends on the Database C.
作为系统架构师,我们负责系统A,并已决定将框架B集成到我们的系统架构中。 事实证明,框架B取决于数据库C。
For the sake of argument, let’s imagine that the Database C has some features that are not used by the Framework B and, in turn, are not of any particular interest to the System A.
出于争论的目的,让我们想象一下数据库C具有某些功能,这些功能是框架B所不使用的,从而对系统A没有特别的意义。
Any changes to those features within the Database C might lead to changes in the Framework B and, subsequently, unforeseen and unwanted changes in the System A — as system architects, we would like to avoid this unnecessary dependency.
对数据库C中那些功能的任何更改都可能导致框架B的更改,进而导致系统A的意外更改和不希望的更改-作为系统架构师,我们希望避免这种不必要的依赖性。
This quote by Robert Martin is a great summary of the previous example: “The lesson here is that depending on something that carries baggage that you don’t need can cause you troubles that you didn’t expect”.
罗伯特·马丁(Robert Martin)的这段话很好地总结了前面的例子:“这里的教训是,依靠一些您不需要的行李可能会给您带来意想不到的麻烦”。
DIP:依赖倒置原则 (DIP: Dependency Inversion Principle)
The final principle — Dependency Inversion (not to be confused with Dependency Injection) — deals with dependencies in code, and ways to decouple different software modules from each other by introducing abstractions.
最终原理-依赖关系反转(不要与依赖关系注入相混淆)-处理代码中的依赖关系,以及通过引入抽象来使不同软件模块彼此分离的方法。
The concept of an interface (described above) is also important here, so I suggest making sure you have sufficient understanding of it before proceeding.
接口的概念(如上所述)在这里也很重要,因此建议您在继续之前确保对它有足够的了解。
Aside from sounding like a great title for a Science-Fiction movie, this principle is also one of the easier ones to grasp. As always, instead of diving right into details, let’s occupy ourselves with a short example:
除了听起来像是科幻电影的好标题外,这一原理也是较容易掌握的原理之一。 与往常一样,让我们不着重于一个简单的例子:
Imagine being a CEO of DHL (sounds great already, I know). As the CEO, how would your typical responsibilities look like? I bet you would mostly be concerned with strategic topics, like business expansion, new market entry, logistics & digitalization, etc.
想象一下当DHL的首席执行官(我知道这听起来已经很棒)。 作为首席执行官,您的典型职责是什么样的? 我敢打赌,您将主要关注战略主题,例如业务扩展,新市场进入,物流和数字化等。
Now think about all the things that you wouldn’t be involved in, like actual mail deliveries, sorting out post, applying stamps to envelopes and thousands of other low level, operational utility tasks.
现在,考虑一下您不需要涉及的所有事情,例如实际的邮件投递,整理邮件,在信封上盖章以及成千上万个其他低级别的操作实用程序任务。
As the CEO of the company, there is no way you can manage all of those low-level activities directly and, most importantly, nobody actually expects you too. Your job is to focus on the high-level management part, and you don’t need to worry about or depend on the operational details.
作为公司的首席执行官,您无法直接管理所有这些低级别的活动,最重要的是,没有人真正期望您。 您的工作是专注于高级管理部分,而您不必担心或依赖于操作细节。
This real-life example is a good way to illustrate the key idea behind the DIP:
这个真实的例子是说明DIP背后关键思想的好方法:
If you want a system to be flexible, make sure that your high-level system modules depend on abstractions, rather than low-level implementations.
如果要使系统灵活,请确保高级系统模块依赖于抽象而不是低级实现。
Based on our example, you can also easily deduce what the difference between high-level and low-level modules is all about:
根据我们的示例,您还可以轻松推断出高级模块和低级模块之间的差异是什么:
- A high-level module is using low-level modules for performing a specific task. 高级模块正在使用低级模块来执行特定任务。
- A low-level module is just a tool — a utility functionality — that is necessary for performing the task. 低级模块只是执行任务所需的工具-实用程序功能。
What does this mean for us on practice? If you remember the wristwatch example from the ISP section, you probably already realized that we should design system modules based on interfaces — hiding specific implementation details away.
这对我们练习意味着什么? 如果您还记得ISP一节中的手表示例,您可能已经意识到我们应该基于接口设计系统模块-隐藏具体的实现细节。
For instance, if I am designing a new software module that has to work together with other — already developed — modules, what I need to know about the system is what other modules there are and what they can do.
例如,如果我正在设计一个新的软件模块,该模块必须与其他已开发的模块一起工作,那么我需要了解的系统就是其他模块以及它们可以做什么。
Specific details of how they do it — at this moment — are not interesting to me, and I should design my module in such a way as to be independent from their low-level implementations.
目前,它们如何执行操作的具体细节对我而言并不有趣,因此我应该以与它们的底层实现无关的方式设计模块。
The bottom line is, the design — in particular, high-level and low-level modules — should be dependent on abstractions and interfaces and not vice versa.
最重要的是,设计(尤其是高级和低级模块)应依赖于抽象和接口,反之亦然。
So, instead of having an architecture looking like this:
因此,与其采用这样的架构,不如:
We should aim to achieve this target picture:
我们应该致力于实现以下目标:
Specific implementation of this principle would look differently, depending on your language of choice. While you could directly use an interface in Java, in a high-level language, like Python, Dependency Inversion could be implemented via wrapper functions that, in turn, invoke specific class methods or other low-level implementations.
根据您选择的语言,此原则的具体实现会有所不同。 虽然您可以直接使用Java中的接口,但可以使用高级语言(如Python),但是可以通过包装函数来实现依赖关系反转,而包装函数又会调用特定的类方法或其他低级实现。
结论 (Conclusion)
The goal of this article was to present the reader with a short overview of the SOLID principles: their background, theoretical foundation and practical application.
本文的目的是向读者简要介绍SOLID原理:它们的背景,理论基础和实际应用。
Knowing that there is a time-space constraint for this article, I tried to find the right balance between providing too many details, examples, use cases and specific language implementations vs having only theoretical foundations of the principles themselves.
知道本文存在时空限制后,我试图在提供太多细节,示例,用例和特定语言实现与仅提供原理本身的理论基础之间找到适当的平衡。
One of the key points that I tried to convey is that, even though the SOLID principles have been formulated so as to apply to Object-Oriented design specifically, their application in modern software development is much wider and can be as successfully applied both to writing functions and building a system architecture design.
我试图传达的关键点之一是,即使制定了SOLID原则以便专门应用于面向对象的设计,它们在现代软件开发中的应用也要广泛得多,并且可以成功地应用于写作功能和构建系统架构设计。
Specific implementation techniques of these principles, including detailed code examples, will be presented in the upcoming articles dedicated to Object-Oriented design patterns. For convenience, I will be linking those articles here as soon as they are published.
这些原则的具体实现技术,包括详细的代码示例,将在即将发布的有关面向对象设计模式的文章中介绍。 为了方便起见,我将在发布这些文章后立即将其链接到此处。
有用的文献和学习资料 (Useful Literature & Study Materials)
- C., Martin Robert. Clean Architecture (Robert C. Martin Series) C.,马丁·罗伯特 清洁建筑(Robert C. Martin系列)
- Weisfeld, Matt. The Object-Oriented Thought Process (Fifth Edition) 魏斯菲尔德,马特。 面向对象的思维过程(第五版)
https://www.baeldung.com/solid-principles
https://www.baeldung.com/solid-principles
https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898
https://medium.com/backticks-tildes/the-solid-principles-in-pictures-b34ce2f1e898
https://stackoverflow.com/questions/2866987/what-is-the-definition-of-interface-in-object-oriented-programming
https://stackoverflow.com/questions/2866987/what-is-the-definition-of-interface-in-object-oriented-programming
翻译自: https://medium.com/swlh/the-solid-principles-of-object-oriented-design-fa4e2bd8a1be