即使我们没有意识到,我们都会用一些模式编写代码。我想要真正理解一些 S.O.L.I.D 原则,以及如何在现实世界中应用这些原则。
我正在努力与#34; D "。
我有时会将依赖性反转与依赖注入混淆。这是否意味着只要你依赖于抽象(IE:接口)就可以完成任务。
是否有人有一个小的C#示例解释它?
感谢。
答案 0 :(得分:18)
看看Mark Seeman的博客,或者更好的是,购买他的书。它涵盖的不仅仅是DI。我很感激你可能只想要一个简单的样本。然而,这是一个许多声称理解的主题,因此值得学习的主题。
那就是说,这是一个非常简单的例子。据我所知,术语是控制和依赖注入的反转。控制反转是指您将类的依赖关系控制到其他类,与控制依赖关系本身的类相反,通常通过 new 关键字。此控制通过依赖注入来执行,其中给出或注入了一个类及其依赖项。这可以通过IoC框架或代码(称为Pure DI)来完成。可以在类的构造函数中,通过属性或作为方法的参数执行注入。依赖关系可以是任何类型,它们不必是抽象的。
这是一个列出没有掺杂的环法自行车赛冠军的课程:
class CleanRiders
{
List<Rider> GetCleanRiders()
{
var riderRepository = new MsSqlRiderRepository();
riderRepository.GetRiders.Where(x => x.Doping == false);
}
}
此课程取决于MsSqlRiderRepository
。该类控制实例的创建。问题是这种依赖性是不灵活的。很难将其更改为OracleRiderRepository
或TestRiderRepository
。
IoC和DI为我们解决了这个问题:
class CleanRiders
{
private IRiderRepository _repository;
public CleanRiders(IRiderRepository repository)
{
_repository = repository;
}
List<Rider> GetCleanRiders()
{
_repository.GetRiders.Where(x => x.Doping == false);
}
}
现在该类只依赖于接口。对依赖项的控制已经被赋予类的创建者,并且必须通过其构造函数注入:
void Main()
{
var c = new CleanRiders(new MsSqlRepository());
var riders = c.GetRiders();
}
可以说是一种更灵活,可测试和SOLID的方法。
答案 1 :(得分:5)
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
}
}
要解决此问题,我们需要拆分Automobile类并使用单独的接口:
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:开放/封闭原则
当需求发生变化并且为处理添加了更多类型时,类应该具有足够的可扩展性,以便它们不需要修改。可以创建新类并用于处理。换句话说,类应该是可扩展的。我称之为&#34; If-Type&#34;原理。如果您的代码中有很多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:Liskov替代原则
SOLID中的L指Liskov原理。面向对象编程的继承概念可以固化,其中派生类不能以任何方式修改基类的行为。我将回到LISKOV原理的现实世界的例子。但就目前而言,这就是原则本身:
T - &gt;基
其中T [派生类]不应该篡改Base的行为。
I:界面分离原则
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:依赖性倒置原则
问题是:谁取决于谁?
我们说我们有一个传统的多层应用程序:
控制器层 - &gt;业务层 - &gt;数据层。
假设Controller希望告诉业务将Employee保存到数据库中。业务层要求数据层执行此操作。
所以我们开始创建我们的Controller(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);
}
}
在我们的数据层中,我们创建连接并将员工保存到数据库中。这是我们传统的3层架构。
现在让我们对Controller进行改进。我们可以创建一个负责所有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类硬编码并紧密耦合。它在contstructor中接受一个Emloyee并实例化一个Business类来保存它。如果我们想要保存一个&#34; Admin&#34;而不是&#34;员工&#34;?现在我们的坚持课程完全依赖于#34; Dependent&#34;在Employee类上。
让我们使用&#34;依赖倒置&#34;解决这个问题。但在此之前,我们需要创建一个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/
我希望这有帮助!
答案 2 :(得分:2)
我试图在前几天向我的同事解释这一点,在这个过程中我甚至自己都理解了这个概念。特别是当我想出现实生活中依赖倒置的现实例子时。
故事
想象一下,如果一名汽车司机依赖汽车:只能驾驶一辆汽车 - 汽车!这将是非常糟糕的:
在这种情况下,依赖的方向是:Driver =&gt; Car(Driver对象取决于Car对象)。
值得庆幸的是,在现实生活中,每辆车都有接口:“方向盘,踏板和变速杆”。司机不再依赖于汽车,所以司机可以驾驶任何汽车:
现在TheDriver依赖于ICar界面,TheCar也依赖于ICar界面 - 依赖是 INVERTED :
答案 3 :(得分: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依赖于抽象而不是实现。