Home > Article > Backend Development > SOLID: Part 4 - Dependency Inversion Principle
Single Responsibility (SRP), Open/Closed (OCP), Liskov Substitution, Interface Isolation and Dependency Inversion. Five agile principles that should guide you every time you write code.
It would be unfair to tell you that any one SOLID principle is more important than another. However, perhaps none has such a direct and profound impact on your code than the Dependency Inversion Principle, or DIP for short. If you find other principles difficult to grasp or apply, start with this one and then apply the remaining principles to code that already follows the DIP.
A. High-level modules should not depend on low-level modules. Both should rely on abstractions.
B. Abstractions should not depend on details. The details should depend on the abstraction.
This principle was defined by Robert C. Martin in his book "Agile Software Development, Principles, Patterns, and Practices" and was later republished in the C# version in the book "Agile Principles, Patterns, and Practices in C#" , this is the last principle of the five SOLID agile principles.
Before we start coding, I want to tell you a story. At Syneto, we're not always that careful with our code. A few years ago we knew very little and although we tried our best, not all projects were that good. We've been through hell and back, and we've learned a lot through trial and error.
Uncle Bob’s (Robert C. Martin) SOLID principles and architectural simplicity principles were game-changers for us and changed the way we code in ways that are hard to describe. In short, I will try to illustrate some of the key architectural decisions implemented by DIP that had a significant impact on our project.
Most web projects contain three main technologies: HTML, PHP, and SQL. It doesn't matter which specific version of these applications we're talking about or the type of SQL implementation you use. The problem is, the information from the HTML form has to end up in the database somehow. The glue between the two can be provided by PHP.
The most important thing is that these three technologies represent three different architectural layers very well: user interface, business logic and persistence. We'll discuss what these layers mean later. Now, let's focus on some strange but often encountered solutions to making these technologies work together.
Many times I've seen projects use SQL code in PHP tags within HTML files, or PHP code that echoes the HTML page and directly interprets the $_GET
or $_POST
global variable. But why is this bad?
The image above represents the original version we described in the previous paragraph. The arrows represent various dependencies and we can conclude that basically everything depends on everything. If we need to make changes to a database table, we may end up editing the HTML file. Or, if we change a field in the HTML, we might end up changing the column names in the SQL statement. Or if we look at the second pattern, if the HTML changes, we most likely need to modify the PHP, or in a very bad case, when we generate all the HTML content from inside the PHP file, we definitely need to change the PHP file to Modify HTML content. So, there is no doubt that the dependencies between classes and modules are tortuous. But it didn't end there. Can store procedures; PHP code in SQL tables.
In the above schema, the query against the SQL database returns PHP code generated using the data in the table. These PHP functions or classes are executing other SQL queries that return different PHP code, and the loop continues until eventually all the information is fetched and returned... possibly back to the UI.
I know this may sound outrageous to many of you, but if you haven't been involved in a project invented and implemented in this way, you will definitely do so in your future career. Most existing projects, regardless of programming language, are written according to old principles, by programmers who don't care or don't know how to do it better. If you're reading these tutorials, chances are you've already reached a higher level. You're ready, or ready, to respect your profession, embrace your craft, and do it better.
The alternative is to repeat the mistakes of those who came before you and suffer the consequences. At Syneto, when one of our projects reached a state of near unmaintainability due to its old and interdependent architecture and we basically had to abandon it for good, we decided not to go down that path anymore. Since then we have strived to have a clean architecture, properly respecting the SOLID principles and most importantly the dependency inversion principle.
The magic of this architecture is how the dependencies are pointed:
Applying the Dependency Inversion Principle (DIP) at the architectural level is very easy if you respect classic agile design patterns. It's also easy and even fun to practice and give examples in business logic. We will imagine an e-book reader application.
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new PDFReader($b); $this->assertRegExp('/pdf book/', $r->read()); } } class PDFReader { private $book; function __construct(PDFBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook { function read() { return "reading a pdf book."; } }
We started developing the e-reader into a PDF reader. So far, so good. We have a PDFReader
class that uses PDFBook
. The read()
function on the reader delegates to the read()
method of the book. We just verified this by doing a regular expression check after the critical part of the string returned by PDFBook
's reader()
method.
Please remember this is just an example. We will not implement reading logic for PDF files or other file formats. That's why our test will only check some basic strings. If we were to write a real application, the only difference would be how we test different file formats. The dependency structure is very similar to our example.
拥有一个使用 PDF 书籍的 PDF 阅读器对于有限的应用程序来说可能是一个合理的解决方案。如果我们的范围是编写一个 PDF 阅读器,仅此而已,那么它实际上是一个可以接受的解决方案。但我们想编写一个通用的电子书阅读器,支持多种格式,其中我们第一个实现的版本是 PDF。让我们重命名我们的读者类。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new EBookReader($b); $this->assertRegExp('/pdf book/', $r->read()); } } class EBookReader { private $book; function __construct(PDFBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook { function read() { return "reading a pdf book."; } }
重命名没有功能上的反作用。测试仍在通过。
测试于下午 1:04 开始...
PHPUnit 3.7.28 由 Sebastian Bergmann 编写。
时间:13 毫秒,内存:2.50Mb
好的(1 个测试,1 个断言)
进程已完成,退出代码为 0
但它具有严重的设计效果。
我们的读者变得更加抽象。更一般。我们有一个通用的 EBookReader
,它使用非常具体的书籍类型 PDFBook
。抽象取决于细节。我们的书是 PDF 类型这一事实应该只是一个细节,任何人都不应该依赖它。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new EBookReader($b); $this->assertRegExp('/pdf book/', $r->read()); } } interface EBook { function read(); } class EBookReader { private $book; function __construct(EBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook implements EBook{ function read() { return "reading a pdf book."; } }
反转依赖关系最常见、最常用的解决方案是在我们的设计中引入一个更抽象的模块。 “OOP 中最抽象的元素是接口。因此,任何其他类都可以依赖于接口并且仍然遵循 DIP”。
我们为读者创建了一个界面。该接口名为 EBook
,代表 EBookReader
的需求。这是尊重接口隔离原则 (ISP) 的直接结果,该原则提倡接口应反映客户端需求的理念。接口属于客户端,因此它们的命名反映了客户端需要的类型和对象,并且它们将包含客户端想要使用的方法。 EBookReader
使用 EBooks
并拥有 read()
方法是很自然的。
我们现在有两个依赖项,而不是单个依赖项。
EBookReader
指向 EBook
接口,并且它是类型用法。 EBookReader
使用 EBooks
。PDFBook
指向相同的 EBook
接口,但它是类型实现。 PDFBook
只是 EBook
的一种特殊形式,因此实现了该接口来满足客户的需求。不出所料,该解决方案还允许我们将不同类型的电子书插入阅读器。所有这些书籍的唯一条件是满足 EBook
接口并实现它。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new EBookReader($b); $this->assertRegExp('/pdf book/', $r->read()); } function testItCanReadAMobiBook() { $b = new MobiBook(); $r = new EBookReader($b); $this->assertRegExp('/mobi book/', $r->read()); } } interface EBook { function read(); } class EBookReader { private $book; function __construct(EBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook implements EBook { function read() { return "reading a pdf book."; } } class MobiBook implements EBook { function read() { return "reading a mobi book."; } }
这又将我们引向开闭原则,并且圆圈是闭合的。
依赖倒置原则是引导或帮助我们尊重所有其他原则的原则。尊重 DIP 将:
就是这样。我们完了。所有有关 SOLID 原理的教程均已完成。对我个人来说,发现这些原则并在实施项目时牢记它们是一个巨大的变化。我完全改变了我对设计和架构的思考方式,我可以说从那时起我从事的所有项目都变得更加容易管理和理解。
我认为 SOLID 原则是面向对象设计最基本的概念之一。这些概念必须指导我们使我们的代码变得更好,并使我们作为程序员的生活变得更加轻松。设计良好的代码更容易让程序员理解。计算机很聪明,无论代码多么复杂,它们都可以理解。另一方面,人类能够在活跃、专注的头脑中保存的事物数量有限。更具体地说,此类事物的数量是神奇数字七、正负二。
We should strive to structure our code around these numbers, and there are several techniques that can help us do this. Functions should be a maximum of four lines in length (five lines including the definition line) so that they can fit in our heads at the same time. The depth of indentation does not exceed five layers. Classes with no more than nine methods. Typically five to nine classes of design patterns are used. Our high-level design in the pattern above uses four to five concepts. There are five SOLID principles, each requiring between five and nine sub-concepts/modules/classes to be exemplified. The ideal size for a programming team is five to nine people. The ideal number of teams in a company is five to nine.
As you can see, the magic number seven, plus or minus two, is all around us, so why should your code be any different?
The above is the detailed content of SOLID: Part 4 - Dependency Inversion Principle. For more information, please follow other related articles on the PHP Chinese website!