65.9K
CodeProject 正在变化。 阅读更多。
Home

依赖倒置原则 - 你一直以来对DIP的理解并不完全准确。让我们把它简单化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (38投票s)

2015年2月19日

CPOL

17分钟阅读

viewsIcon

46275

downloadIcon

281

让我们准确理解什么是依赖倒置原则,什么不是。人们如何将其与依赖注入混淆,而它并非如此。

引言

朋友们!今天我来写一篇关于依赖倒置原则(DIP)的文章,它是SOLID设计原则之一。SOLID是一个首字母缩略词,其中D代表依赖倒置原则。我不敢说这个原则比其他四个设计原则更重要或不那么重要,但它被更明确地广泛使用,因为市场上存在大量著名为IoC容器的产品,这些产品可以最好地被遵循依赖倒置原则的软件模块利用。因此,在你了解IoC之前,你应该对DIP有很好的理解。这就是为什么在面试中你也会得到更多关于DI原则的问题:)

议程

我决定将我的整个议程分为两部分系列,因为我想在依赖倒置原则和依赖注入设计模式(这是实现IoC的一种方式)之间划清界限。我希望这是帮助读者欣赏我的座右铭的更好方式。希望我的长篇文章不会让人感到不堪重负。这是本篇文章的议程

  1. 什么是依赖倒置原则及其基本实现
  2. 理解依赖倒置原则可以在不依赖于模块中的依赖注入模式或项目中存在IoC容器的情况下完全实现。

我文章的第二部分将涵盖以下内容。  可以在这里找到

  1. 什么是依赖注入?它的基本实现?理解依赖倒置原则不是依赖注入,尽管它们共享相同的首字母缩写。它就是不是。反之亦然。
  2. 理解即使在你的模块和组件不遵循或不展示依赖倒置原则的情况下,也可以实现依赖注入。

最后,我想证明“依赖倒置”原则和“依赖注入”模式不是彼此的镜像。它们协同工作。它们相辅相成。没有对方会感到不适。我将向你展示如何做到这一点。  当你同时应用DIP和依赖注入时,对任何新需求的 कोड更改量将是最小的。

必备组件

阅读本文并使用附带的源代码,需要具备C#语言和Microsoft Visual Studio IDE的基础知识。

关于附带代码的说明

要在您的计算机上运行附带的代码,您需要以下软件作为先决条件

  1. Visual Studio 2010 或更高版本。
  2. Microsoft SQL Server。即使是Express版也足够了。

附带的代码包含一个名为“DatabaseScripts.sql”的文件,您需要在本地sql server实例上运行该文件才能无错误地运行代码。如果您机器上没有SQL Server,您仍然可以理解代码。要无SQL Server运行代码,您将需要注释掉一些用于将记录保存到“SaveDataToStorage”函数中的数据库的代码行。“SaveDataToStorage”函数存在于多个文件中。

约定

我将在下一条声明后不再在我的文章中使用DI这个缩写。问题在于“依赖倒置”原则和“依赖注入”模式共享相同的首字母缩写 - DI。这可能也是人们如此混淆这两个概念的一个来源。这两个概念如此纠缠不清,以至于开发人员开始将它们视为彼此的另一面。我们未能区分清楚并单独区分它们。使用缩写可以为我节省一些打字工夫,但然后它会进一步加剧您的困惑,并模糊这两个概念之间的界限,而我的意图恰恰相反。

开始前的特别说明:如果您对设计原则、模式和实践的世界完全陌生,那么您已经准备好了。如果您有一些公平或基本的了解,您可能想暂时搁置控制反转(IoC)的概念,以便理解本文的要点。本文与IoC无关。

依赖倒置原则:让我们从零开始

书上怎么说?我将两句话分解成四句话,以便在我的文章中带您经历一次演变: 

  1.  
    1. 高层模块不应依赖于低层模块。
    2. 高层模块和低层模块都应依赖于抽象。
  2.  
    1. 抽象不应依赖于细节。
    2. 细节应依赖于抽象。

以上两点共有四句话。让我们逐一彻底理解它们。

让我们先理解上面四句话中使用的术语

模块:它是组件分组的高级抽象,这些组件一起尝试提供一项功能,例如,应用程序中的日志记录模块,它负责将事件和日志语句记录到文件或事件查看器中。模块可以是类、组件、程序集,甚至是一组程序集。在我们的例子中,我们将类作为我们的模块。

抽象:任何无法实例化或非具体的东西。它只是两个实体之间的契约的表示,例如,抽象类或接口。抽象类和接口都是抽象的类型,其中接口是纯粹的抽象。我将在我的文章中互换使用这两个术语。

陈述 # 1.a:“高层模块不应依赖于低层模块”。

让我尝试为您重新措辞一下:“高层模块不应依赖于(低层模块的具体实现)”。让我们试着理解我在斜体括号中引用的词的确切意思。

让我们先看看高层模块如何依赖于低层模块,以及由于技术原因人们为何反对它所带来的具体实现问题。请看下面的代码

我有一个Employee类,它公开了一个“SaveEmployeeDetails”API来保存员工详细信息。请看下面的代码

代码片段 # 1(附带代码中的EmployeeWithSqlDb.cs文件)

using System.Data.SqlClient;

namespace DependencyInversionPrincipleSqlDbDependency
{
    public class Employee
    {

        private string empName;
        private int salary;

        public string EmployeeName
        {
            get { return empName; }
            set { empName = value; }
        }

        public int Salary
        {
            get { return salary; }
            set { salary = value; }
        }

        private bool IsValidEmployee(string inputName, int inputSalary)
        {
            return !string.IsNullOrEmpty(intputName) && inputSalary > 0;
        }

        public void SaveEmployeeDetails()
        {  
             //Instantiate low-level Dependency
             DatabaseStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
             if (IsValidEmployee(empName,salary))
             {
                 //create query text
                 string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);

                 //create sql connection
                 var sqlCon = persistanceStorageAgent.GetSqlConnection();

                 //save employee details into the SQL Server database
                 persistanceStorageAgent.ExecuteNonQuery(sqlCon, queryText);
             }
         }
    }

    public class DatabaseStorageAgent
    {
        public SqlConnection GetSqlConnection()
        {
            return new SqlConnection("Data Source=rasik-PC;Initial Catalog=EmployeeDB;Integrated Security=SSPI;");
        }

        public void ExecuteNonQuery(SqlConnection sqlCon, string queryText)
        {
            var sqlCmd = new SqlCommand(queryText, sqlCon);
            sqlCmd.ExecuteNonQuery();
        }
    }
}

代码解释:这里“Employee”是高层模块。“DatabaseStorageAgent”是低层模块。高层模块“Employee”依赖于低层模块“DatabaseStorageAgent”将员工详细信息保存到持久性介质SQL Server数据库中。所以这里的依赖是从高层模块到低层模块的向下依赖。这里一切看起来都很好。那么**陈述 #** 1.a为何反对它?

真正的问题:作为软件开发的一部分,我们总是希望在由于新的需求而需要更改软件功能时,最大限度地减少代码更改。让我们看看我们是否真的能够实现这一目标。我有一个新需求。客户现在希望将员工数据保存在MS-Access数据库中,而不是SQL Server数据库中。这将导致Employee类及其依赖项“DatabaseStorageAgent”发生如下更改。

代码片段 # 2 (附带代码中的EmployeeWithMsAccessDb.cs)

using System.Data.OleDb;

namespace DependencyInversionPrincipleMsAccessDbDependency
{
    public class Employee
    {

        //Trivial code has been removed for brevity and clarity. Please download attached code for 
        //complete reference

        public void SaveEmployeeDetails()
        {
            //Instantiate low-level Dependency 
            DatabaseStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
            if (IsValidEmployee(empName,salary))
            {
                //create query text
                string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);

                //create oledb connection
                var oleDbCon = persistanceStorageAgent.GetOleDbConnection();

                //save employee details into ms access database
                persistanceStorageAgent.SaveInfoToAccessFile(oleDbCon, queryText);
            }
        }
    }

    public class DatabaseStorageAgent
    {
        public OleDbConnection GetOleDbConnection()
        {
            return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\AccessDatabases\\EmployeeDb\\employee.mdb;Persist Security Info=True");
        }

        public void SaveInfoToAccessFile(OleDbConnection sqlCon, string queryText)
        {
            var oleDbCmd = new OleDbCommand(queryText, sqlCon);
            oleDbCmd.ExecuteNonQuery();
        }
    }
}

代码中的所有更改:“DatabaseStorageAgent”类已完全更改,因为它现在必须为MS Access数据库服务。所以这还可以。但请等等,其他一些东西也发生了变化。我们的客户端代码,或者说“Employee”类中的消费者代码,它依赖于“DatabaseStorageAgent”类,也必须进行修改。观察“Employee”类中SaveEmployeeDetails函数中的斜体代码。现在这才是真正的问题,仅仅因为我的依赖类将员工详细信息保存在MS Access数据库而不是SQL Server数据库中,我就不得不大量修改我的“Employee”类代码。

             这肯定不是我们的初衷。不是吗?**真正的问题**是什么?“Employee”类所展示的关键属性,也是这个问题的根本原因,就是——“高层模块依赖于(低层模块的具体实现)。我在最后一个陈述中括号中提到的文字应该让你有所思考。问题在于具体实现。我们与“DatabaseStorageAgent”的具体实现紧密绑定。

             这个问题也称为僵化问题。如果明天客户期望我们将员工详细信息保存到第三种类型的数据库,比如Oracle,我们将再次经历整个过程,导致多处(更改地点以及使用地点)的大量更改。您的软件是僵化的或难以更改。每一次小的更改都会导致级联的更改,因为您确实在高级模块的使用方式中窥视了依赖类的内部实现。当“DatabaseStorageAgent”类发生实际更改时,“Employee”类也需要进行整个测试周期,因为它受到了影响。您现在有线索如何解决这个问题了吗?让我们来看陈述 # 1.b,它有助于我们解决这个问题。

陈述 # 1.b:“高层模块和低层模块都应依赖于抽象”。

是的。就是这样。如果我们能够以某种方式让高层模块和低层模块都依赖于抽象,我认为问题就能解决。让我们通过代码来实际看看。对于抽象,我们将使用一个接口。让我们重写代码片段 # 1,从我们开始的地方。我提供了一个新的实现,如下所示。

代码片段 # 3(附带代码中的EmployeeWithAbstractDependencySqlServer.cs文件)

using System.Data;
using System.Data.SqlClient;

namespace DependencyInversionPrincipleAbstractDbDependencySqlServer
{
    public class Employee
    {

        //Trivial code has been removed for brevity and clarity. Please download attached code for 
        //complete reference

        public void SaveEmployeeDetails()
        {
            if (IsValidEmployee(empName,salary))
            {
                //Instantiate low-level Dependency
                IStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
                //create query text
                string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);

                //create persistance storage connection
                var connection = persistanceStorageAgent.GetPersistantStorageConnection();

                //save employee details into the persistance storage
                persistanceStorageAgent.SaveDataToStorage(connection, queryText);
            }
        }
        
    }

    public interface IStorageAgent
    {
        IDbConnection GetPersistantStorageConnection();
        void SaveDataToStorage(IDbConnection Connection, string queryText);
        
    }

    public class DatabaseStorageAgent : IStorageAgent
    {
        public IDbConnection GetPersistantStorageConnection()
        {
            return new SqlConnection("Data Source=rasik-PC;Initial Catalog=EmployeeDB;Integrated Security=SSPI;");
        }

        public void SaveDataToStorage(IDbConnection dbConnection, string queryText)
        {
            var sqlCmd = new SqlCommand(queryText, (SqlConnection)dbConnection);
            sqlCmd.ExecuteNonQuery();
        }
    }
}

代码中的所有更改:您可以看到我引入了一个名为“IStorageAgent”的接口。现在,高层模块和低层模块都依赖于这个公共接口。高层模块“Employee”现在在其“SaveEmployeeDetails”函数实现中依赖于IStorageAgent接口。低层模块“DatabaseStorageAgent”现在实现它,因此也依赖于IStorageAgent接口。但真正的问题是,我们如何从新的更改中受益,即高层模块现在不再依赖于低层模块,而是两者都依赖于一个抽象(这里IStorageAgent接口代表该抽象/契约)。为了获得真正的益处,让我们再次重新实现当客户想要从SQL Server数据库切换到MS Access数据库时的客户更改,并注意我们现在必须做出多少更改。这是代码示例。

代码片段 # 4 (附带代码中的EmployeeWithAbstractDependencyMsAccess.cs文件)

using System.Data;
using System.Data.OleDb;

namespace DependencyInversionPrincipleAbstractDbDependencyMsAccess
{
    public class Employee
    {

        //Trivial code has been removed for brevity and clarity. Please download attached code for 
        //complete reference

        public void SaveEmployeeDetails()
        {
            //Instantiate low-level Dependency
            IStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
            if (IsValidEmployee(empName,salary))
            {
                //create query text
                string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);

                //create persistance storage connection
                var oleDbCon = persistanceStorageAgent.GetPersistantStorageConnection();

                //save employee details into the persistance storage
                persistanceStorageAgent.SaveDataToStorage(oleDbCon, queryText);
            }
        }

    }
    public interface IStorageAgent
    {
        IDbConnection GetPersistantStorageConnection();
        void SaveDataToStorage(IDbConnection Connection, string queryText);

    }
    public class DatabaseStorageAgent : IStorageAgent
    {
        public IDbConnection GetPersistantStorageConnection()
        {
            return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\AccessDatabases\\EmployeeDb\\employee.mdb;Persist Security Info=True");
        }

        public void SaveDataToStorage(IDbConnection dbConnection, string queryText)
        {
            var oleDbCmd = new OleDbCommand(queryText, (OleDbConnection)dbConnection);
            oleDbCmd.ExecuteNonQuery();
        }
    }
}

 

真正的解决方案:因此,我们更改了代码片段 # 3 中提到的代码,以使我们的低层依赖项“DatabaseStorageAgent”支持MS Access数据库。现在,比较为了整合这些更改而对高层模块所做的更改数量。好消息是,所有更改都在您的低层依赖项“DatabaseStorageAgent”(图中用斜体标出)中完成,而您的“Employee”高层模块的总更改量为。这听起来像是一个欢呼的时刻吗?是的。您已从系统中消除了僵化。您的依赖项可以继续更改其内部功能,而不会对其客户端代码产生任何影响。所有这一切都归功于依赖倒置原则的陈述1.b。

故事并未就此结束:世界上有些事情没有尽头。客户不断变化的需求就是其中之一。现在我有一个新需求。客户现在希望通过调用Web服务来保存员工详细信息。不再与本地数据库交互。当然,Web服务通常会抽象出它如何将员工详细信息保存到Web服务调用中的持久性存储的所有实现细节。您通常不必多次调用getPersistantStorageConnection和SaveDataToStorage等操作才能完成工作。让我们通过代码来充分理解它。让我们在牢记原则1.a和1.b的情况下实现这个需求,当然我们已经吃过苦头才学会了1.a和1.b :) 

代码片段 # 5 (附带代码中的EmployeeWithWebServiceStorage.cs文件)

using System.Data;
using System.Data.SqlClient;

namespace DependencyInversionPrincipleWebServiceImplementation
{
    public class Employee
    {

        //Trivial code has been removed for brevity and clarity. Please download attached code for 
        //complete reference

        public void SaveEmployeeDetails()
        {
            //Instantiate low-level Dependency
            IWebService persistanceStorageAgent = new EmployeeWebService();
            if (IsValidEmployee(empName,salary))
            {
                //save employee details using a web service call
                persistanceStorageAgent.SaveEmployeeData(empName, salary);
            }
        }

    }
    public interface IWebService
    {
        void SaveEmployeeData(string name, int salary);
        
    }
    public class EmployeeWebService : IWebService
    {
        public void SaveEmployeeData(string name, int salary)
        {
            //create a proxy of the web service and make a call a web method here to save employee details.
            //I've avoided the code to call a webservice for brevity as they are not of much relevance and won't hamper in understanding the concept being discussed here.
        }
    }
}

代码中的所有更改:我们引入了一个新的Web服务“EmployeeWebService”,它能够进行Web服务调用并保存员工信息。“EmployeeWebService”是代替“DatabaseStorageAgent”的新低层模块。“IWebService”是根据原则1.a和1.b,高层模块“Employee”和低层模块“EmployeeWebService”都依赖的新抽象。到目前为止一切顺利。但嘿,等等!这里一切都好吗?让我们观察一下代码片段 # 5 与代码片段 # 3 和 4 相比发生的变化。当我们从SQL数据库(代码片段 # 3)迁移到MS Access数据库(代码片段 # 4)时,高层依赖项“Employee”没有做任何更改。但代码片段 # 5 中发生了什么?我用斜体标出了所有更改。我们的依赖项代码已更改,这完全没问题,因为我们现在迁移到了Web服务实现。但我们的客户端代码或高层模块是否完好无损?答案是:不。我们不得不更改正在使用的接口以及正在实例化的依赖项。实际上,“Employee”类中SaveEmployeeDetails函数的整个实现都必须更改,才能整合新的抽象(这里是IWebService接口)。

新的真正问题:这不好。每次低层依赖项依赖于不同的接口时,我都必须更改高层依赖项中的代码。这里的线索是什么?您说对了。现在是时候进化到依赖倒置原则的陈述2.a了。

陈述 # 2.a:抽象不应依赖于细节。

如果我们能够遵循依赖倒置原则的这一陈述,我认为我们可以解决这个问题。让我们来理解一下。我们现在都知道抽象是什么。它是高层模块和低层模块之间的公共契约。什么是细节?细节本质上是您的低层模块所展示的功能。例如,“DatabaseStorageAgent”模块可以保存员工数据到某个数据库(SQL Server或MS Access),“EmployeeWebService”模块可以使用Web服务保存员工数据。所以这里的问题是,我们的抽象(IStorageAgent 或 IWebService)依赖于细节或其内部实现。

                如果我们将低层模块的实现细节从数据库更改为Web服务,我们就被迫更改高层模块中使用的抽象。结果,当我们从数据库持久化存储迁移到Web服务调用时,即使我们的高层模块符合原则1.a和1.b,也必须对我们的高层模块进行更改。那么如何摆脱它呢?

    是的。你猜对了!现在是时候进化到这个原则的最后一条陈述了。让我们来看看。

陈述 # 2.b:细节应依赖于抽象。

是的,没错!当低层依赖项的实现细节发生变化时,不要更改您的抽象。而是强制或要求细节(低层依赖项)遵循一个由高层模块决定的单一抽象。依赖关系现在已经反转。低层模块现在听命于高层模块了:) 我相信现在您将能够真正欣赏依赖倒置原则的“反转”了。现在,高层模块不再依赖于低层模块的具体实现。此外,当细节(低层模块实现)变化时,它也不会改变其抽象。让我们看看现在低层模块“DatabaseStorageAgent”和“EmployeeWebService”如何屈服于高层模块的这种强制。显而易见的方法是,它们都应该实现一个由高层模块“Employee”决定和指定的公共接口。让我们看看代码示例。

代码片段 # 6(附带代码中的EmployeeWithGenericStorage.cs)

using System.Data;
using System.Data.SqlClient;
using System.Data.OleDb;

namespace DependencyInversionPrincipleGenericStorageImplementation
{
    public class Employee
    {

        //Trivial code has been removed for brevity and clarity. Please download attached code for 
        //complete reference

        public void SaveEmployeeDetails()
        {
            //Instantiate low-level Dependency
            IStorageService persistanceStorageAgent = new DatabaseStorageAgentForMsAccess();
            if (IsValidEmployee(empName,salary))
            {
                //save employee details
                persistanceStorageAgent.SaveData(empName, salary);
            }
        }

    }
    public interface IStorageService
    {
        void SaveData(string name, int salary);
        
    }
    public class EmployeeWebService : IStorageService
    {
        public void SaveData(string name, int salary)
        {
            //create a proxy of the web service and make a call a web method here to save employee details.
            //I've avoided the code to call a webservice for brevity as they are not of much relevance and won't hamper in understanding the concept being discussed here.
        }
    }

    public class DatabaseStorageAgentForMsAccess : IStorageService
    {
        public void SaveData(string name, int salary)
        {
            //create query text
            string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", name, salary);

            //create persistance storage connection
            var oleDbCon = GetPersistantStorageConnection();

            //save employee details into the persistance storage
            SaveDataToStorage(oleDbCon, queryText);
        }
        private IDbConnection GetPersistantStorageConnection()
        {
            return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\AccessDatabases\\EmployeeDb\\employee.mdb;Persist Security Info=True");
        }

        private void SaveDataToStorage(IDbConnection dbConnection, string queryText)
        {
            var oleDbCmd = new OleDbCommand(queryText, (OleDbConnection)dbConnection);
            oleDbCmd.ExecuteNonQuery();
        }
    }

    public class DatabaseStorageAgentForSqlServer : IStorageService
    {
        public void SaveData(string name, int salary)
        {
            //create query text
            string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", name, salary);

            //create persistance storage connection
            var oleDbCon = GetPersistantStorageConnection();

            //save employee details into the persistance storage
            SaveDataToStorage(oleDbCon, queryText);
        }
        private IDbConnection GetPersistantStorageConnection()
        {
            return new SqlConnection("Data Source=rasik-PC;Initial Catalog=EmployeeDB;Integrated Security=SSPI;");
        }

        private void SaveDataToStorage(IDbConnection dbConnection, string queryText)
        {
            var sqlCmd = new SqlCommand(queryText, (SqlConnection)dbConnection);
            sqlCmd.ExecuteNonQuery();
        }
    }
}

 

代码中的所有更改:我们摒弃了由低层模块驱动的抽象“IStorageAgent”和“IWebService”。我们的高层模块“Employee”现在主导着一切。它现在引入了一个名为“IStorageService”的新接口,并要求所有低层模块毫无疑问地实现这个公共接口。当然,现在我们已经让高层模块占据了主导地位,低层模块无法质疑任何事情。要么遵守新接口,要么出局。“EmployeeWebService”现在实现了新的“IStorageService”接口。我们还为MS Access数据库和SQL Server数据库引入了两个新类“DatabaseStorageAgentForMsAccess”和“DatabaseStorageAgentForSqlServer”,它们将遵循相同的模式。

                           就是这样,我们已经对初始代码进行了所有更改,使其遵循我们依赖倒置原则的所有四项陈述1.a、1.b、2.a和2.b。

核心优势

低层模块现在变成了即插即用的,这基本上会产生以下关键影响:

  1. 低层模块内部细节的任何更改都无法影响高层模块,因为它们依赖于高层模块规定的抽象。实现IStorageService接口的每个低层模块都必须实现其saveData方法。它们在内部将细节保存在磁盘、数据库或进行Web服务调用,都不会影响高层模块。
  2. 这允许您在不进行重大返工的情况下替换系统的较低层部分。仍然会有一些小的更改来插入您想要替换旧实现的新类,但现在更改是最小的,因为依赖项是基于契约的。如果您想通过Web服务保存数据,只需将“DataStorageAgentForSqlServer”类替换为实例化正在发生“EmployeeWebService”类。其他一切都完全相同。

由于您的高层模块受到的影响最小(您只需更改正在实例化的依赖项),因此一旦您完全测试了低层模块,您对高层模块的测试工作将是最小的。

我知道这篇文章有点长,让人不知所措,但我希望它能达到您的预期,并让您对依赖倒置原则有了新的理解。我试图解释依赖倒置原则,而没有提及依赖注入、IoC容器或任何类似的东西。我想确保您理解——“依赖倒置不是依赖注入”。因此,这也完成了我们之前为本文设定的议程编号2,我们最初在开头就决定了。

您的行动项

还有一项需求。现在客户对将员工信息保存在磁盘上的XML文件中感兴趣,而不是保存在数据库或使用Web服务。我让我的同事为这个任务写了一个新的依赖类。该类的代码在附带代码的“EmployeeWithFileStorage.cs”文件中。我的同事对依赖倒置原则一无所知,所以我告诉他我的另一个朋友了解这个原则,他将针对这个伟大的SOLID原则审查您的代码,以使您的软件实现更加稳固。碰巧那个朋友就是你。下载代码并查看我上面提到的文件。您能否修复它,使其符合 said 模块的“依赖倒置”原则?我知道如果您能和我一起走到这一步,您就能做到。祝您好运!

有些事情仍然困扰您:敬请关注

虽然我们能够反转依赖关系以最小化更改,但我知道有一件事仍然让您头疼。低层依赖项的实例化仍然发生在Employee类中,这您可能会认为是硬依赖。就事论事,我同意您,尽管我们已经使低层依赖项即插即用,但我每次需要插入新的低层依赖项时仍然需要更改我的类。这可以称之为50%的即插即用。我实际上希望在插入新的低层模块时,我的高层模块零更改。那么谁将完成剩余50%的事务?这剩余的50%是我们下一个文章的动机和议程:“控制反转——现在您将比以往任何时候都更了解它”。您很快就会明白为什么依赖倒置和依赖注入相辅相成。请继续关注本文。文章上线后,我将激活后续文章的链接!

更新:本文的下一部分现已在以下网址提供

依赖注入:控制反转究竟是如何发生的 

历史

采纳了读者的反馈,使文章更加健壮和精炼,以便更强调性地传达文章的议程。

2017年2月8日:添加了本文后续部分的链接

© . All rights reserved.