[Translation] SOLID principles developer of Information

原文:SOLID Principles every Developer Should Know – Bits and Pieces

SOLID Principles every devloper should know

Object brings new design approach for software development, it enables developers to data having the same purpose or function of junction into one class to complete the single purpose, no need to consider the entire application.

However, object-oriented programming is no less confusing and unmaintainable program. It is this, Robert C. Martin developed a five guidelines / standards, allows developers to create easy-to-read and easy to maintain program.

This criterion is SOLID five principles (abbreviation is Michael Feathers deduced)

  • S: Single Responsibilty Principle single responsibility principle
  • O: Open-Closed Principle shutter principle
  • L: Liskov Substitution Principle Leeb replaced
  • I: Interface Segregation Principle Interface Separation
  • D: Dependency Inversion Principle Dependency Inversion

Next we discuss these principles in detail.
Note: Most of the examples in this article may not meet applicable or real-world applications. Depending on your own the actual design and usage scenarios to be. The most important is to understand and master how to use or follow these principles.

Recommendation: Use Bit of such tools to practice SOLID principles that can help you organize, find, and reuse components to build new applications. Components can be found and shared among different projects, so you can build applications faster, worth a try.

Single responsibility principle Single Responsibilty Principle

“...You had one job”---Loki to Skurge in Thor: Ragnarok

A class only do one job

A class is only responsible for one thing. If a class has a number of responsibilities, it becomes couples. Changes can cause a function to another function changes.

  • Note: This principle applies not only to the class, but also to software components and micro service.

For example, consider a design:

    class Animal{
      constructor(name: string){}
      getAnimalName(){}
      saveAniamal(a: Animal){}    
    }
    

Here's Animal class is contrary to the single responsibility principle (SRP)?

What violated?

SRP says a class should contain only one function, and now we can separate the two functions: data management and animal characteristics of animal management. Constructors and getAnimalName management of animal characteristics, while saveAnimal responsible for the animals stored in the database.

This design will lead to what kind of problem in the future?

If that part of the application for database management-related functions for the change, the use of animal characteristics have affected the function code and recompile to adapt to the new changes.

This shows that the system is very rigid, like a domino effect, touching a card will affect all other licensing arrangement.

In order to comply with SRP, we create another class only a single function is responsible for an animal stored in a database:

class Animal {
  constuctor(name: string) { }
  getAnimalName() { }
}

class AnimalDB {
  getAnimal(a: Animal) { }
  saveAnimal(a: Animal) { }
}
When designing our classes, we should aim to put related features together,
so whenever they tend to change they change for the same reason.
And we should try to separate features if they will change for different reasons. 
我们在设计类的时候,要以将相关的特性放在一起为目标,
当他们需要改变时应当是出于相同的原因,
如果我们发现他们会因为不同的原因改变,则需考虑将特性拆分开来
                                                      ---Steve Fenton

Opening and closing the principles of Open-Closed Principle

Software entities (Classes, modules, functions ) should be open for extension, not modification.
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for change

We continue to discuss the Animal class,

class Animal {
  construtor(name: string) { }
  getAnimalName() { }
}

We want to traverse a list of animal and let make their voices heard.

//...
const animals: Array<Animals> = [
  new Animal('lion'),
  new Animal('mouse')
  ];
  
function AnimalSound(a: Array<Animal>) {
  for(int i = 0; i <= a.length; i++){
    if(a[i].name == 'lion')
      log('roar');
    if(a[i].name == 'mouse')
      log('squeak');
  }
}
AnimalSound(animals);

AnimalSound this function does not comply with the principle of opening and closing, because it can not remain closed to new animal species. If we add a new kind of animal, Snake:

//....
const animals: Array<Animal> = [
  new Animal('lion'),
  new Animal('mouse'),
  new Animal('snake')
];
//...

We have to modify AnimalSoound function

//...
function AnimalSound(a: Array<Animal>) {
  for (int i = 0; i <= a.length; i++){
    if(a[i].name == 'lion')
      log('roar');
    if(a[i].name == 'mouse')
      log('squeak');
    if(a[i].name == 'snake')
      log('hiss');
    }
}

AnimalSound(animals);

Visible, no new kind of animal, AnimalSound function will add new logic. This example has been very simple. When applications become larger and more complex as you will find that every time you add a new animal, AnimalSound of if statements will continue to be repeated in the program.

But how to make it in line with the principle of opening and closing (OCP) it?

class Animal {
  makeSound();
  //...
}

class Lion extends Animal {
  makeSound() {
    return 'roar';
  }
}

class Squirrel extends Animal {
  makeSound() {
    return 'squeak';
  }
}

class Snake extends Animal {
  makeSound() {
    return 'hiss';
  }
}

//...
function AnimalSound(a: Array<Animal>) {
  for(int i = 0; i <= a.length; i++) {
    log(a[i].makeSound());
  }
}

AnimalSound(animals);

Now the Animal class has a virtual function makeSound , we let each animal inherits the Animal class and the realization of their own makeSound.
Every animal in makeSound add hair to achieve sound, traversing the array of animals only when they need to call the makeSound method.

Thus, if you want to add new animals, AnimalSound do not change. We only need to add a new animal array of animals.

Another example:

Suppose you have a shop, you want to give your favorite a 20% discount to those customers, the following are class implementation:

class Discount {
  giveDiscount() {
    return this.price * 0.2;
  }
}

When you decide to double VIP user discounts, you might modify this class:

class Discount {
  giveDiscount() {
    if(this.customer == 'fav')
      return this.price * 0.2;
    if(this.customer == 'vip')
      return this.price * 0.4;
  }
}

wrong! This is not in line with the principle of OCP, OCP against it. If you want to offer new discounts to various other customers, you have to add new logic.

In order to make it consistent with OCP, we need to add a class to extend Discount class, implement its new behavior in this new category:

class VIPDiscount: Discount {
  getDiscount() {
    return super.getDiscount() * 2;
  }
}

If you need a super VIP customers 80% discount, implementation is probably what happened:

class SuperVIPDiscount: VIPDiscount {
  getDiscount() {
    return super.getDiscount() * 2;
  }
}

In this way, without modification to achieve the expansion.

Richter replace Liskov Substitution Principle

A sub-class must be substitutable for its super class
subclasses the parent class can replace some

This is the principle purpose is to ensure error-free subclass can replace the position of the parent class. It also found that if the code needs to check that subclass, it is not in line with this principle.

Animal class exemplified by:

function AnimalLegCount(a: Array<Animal>) {
  for(int i = 0; i <= a.length; i++) {
    if(typeof a[i] == Lion)
      log(LionLegCount(a[i]));
    if(typeof a[i] == Mouse)
      log(MouseLegCount(a[i]));
    if(typeof a[i] == Snake)
      log(SnakeLegCount(a[i]));
  }
}
AnimalLegCount(animals);

This code does not meet the LSP, nor with OCP. It must be determined for each type of animal and call the appropriate method meter leg.

Whenever new kind of animal, this function will need to make changes to adapt.

//...
class Pigeon extends Animal {

}
const animals[]: Array<Animal>) = [
  //...
  new Pigeon();
];

function AnimalLegCount(a: Array<Animal>) {
  for(int i = 0; i <= a.length; i++) {
    if(typeof a[i] == Lion)
      log(LionLegCount(a[i]));  
    if(typeof a[i] == Mouse)     
      log(MouseLegCount(a[i]));     
    if(typeof a[i] == Snake)       
      log(SnakeLegCount(a[i]));     
    if(typeof a[i] == Pigeon)     
      log(PigeonLegCount(a[i]));
  }
}
AimalLegCount(animals);

To make this function in line with LSP, following must be observed Steven Fenton asked:

  • If the parent class (Animal) has accepted a parent class type method (Animal) parameter, its subclasses (Pigeon) should receive a type of the parent class (Animal) or sub-class type (Pigeon) as a parameter
  • If the parent class returns a parent type (Animal), subclasses should return a parent type (Animal) or sub-class type (Pigeon).

Now re-implement AnimalLegCount function:

function AnimalLegCount(a: Array<Animal>) {
  for(let i = 0; i <= a.length; i++) {
    log(a[i].LegCount());
  }
}
AnimalLegCount(animals);

AnimalLegCount functions are now less concerned about the type of Animal pass, it just calls LegCount method. All it knows is passed in argument must be of type Animal, Animal either type or a subclass.

Animal Type now need to achieve / LegCount define a method:

class Animal {
  //...
  LegCount();
}

Its subclasses also need to implement LegCount method:

class Lion extends Animal{
  //...
  LegCount() {
    //...
  }
}

When it is passed to AnimalLegCount function, he returns the number of legs of a lion.

Visible AnimalLegCount function does not need to know the specific type Animal, and just call LegCount method Animal class, because according to the agreed subclass of the Animal class must implement LegCount function.

The principle of separation Interface Interface Segregation Principle

Make fine grained interfaces that are client specific
production of fine-grained interfaces for specific clients
Clients should not be forced to depend upon interfacees that they do not use
the client interfaces should not be forced to use since they do not

This principle drawbacks when used to treat large-scale implementation of the interface. View of the following interfaces IShape:

interface Ishape {
  drawCircle();
  drawSquare();
  drawRectangle();
}

This interface can be drawn round, square, rectangular. When Circle class, Square-based, Rectangel IShape class implements the interface must be defined drawCircle (), drawSqure (), drawRectangle () method.

class Circle implements Ishape {
  drawCircle(){
    //...
  }
  
  drawSquare(){
    //...
  }
  
  drawRectangle(){
    //...
  }
}

class Square implements Ishape {
  drawCircle() {
    //...
  }
  
  drawSquare(){
    //...
  }
  
  drawRectangle(){
    //...
  }
}

class Rectangel implements Ishape {
  drawCircle() {
    //...
  }
  
  drawSquare(){
    //...
  }
  
  drawRectangle(){
    //...
  }
}

The above code looks strange. Rectangle drugs do not have access to achieve its drawCircle (), drawSquare () method, Square Circle class, and class also empathy.

If we add an interface to Ishape, as drawTriangle ():

interface IShape {
  drawCircle();
  drawSquare();
  drawRectangle();
  drawTriangle();
}

All subclasses need to implement this new approach, otherwise it will error.

See also impossible to achieve but can not draw a circle may be square, triangular and rectangular or draw graphics classes. We can only achieve all of the above sub-class method throws an error but indicate incorrect operation can not be performed.

ISP does not advocate the IShape achieve the above. Client (Circle Here, Rectangle, Square, Triangle) should not be forced to rely on a method which does not require, or do not have access. ISP also pointed out an interface only one thing (and similar SRP), the behavior of all other packets should be abstracted to other interfaces.

Here, Ishape interface performs this behavior should be handled independently of other interfaces.

In order to comply with ISP IShape principle, we will separate the behavior to different interfaces to:

interface Ishape {
  draw();
}

interface ICircle {
  drawCircle();
}

interface ISquare {
  drawSquare();
}

interface IRecetangle {
  drawRectangle();
}

interface ITriangle {
  drawTriangle();
}

class Circle implements ICircle {
  drawCircle() {
    //...
  }
}

class Square implements ISquare {
  drawSquare() {
    //...
  }
}

class Rectangle implements IRectangle {
  drawRectangle() {
    //...
  }
}

class Triangle implements ITriangle {
  drawTriangle() {
    //...
  }
}

class CustomShape implements IShape {
  draw() {
    //...
  }
}

ICircle interface simply draw a circular process, IShape any graphics rendering process, ISquare rendering process only square, IRectangle only handles drawing a rectangle.

or

Subclasses can inherit directly from the interface and achieve their Ishape draw () method:

class Circle implements IShape {
  draw() {
    //...
  }
}

class Triangle  implements IShape {
  draw() {
    //...
  }
}

class Square  implements IShape {
  draw() {
    //...
  }
}

class Rectangle  implements IShape {
  draw() {
    //...
  }
}

I- I can now also use the interface to create more special shapes, such as the Semi
Circle, Right-Angled Triangle, Equilateral Triangle, Blunt-Edged of the Rectangle, and so on.

Dependency Inversion Dependency Inverse Principle

Dependency should be on abstractions not concretion
depend on abstractions rather than specific examples

Should modules High-Level A. Not the depend upon Low-Level modules. Both Should the depend upon avstractions.
B. Not Abstractions Should the depend ON deatils. Should the depend upon the Details Abstractions.
A. level modules should not depend on the lower module. They should depend on the abstract.
B. abstract should not depend on the details. Details should depend on the abstract.

This is very important for the development by the application of a number of modules. At this time, we have to use dependency injection (dependency injection) to clarify the relationship between the upper element depends on the underlying element to work.

class XMLHttpService extends XMLHttpRequestService {}

class Http {
  constructor(private xmlhttpService:XMLHttpService ){ }
  
  get(url: string, options: any) {
    this.xmlhttpService.request(url, 'GET');
  }
  
  post() {
    this.xmlhttpService.request(url, 'POST');
  }
  //...
}

Here Http is an upper element and a lower element is HttpService. This design principle contrary to the DIP A: upper module should not depend on the lower module. They should depend on the abstract.

This Http class forced to rely on XMLHttpService class. If we want to change Http connection service, we may even simulate http service by Nodejs. We will move to all instances of pain Http to edit code, which would be contrary to OCP (open closed).

Http Http class should be concerned about reducing the use of the type of service, we have established a Connection interface:

interface Connection {
  request(url: string, opts: any);
}

Connection interface has a request method. We pass a parameter of type Connection through his class to Http:

class Http {
  constructor(private httpConnection: Connection) {}
  
  get(url: string, options: any) {
    this.httpConnection.request(url, 'GET');
  }
  
  post() {
    this.httpConnection.request(url, 'POST');
    //...
  }
}

Now, no matter what type of services delivered over Http connection, Http class can easily connect to the network without concern for the type of network connection.

Now we can reimplement XMLHttpService class that implements the Connection interface:

class XMLHttpService implements Connection {
  const xhr = new XMLHttpRequest();
  //...
  request(url: string, opts: any) {
     xhr.open();
     xhr.send();
  }
}

We can create many types of Http Connection and then passed to Http class but does not raise any errors.

class NodeHttpService implements Connection {
  request(url: string, opts: any){
    //...
  }
}

class MockHttpService implements Connection {
  request(url: string, opts:any) {
    //...
  }
}

Now, you can see the upper module and a lower module relies on the abstract. Http class (upper block) depend on the Connection interface (abstract) and Http service type (the lower module) is also dependent on the Connection interface (abstract).

Epilogue

We discussed the five principles every software developer needs to comply. The beginning of time to comply with these principles may be a little difficult, but through constant practice and perseverance, it will become part of us and have a huge impact on the maintenance of our application.

If you have any questions or have the need to enhance, correct or remove content, although leave a comment below and I'll be happy to discuss with you!

原文:SOLID Principles every Developer Should Know – Bits and Pieces

Guess you like

Origin www.cnblogs.com/stonecutter/p/11028470.html