可靠性设计的基本原则_可靠的原则

可靠性设计的基本原则

The SOLID principles are a fundamental set of concepts that ensure that Object Oriented Programming (OOP) is done correctly. SOLID was initially promoted as a set of related principles by Robert C. Martin, and since have become a standard in the field of software engineering. This article explains the five SOLID principles in practical terms with a few simple examples in C#.

SOLID原则是一组基本概念,可确保正确完成面向对象编程(OOP)。 SOLID最初是由Robert C. Martin提出的一组相关原则,并从此成为软件工程领域的标准。 本文用C#中的一些简单示例以实用的方式解释了SOLID的五项原则。

The SOLID principles of OOP are: Single Responsibility Principle, Open-Closed Principle, Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). The SOLID principles ensure that OOP applications are readable, testable, scalable, and maintainable.

OOP的SOLID原则是:单一职责原则,开放-封闭原则,Liskov替代原则(LSP),接口隔离原则(ISP)和依赖项倒置原则(DIP)。 SOLID原则确保OOP应用程序是可读,可测试,可伸缩和可维护的。

Single Responsibility Principle

单一责任原则

SRP dictates that each component should have only one responsibility. This makes the Classes and their methods easy to read and understand, and makes sure changes affect only the component that needs to be changed. An example that violates the SRP principle is shown in the following code snippet. In this case, a class has the task of calculating sales tax. Because sales tax regulations vary from province to province, the class has to be able to calculate the tax for multiple provinces.

S RP规定每个组件仅应承担一项责任。 这使类及其方法易于阅读和理解,并确保更改仅影响需要更改的组件。 下面的代码片段中显示了一个违反SRP原理的示例。 在这种情况下,班级的任务是计算营业税。 由于各省的营业税法规不同,因此该类别必须能够计算多个省的税额。

class TaxCalculator
{
private string Province { get; set; }
private double TaxPercentage { get; set; }
public TaxCalculator(string province)
{
Province = province;
switch(province)
{
case "Quebec":
Province = "Quebec";
TaxPercentage = 14.975;
break;
default:
Province = "BritishColumbia";
TaxPercentage = 12;
break;
}
}
public double calculate_tax(Product product)
{
double result = 0;
double result = Math.Round(product.Price*TaxPercentage/100,2);
return result;
}
}public class Product
{
public string Name { get; }
public double Price { get; }
public string Category { get; }
public bool Imported { get; } public Product(string name, double price, string category, bool imported)
{
Name = name;
Price = price;
Category = category;
Imported = imported;
}
}

This solution works, but violating the SRP principle makes it hard to test and maintain. For example, if one of the provinces decides to change its sales tax percentage, the entire class needs to change, which could result in unintended side-effects for the other province.

此解决方案有效,但违反SRP原理使其难以测试和维护。 例如,如果一个省决定更改其销售税百分比,则整个类别都需要更改,这可能会给另一个省带来意外的副作用。

The code snippet solution solves the SRP violation by introducing an Abstraction and decoupling the tax calculator Class implementations by province.

该代码段解决方案通过引入抽象并按省将税费计算器的类实现解耦来解决SRP违规问题。

public abstract class TaxCalculator
{
public abstract double TaxPercentage { get; }
public abstract double calculate_sales_tax(Product product);
}
public class BritishColumbiaTaxCalculator : TaxCalculator
{
override public double TaxPercentage { get; } = 12;
override public double calculate_sales_tax(Product product)
{
return Math.Round(product.Price*TaxPercentage/100,2);
}
}public class QuebecTaxCalculator : TaxCalculator
{
override public double TaxPercentage { get; } = 14.975; override public double calculate_sales_tax(Product product)
{
return Math.Round(product.Price * TaxPercentage/100,2);
}
}

Now, changes to one of the province will not affect the other. Furthermore, refactoring of unit tests for the tax calculator implementations would only need to be done for the implementation that has changed.

现在,对一个省的更改不会影响另一个省。 此外,仅需要针对已更改的实现对税额计算器实现进行单元测试的重构。

Open Closed Principle

开闭原则

OCP dictates that OOP entities should be open for extension, but closed for modification. The intend of this principle is to make existing components resilient to changing requirements. To implement OCP, several concepts are available within OOP languages, including abstractions, interfaces, and generics. Using these, it is possible to decouple the basics from the specifics. Open for Extension means that new implementations can be added to extend specific functionalities, while Closed for Modification means that fundamental functionalities of a component will not change.

O CP指示OOP实体应为扩展而开放,但应为修改而封闭。 该原理的目的是使现有组件能够适应不断变化的需求。 为了实现OCP,OOP语言中提供了几种概念,包括抽象,接口和泛型。 使用这些,可以将基础与细节分离。 Open for Extension意味着可以添加新的实现以扩展特定功能,而Closed for Modification则意味着组件的基本功能不会改变。

The improved example with the sales tax calculator above already partially satisfies OCP by using an abstraction and allowing extension through inheritance from that abstraction. If a new province is added, it would result in a new child TaxCalculator class without the need to modify the abstraction, nor any of the existing specific implementations.

上面带有销售税计算器的改进示例通过使用抽象并允许通过继承该抽象来进行扩展,已经部分满足了OCP。 如果添加了一个新省,则将导致一个新的子TaxCalculator类,而无需修改抽象或任何​​现有的特定实现。

Now, what if British Columbia decides to apply an additional 5% sales tax on all imported products? The following solution implements the change, but violates the OCP, because the class is changed to add an additional method for the import tax.

现在,如果不列颠哥伦比亚省决定对所有进口产品加收5%的营业税呢? 以下解决方案实现了更改,但是违反了OCP,因为更改了类以添加用于进口税的其他方法。

public class QuebecTaxCalculator : TaxCalculator
{
override public double TaxPercentage { get; } = 14.975; override public double calculate_sales_tax(Product product){
return Math.Round(product.Price*(TaxPercentage)/100,2);
} public double calculate_import_tax(Product product)
{
return Math.Round(product.Price*5/100,2);
}}

A way to implemented this change would be to use inheritance to create a specific implementation for imported products as follows.

实现此更改的一种方法是使用继承为导入的产品创建特定的实现,如下所示。

public class ImportQuebecTaxCalculator : QuebecCalculator
{
public double calculate_import_tax(Product product)
{
return Math.Round(product.Price*5/100,2);
}
}

Now, we have extended the functionality of our code by adding more functionality (Open for Extension), while avoiding changing the existing code (Closed for modification).

现在,我们通过添加更多功能(扩展为Open)扩展了代码的功能,同时避免更改现有代码(为修改而关闭)。

A valuable benefit of OCP is that existing Unit Tests do not need to be refactored — only extended with additional test to cover the new specific implementations.

OCP的一个重要好处是不需要重构现有的单元测试-只需扩展附加的测试即可覆盖新的特定实现。

Liskov Substitution Principle

里斯科夫替代原则

“If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction.”

“如果它看起来像鸭子,却像鸭子一样嘎嘎叫,但它需要电池,则可能是错误的抽象。”

LSP means that an OOP application behaves correctly if an object of a child class replaces an object of its base class. In very simple terms, an object of class apple, should be able to replace an object of class fruit without breaking the application.

L SP表示如果子类的对象替换其基类的对象,则OOP应用程序的行为正确。 简单来说,苹果类的对象应该能够替换水果类的对象,而不会破坏应用程序。

LSP is achieved by making sure of two things. First, every method in a child class that overrides a method in a parent class has to accept the same number of arguments as input and be either as restrictive or less than the parent with regard to the type of arguments. Second, the return values of overridden methods must be of the same type as the return values of the parent class methods, and as restrictive or less.

LSP是通过确保两件事来实现的。 首先,子类中重写父类中方法的每个方法都必须接受相同数量的参数作为输入,并且在参数类型方面要比父类具有相同的限制或更少的限制。 其次,被覆盖方法的返回值必须与父类方法的返回值具有相同的类型,并且应具有限制性或更小。

If we go back to the tax calculator, the following example would violate the LSP in both regards.

如果回到税收计算器,以下示例将在两个方面违反LSP。

public abstract class TaxCalculator<T>
{
public abstract double TaxPercentage { get; }
public T calculate_sales_tax(IProduct product);
}
public class BritishColumbiaTaxCalculator : TaxCalculator
{
override public double TaxPercentage { get; } = 12;
override public double calculate_sales_tax(Product product)
{
double tax;
if (product.Imported)
{
tax = Math.Round(calculate_tax(product)+product.Price*5/100);
} else
{
tax = Math.Round(calculate_tax(product));
}
return tax;
}
}public interface IProduct
{
public string Name { get; }
public double Price { get; }
public string Category { get; }
}

Notice the changes to the TaxCalculator abstraction. For one, it is now a generic that does not impose the return type on the calculate_sales_tax method. Second, it used an interface as the argument type of the method. The subclass BritishColumbiaTaxCalculator, on the other hand, overrides the method and imposes a stricter type on both the argument and the return. As you may have noticed, the child class not only uses the Product class instead of the Product interface, but it also uses the Imported property of Product in an If statement. This is kind of what the quote about the duck is referring to.

请注意TaxCalculator抽象的更改。 举例来说,现在它是一个通用类型,不会将返回类型强加到calculate_sales_tax方法上。 其次,它使用接口作为方法的参数类型。 另一方面,子类BritishColumbiaTaxCalculator覆盖了该方法,并在参数和返回值上施加了更严格的类型。 您可能已经注意到,子类不仅使用Product 而不是Product 接口 ,而且还在If语句中使用ProductImported属性 。 这就是关于鸭子的报价所指的那种。

On a side note, a good statically typed language like C# won’t compile for the type of violation shown in this example. This is how fundamental the principle is and how embedded it is in software development environments. On the other hand, a loosely typed language, such as Python, may let you get away with it (at your own risk and peril).

附带说明一下,良好的静态类型语言(如C#)不会针对此示例中显示的违规类型进行编译。 这就是原理的基本原理以及它在软件开发环境中的嵌入方式。 另一方面,松散类型的语言(例如Python)可能会让您无法使用它(风险和风险自负)。

Interface Segregation Principle

接口隔离原理

ISP dictates that a class shouldn’t have to implement members that it does not need. In a language that supports interfaces, this principle can be taken literally — don’t design interfaces to capture all use-cases. Instead, different interfaces should be created to fit the minimum requirements of each use case (a.k.a. implementation). Not complying with ISP brings obvious design flaws, such as unnecessary refactoring of existing Classes coupling the Classes through aggregated interfaces, and making the code harder to understand and maintain.

I SP指示类不必实现它不需要的成员。 在支持接口的语言中,可以从字面上理解这一原理-不要设计接口来捕获所有用例。 相反,应该创建不同的接口以适合每个用例(又称为实现)的最低要求。 不遵守ISP会带来明显的设计缺陷,例如对现有类的不必要的重构,这些类通过聚合接口将类耦合在一起,并使代码更难以理解和维护。

We could go on with the example about the tax calculator, but let’s have some real fun. The following example violates the ISP principle by aggregating method signatures for different types of vehicles in a single interface.

我们可以继续讨论有关税额计算器的示例,但是让我们有一些真正的乐趣。 以下示例通过在单个接口中汇总不同类型车辆的方法签名来违反ISP原理。

public interface ITransportVehicle
{
public void Move(double speed);
public void Fly(double altitude);
}public class Airplane: ITransportVehicle
{
public void Move(double speed)
{
Console.WriteLine("Movind at speed "+speed+" mph");
} public void Fly(double altitude)
{
Console.WriteLine("Flying at altitude "+altitude+" feet");
}
}public class Truck : ITransportVehicle
{
public void Move(double speed)
{
Console.WriteLine("Movind at speed " + speed + " mph");
}
public void Fly(double altitude)
{
Console.WriteLine("Are you high?");
}
}

Obviously, trucks can’t fly, but the interface TransportVehicle nonsensically imposes the implementation of the fly method even for ground vehicles. One way to fix this is by segregating the interface into one interface for ground vehicles, and one for aircraft, as follows.

显然,卡车不能飞行,但是Interface TransportVehicle毫无意义地强加了即使对于地面车辆也采用了飞行 方法 。 解决此问题的一种方法是,将接口分为地面车辆的一个接口和飞机的一个接口,如下所示。

public interface IGroundVehicle
{
public void Move(double speed);
}public interface IAircraft
{
public void Move(double speed);
public void Fly(double altitude);
}public class Airplane: IAircraft
{
public void Move(double speed)
{
Console.WriteLine("Movind at speed "+speed+" mph");
} public void Fly(double altitude)
{
Console.WriteLine("Flying at altitude "+altitude+" feet");
}
}public class Truck : IGroundVehicle
{
public void Move(double speed)
{
Console.WriteLine("Movind at speed " + speed + " mph");
}
}

Good. Now, at least, if the fly method signature changes, we don’t need to refactor the Truck class.

好。 现在,至少,如果fly方法签名更改,我们不需要重构Truck类

In languages that do not support the interface concept, the ISP applies to base classes and abstractions as well.

在不支持接口概念的语言中,ISP也适用于基类和抽象。

Dependency Inversion Principle

依赖倒置原则

“A) High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).

“ A)高级模块不应依赖于低级模块。 两者都应依赖抽象(例如接口)。

B) Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.”

B)抽象不应依赖细节。 细节(具体实现)应取决于抽象。”

DIP dictates that high-level (fundamental logic) components should not depend on low-level (specific logic) components.

D IP指示高级(基本逻辑)组件不应依赖于低级(特定逻辑)组件。

DIP can be achieved by introducing abstractions that both high-level and low-level classes depend on, achieving decoupling between low and high level components of the code. Note that DIP is not about inverting the direction of dependence between low-level and high-level components — it is about decoupling them through a common abstraction.

可以通过引入高级和低级类都依赖的抽象来实现DIP,并实现代码的低级和高级组件之间的解耦。 请注意,DIP并不是要颠倒低级和高级组件之间的依赖关系,而是要通过通用抽象将它们去耦。

Let’s take an example of a car rental company booking application, where we need to be able to reserve different types of cars. We design our application as follows, violating the DIP.

让我们以汽车租赁公司预订应用程序为例,在这里我们需要能够预订不同类型的汽车。 我们设计应用程序的方式如下,违反了DIP。

public class ReservationApp
{
public List<CompactCar> CompactCars {get; set;}
public List<LuxuryCar> LuxuryCars { get; set; } public ReservationApp(CompactCars cars, LuxuryCars cars)
{
// Adds some cars...
} public bool reserveCompact(CompactCar car, Client client)
{
if (!CompactCars.Contains(car) && car.Client != null)
{
car.Reserve(client);
return true;
}else
{
Console.WriteLine("The car was not found or is reserved");
return false;
}
} public bool reserveLux(LuxuryCar car, Client client)
{
if (!LuxuryCars.Contains(car) && car.Client != null)
{
car.Reserve(client);
return true;
} else
{
Console.WriteLine("The car was not found or is reserved");
return false;
}
}
}public class CompactCar
{
public string ID { get; }
// ... make, model, year, mileage, etc.
public Client Client { get; set; } public void Reserve(Client client)
{
Client = client;
} public bool hasGPS()
{
// ... method checks and returns true/false
}}public class LuxuryCar
{
public string ID { get; }
// ... make, model, year, mileage, etc.
public Client Client { get; set; }
public void Reserve(Client client)
{
Client = client;
} public bool hasSatelliteRoadsideAssistance()
{
// ... method checks and returns true/false
}}public class Client
{
public string Name { get; }
public string Phone { get; set; }
public Client(string name, string phone)
{
Name = name;
Phone = phone;
}
}

Alright. If you have been paying attention so far, you would have noticed more than one SOLID violation in this example. Besides violating DIP, which we’ll come back to in a bit, it also violates the OCP — the reservationApp class is not OCP, since adding a new vehicle category would surely result in refactoring that class.

好的。 如果到目前为止您一直在关注,那么在此示例中您会注意到不止一个SOLID违规。 除了违反DIP(我们稍后再讲)之外,它还违反了OCP- 保留类不是OCP,因为添加新的车辆类别肯定会导致重构该类。

Now, back to DIP. In the reservation app example, the reservationApp class is a high-level module, while the car is a low-level module. One aspect of DIP that could be a little bit harder to grasp is how are components ranked from high to low level. For this, you need to thing from the domain perspective — what is the app’s primary business use? Fundamentally, a car reservation app’s primary use is to reserve cars. So the module that manages the reservations is logically a higher level (more fundamental) component, while the specifics of what is being reserved is the cars. The previous code example’s problem is that it makes to high level component depend on the specific car types. This is not an expandable, easy to maintain design, because if the specific requirements change, and new categories of cars are to be added, current modules will require refactoring. To satisfy the DIP, we can refactor as follows, introducing an interface on which both, the high-level component and the low-level components depend.

现在,回到DIP。 在预订应用程序示例中, reservationApp类是高级模块,而汽车是低级模块。 DIP可能很难把握的一个方面是如何将组件从高到低排序。 为此,您需要从域角度出发-应用程序的主要业务用途是什么? 从根本上讲,汽车预订应用程序的主要用途是预订汽车。 因此,管理预订的模块在逻辑上是一个较高级别(更基础)的组件,而预订的细节是汽车。 前面的代码示例的问题在于,它使高级组件取决于特定的汽车类型。 这不是可扩展的,易于维护的设计,因为如果特定要求发生变化,并且要添加新类别的汽车,则当前模块将需要重构。 为了满足DIP,我们可以进行以下重构,引入一个接口,高层组件和低端组件都依赖于此接口。

public class ReservationApp
{
public List<IVehicle> Cars {get; set;}
public ReservationApp(List<IVehicle> cars)
{
// Adds some cars...
} public bool reserveVehicle(IVehicle car, Client client)
{
if (!Cars.Contains(car) && car.Client != null)
{
car.Reserve(client);
return true;
} else
{
Console.WriteLine("The car was not found or is reserved");
return false;
}
}
}public interface IVehicle
{
public string ID { get; }
public Client Client { get; set; }
public void Reserve(Client client);
}public class CompactCar: IVehicle
{
public string ID { get; }
public Client Client { get; set; } public void Reserve(Client client)
{
Client = client;
}
}public class LuxuryCar : IVehicle
{
public string ID { get; }
public Client Client { get; set; } public void Reserve(Client client)
{
Client = client;
}
}

With this, DIP is satisfied. Notice that this also solved the OCP issue, and is LSP compliant, since we can substitute a more specific class of car that implements IVehicle interface, without a risk of causing an exception in the ReservationApp class. This last observation is an interesting fact about DIP: it goes hand-in-hand with OCP and LSP compliance.

由此,DIP得到满足。 请注意,这也解决了OCP问题,并且符合LSP,因为我们可以替换实现IVehicle接口的更特定的汽车类,而不会在ReservationApp类中引起异常 最后的观察结果是有关DIP的一个有趣的事实:它与OCP和LSP法规遵从性息息相关。

Final Notes

最后说明

Did you notice how by solving the SRP violation in the sales tax calculator example, we incidentally satisfied the OCP principle? By decoupling the implementations for each province for the base class, which is the Abstraction, we made the base open for extension, but closed for modification (at least when requirement changes would involve adding new provinces). Does this mean that we are done with the OCP as far as the sales tax calculator is concerned? Not at all.

您是否注意到通过解决营业税计算器示例中的SRP违规,我们偶然满足了OCP原则? 通过将每个省的实现的基础类(抽象)解耦,我们使基础可以扩展,但可以进行修改(至少在需求变更涉及添加新省时)。 就销售税计算器而言,这是否意味着我们已经完成了OCP? 一点也不。

One interesting aspect of SOLID, and one that doesn’t get mentioned often (if not ever), is that there is no recipe for applying SOLID — call it a framework, a mindset, or guidelines, but there will never be a step-by-step tutorial about how to do SOLID. Therefore, an application can be more or less “solid”, depending on how skillful a developer is at interpreting and applying the principles — one can only strive to develop more “solid” software, while meeting deadlines, but trying to achieve absolute SOLID will ultimately derail a project and add unnecessary complexity. Domain knowledge, combined with experience and thoughtfulness during design and development is the only way to scope the application of SOLID principles by targeting specific use cases and anticipating how requirements my change in the future.

SOLID的一个有趣的方面(很少被提及)(即使从未被提及)是没有适用SOLID的诀窍-称之为框架,思维定势或指导方针,但永远不会走出一步-有关如何进行SOLID的分步教程。 因此,取决于开发人员在解释和应用原则方面的熟练程度,一个应用程序或多或少可以是“可靠的” —一个人只能在满足最后期限的情况下努力开发更多“可靠”的软件,但尝试实现绝对的SOLID将最终使项目脱轨并增加不必要的复杂性。 通过针对特定用例并预测未来需求的变化,领域知识与设计和开发过程中的经验和体贴相结合是确定SOLID原则应用范围的唯一方法。

翻译自: https://medium.com/swlh/solid-principles-of-oop-c24bd6ccde77

可靠性设计的基本原则

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值