17

即使我们没有意识到这一点,我们也会用一些模式编写代码。我试图真正理解一些SOLID原则以及如何在现实世界中应用这些原则。

我正在与“ D ”作斗争。

我有时会混淆Dependency InversionDependency Injection。这是否意味着只要您根据抽象(IE:接口)保持事情就完成了。

有人甚至有一个小的 C# 示例来解释它吗?

谢谢。

4

4 回答 4

23

看看 Mark Seeman 的博客,或者买他的书更好。它涵盖的不仅仅是 DI。我很感激您可能只是想要一个简单的示例来使用。然而,这是一个许多声称了解的人并不了解的主题,因此值得好好学习。

也就是说,这是一个非常简单的例子。据我了解,术语是 Inversion of ControlDependency Injection。控制反转是指您将一个类的依赖关系的控制权交给了其他一些类,这与控制依赖关系本身的类相反,通常是通过new关键字。这种控制是通过依赖注入来施加的,其中给定或注入了一个类及其依赖项。这可以通过 IoC 框架或代码(称为Pure DI)来完成。注入可以在类的构造函数中通过属性或作为方法的参数执行。依赖关系可以是任何类型,它们不必是抽象的。

这是一个列出未使用兴奋剂的环法自行车赛冠军的课程:

class CleanRiders
{
    List<Rider> GetCleanRiders()
    {
        var riderRepository = new MsSqlRiderRepository();

        return riderRepository.GetRiders.Where(x => x.Doping == false);
    }
}

这个类依赖于MsSqlRiderRepository. 该类控制实例的创建。问题是这种依赖是不灵活的。很难将其更改为 aOracleRiderRepository或 a TestRiderRepository

IoC 和 DI 为我们解决了这个问题:

class CleanRiders
{
    private IRiderRepository _repository;

    public CleanRiders(IRiderRepository repository)
    {
        _repository = repository;
    }

    List<Rider> GetCleanRiders()
    {
        return _repository.GetRiders.Where(x => x.Doping == false);
    }
}

现在这个类只依赖于一个接口。依赖项的控制权已交给类的创建者,必须通过其构造函数注入:

void Main()
{
    var c = new CleanRiders(new MsSqlRepository());

    var riders = c.GetRiders();
}

可以说,这是一种更灵活、可测试和可靠的方法。

于 2012-10-28T13:03:36.310 回答
12

S:单一职责原则

下面的代码有问题。“汽车”类包含两个不同的职责:首先是照顾汽车模型,添加配件等,然后是第二个职责:出售/租赁汽车。这会破坏 SRP。这两个职责是分开的。

public Interface ICarModels {
}

public class Automobile : ICarModels {
   string Color { get; set; }
   string Model { get; set; }
   string Year { get; set; }
   public void AddAccessory(string accessory)
   {
      // Code to Add Accessory
   }

   public void SellCar()
   {
      // Add code to sell car
   }
   public void LeaseCar()
   {
      // Add code to lease car
   }
}

为了解决这个问题,我们需要分解汽车类并使用单独的接口:

public Interface ICarModels {
}

public class Automobile : ICarModels {
   string Color { get; set; }
   string Model { get; set; }
   string Year { get; set; }
   public void AddAccessory(string accessory)
   {
      // Code to Add Accessory
   }
}

public Interface ICarSales {
}

public class CarSales : ICarSales {
   public void SellCar()
   {
      // Add code to sell car
   }
   public void LeaseCar()
   {
      // Add code to lease car
   }
}

在设计您的接口和类时,请考虑责任。修改类会涉及什么?将类分解成最简单的形式……但不是更简单(正如爱因斯坦所说)。

O:开/关原则

当需求发生变化并添加更多类型进行处理时,类应该具有足够的可扩展性,以便它们不需要修改。可以创建新类并将其用于处理。换句话说,类应该是可扩展的。我称之为“If-Type”原则。如果您的代码中有很多 if (type == ....),则需要将其分解为单独的类级别。

在这个例子中,我们试图计算经销商中汽车模型的总价格。

public class Mercedes { 
   public double Cost { get; set; } 
} 

public class CostEstimation { 
   public double Cost(Mercedes[] cars) { 
     double cost = 0; 
     foreach (var car in cars) { 
        cost += car.Cost; } return cost; } 
}

但是经销店不仅有梅赛德斯!这是类不再可扩展的地方!如果我们还想把其他车型的成本加起来怎么办?!

public class CostEstimation { 
   public double Cost(object[] cars)
   {
      double cost = 0;
      foreach (var car in cars)
      {
        if (car is Mercedes)
        {
            Mercedes mercedes = (Mercedes) car;
            cost += mercedes.cost;
        }
        else if (car is Volkswagen)
        {
            Volkswagen volks = (Volkswagen)car;
            cost += volks.cost;
        }
      }
      return cost;
   }
}

现在坏了!对于经销商批次中的每个车型,我们必须修改类并添加另一个 if 语句!

所以让我们修复它:

public abstract class Car
{
    public abstract double Cost();
}

public class Mercedes : Car
{
    public double Cost { get; set; }
    public override double Cost()
    {
        return Cost * 1.2;
    }
}

public class BMW : Car
{
    public double Cost { get; set; }
    public override double Cost()
    {
        return Cost * 1.4;
    }
}

public class Volkswagen : Car
{
    public double Cost { get; set; }
    public override double Cost()
    {
        return Cost * 1.8;
    }
}

public class CostEstimation { 

   public double Cost(Car[] cars)
   {
    double cost = 0;
    foreach (var car in cars)
    {
        cost += car.Cost();
    }
    return cost;
   }
}

到这里问题就解决了!

L:里氏替换原则

SOLID 中的 L 指的是 Liskov 原则。在派生类不能以任何方式修改基类的行为的情况下,可以巩固面向对象编程的继承概念。我将回到 LISKOV 原则的真实示例。但现在这是原则本身:

T -> 基地

其中 T [派生类] 不应篡改 Base 的行为。

一:接口隔离原则

c# 中的接口列出了需要由实现该接口的类实现的方法。例如:

Interface IAutomobile { 
   public void SellCar();
   public void BuyCar();
   public void LeaseCar();
   public void DriveCar();
   public void StopCar();
}

在这个界面中,有两组活动正在进行。一组属于推销员,另一组属于司机:

public class Salesman : IAutomobile { 
   // Group 1: Sales activities that belong to a salesman
   public void SellCar() { /* Code to Sell car */ }
   public void BuyCar(); { /* Code to Buy car */ }
   public void LeaseCar(); { /* Code to lease car */ }

   // Group 2: Driving activities that belong to a driver
   public void DriveCar() { /* no action needed for a salesman */ }
   public void StopCar(); { /* no action needed for a salesman */ }
}

在上面的类中,我们被迫实现 DriveCar 和 StopCar 方法。对推销员来说没有意义且不属于那里的东西。

public class Driver : IAutomobile { 
   // Group 1: Sales activities that belong to a salesman
   public void SellCar() { /* no action needed for a driver */ }
   public void BuyCar(); { /* no action needed for a driver */ }
   public void LeaseCar(); { /* no action needed for a driver */ }

   // Group 2: Driving activities that belong to a driver
   public void DriveCar() { /* actions to drive car */ }
   public void StopCar(); { /* actions to stop car */ }
}

与我们现在被迫实施 SellCar、BuyCar 和 LeaseCar 的方式相同。显然不属于 Driver 类的活动。

为了解决这个问题,我们需要将界面分成两部分:

Interface ISales { 
   public void SellCar();
   public void BuyCar();
   public void LeaseCar();
}

Interface IDrive {
   public void DriveCar();
   public void StopCar(); 
}

public class Salesman : ISales { 
   public void SellCar() { /* Code to Sell car */ }
   public void BuyCar(); { /* Code to Buy car */ }
   public void LeaseCar(); { /* Code to lease car */ }
}

public class Driver : IDrive { 
   public void DriveCar() { /* actions to drive car */ }
   public void StopCar(); { /* actions to stop car */ }
}

接口分离!

D:依赖倒置原则

问题是:谁取决于谁?

假设我们有一个传统的多层应用程序:

控制器层 -> 业务层 -> 数据层。

假设我们想从 Controller 告诉业务将 Employee 保存到数据库中。业务层要求数据层执行此操作。

所以我们开始创建我们的控制器(MVC 示例):

public class HomeController : Controller { 
   public void SaveEmployee()
   {
       Employee empl = new Employee();
       empl.FirstName = "John";
       empl.LastName = "Doe";
       empl.EmployeeId = 247854;

       Business myBus = new Business();
       myBus.SaveEmployee(empl);
   }
}

public class Employee { 
 string FirstName { get; set; }
 string LastName { get; set; }
 int EmployeeId { get; set; }
}

然后在我们的业务层中,我们有:

public class Business { 
   public void SaveEmployee(Employee empl)
   {
       Data myData = new Data();
       myData.SaveEmployee(empl);
   }
}

在我们的数据层中,我们创建连接并将员工保存到数据库中。这是我们传统的三层架构。

现在让我们对我们的控制器进行改进。我们可以创建一个处理所有 Employee 操作的类,而不是在我们的控制器中使用 SaveEmployee 方法:

public class PersistPeople {
   Employee empl;
   // Constructor
   PersistPeople(Employee employee) {
      empl = employee;
   }
   public void SaveEmployee() {
     Business myBus = new Business();
     myBus.SaveEmployee();
   }    

   public Employee RetrieveEmployee() {
   } 

   public void RemoveEmployee() {
   }
}

// Now our HomeController is a bit more organized.
public class HomeController : Controller { 
   Employee empl = new Employee();
   empl.FirstName = "John";
   empl.LastName = "Doe";
   empl.EmployeeId = 247854;

   PersistPeople persist = new Persist(empl);
   persist.SaveEmployee();
   } 
}

现在让我们专注于 PersistPeople 类。它与 Employee 类硬编码并紧密耦合。它在构造函数中接收一个 Emloyee 并实例化一个 Business 类来保存它。如果我们想保存“Admin”而不是“Employee”怎么办?现在我们的 Persist 类完全“依赖”于 Employee 类。

让我们使用“依赖倒置”来解决这个问题。但在此之前,我们需要创建一个 Employee 和 Admin 类都派生自的接口:

Interface IPerson { 
 string FirstName { get; set; }
 string LastName { get; set; }
 int EmployeeId { get; set; }
}

public class Employee : IPerson {
  int EmployeeId;
}

public class Admin : IPerson {
  int AdminId;
}

public class PersistPeople {
   IPerson person;
   // Constructor
   PersistPeople(IPerson person) {
      this.person = person;
   }
   public void SavePerson() {
      person.Save();
   }    
}

// Now our HomeController is using dependency inversion:
public class HomeController : Controller { 

   // If we want to save an employee we can use Persist class:
   Employee empl = new Employee();
   empl.FirstName = "John";
   empl.LastName = "Doe";
   empl.EmployeeId = 247854;
   PersistPeople persist = new Persist(empl);
   persist.SavePerson();

   // Or if we want to save an admin we can use Persist class:
   Admin admin = new Admin();
   admin.FirstName = "David";
   admin.LastName = "Borax";
   admin.EmployeeId = 999888;
   PersistPeople persist = new Persist(admin);
   persist.SavePerson();
   } 
}

所以总而言之,我们的 Persist 类不依赖于 Employee 类,也没有硬编码。它可以采用任意数量的类型,例如 Employee、Admin 等。保存传入的任何内容的控制权现在由 Persist 类而不是 HomeController。Persist 类现在知道如何保存传入的任何内容(Employee、Admin 等)。控制现在被反转并交给 Persist 类。你也可以参考这个博客来了解一些关于 SOLID 原则的很好的例子:

参考:https ://darkwareblog.wordpress.com/2017/10/17/

我希望这有帮助!

于 2018-01-10T03:44:20.210 回答
3

前几天我试图向我的同事解释这一点,在这个过程中,我什至自己也理解了这个概念。尤其是当我想出现实生活中依赖倒置的真实例子时。

故事

想象一下,如果一个汽车司机依赖一辆车:只能开一辆车——那辆车!这将是非常糟糕的:

直接/硬依赖

在这种情况下,依赖的方向是:Driver => Car(Driver 对象依赖于 Car 对象)。

值得庆幸的是,在现实生活中,每辆车都有界面:“方向盘、踏板和变速杆”。驾驶员不再依赖于汽车,因此驾驶员可以驾驶任何汽车:

依赖倒置

现在 TheDriver 依赖于 ICar 接口,TheCar 也依赖于 ICar 接口 - 依赖是INVERTED

于 2014-02-28T18:23:47.577 回答
1

我不像其他人那样是专家,但会尝试用概念解释 DIP。DIP 的核心是对接口的编程,即您的高级类将依赖于抽象,而您的低级类也依赖于抽象。例如

假设您定义了一个抽象,PhoneVendor即它可以是三星、苹果、诺基亚等。对不起,我有一段时间没有编写 Java 的代码了,它可能有语法错误,但仍然是关于概念的。

public abstract class PhoneVendor {

    /**
    * Abstract method that returns a list of phone types that each vendor creates.
    */
    public abstract Vector getPhones(){ }
}


public class Samsung extends PhoneVendor{
    public Vector getPhones(){ // return a list of phones it manufactures...  }
}

public class PhoneFinder{
   private PhoneVendor vendor;

   public PhoneFinder(PhoneVendor vendor){ this.vendor = vendor;}

  /**
   *for example just return a concatnated string of phones
  */
   public string getPhoneTypes(){
      Vector ListOfPhones = PhoneVendor.getPhones();        
      return ListOfPhones;
   }
}

如您所见,PhoneFinder 类依赖于抽象而不是 PhoneVendor 的实现。实现抽象的基础类与使用它的高级类分离。这使得设计非常灵活,添加新的低级类不会破坏任何以前编写的代码,因为 PhoneFinder 依赖于抽象而不是实现。

于 2012-10-28T19:03:50.693 回答