2
头图

Design patterns, namely Design Patterns, refer to a type of code design experience that is used repeatedly in software design. The purpose of using design patterns is to reusable code and improve the scalability and maintainability of the code. The term design pattern was summarized and refined by Erich Gamma, Richard Helm, Raplh Johnson and Jonhn Vlissides in the 1990s, and wrote a book on Design Patterns.
Project address: https://gitee.com/baichen9187/design-pattern

1. The principle of opening and closing

Definition of opening and closing principle

The opening and closing principle is the most basic design principle, it guides us how to build a stable and flexible system. The principle of opening and closing is defined as follows:

Software entities like classes,modules and functions should be open
for extension but closed for modifications.

One of the five principles in OOP theory (Solid, single responsibility principle, opening and closing principle, Richter replacement, interface isolation, dependency inversion)

A software entity such as class, module and function should be open for extension and closed for modification.

What is the principle of opening and closing

The principle of opening and closing clearly tells us: entity implementation should be open to extension and closed to modification. The meaning is that an entity should achieve change through extension, rather than modify existing code to achieve change. So what is an entity? The software entity includes the following parts:

Modules divided according to certain logical rules in the project or software product
Abstraction and class
method

The role of the principle of opening and closing

The principle of opening and closing is the ultimate goal of object-oriented programming. It enables the software entity to have a certain degree of adaptability and flexibility while possessing stability and continuity. Specifically, its role is as follows.

  1. Impact on software testing If the software complies with the open and closed principle, only the extended code needs to be tested during software testing, because the original test code can still run normally.
  2. Can improve the reusability of the code. The smaller the granularity, the greater the possibility of being reused; in object-oriented programming, the reusability of the code can be improved based on atomic and abstract programming.
  3. It can improve the maintainability of the software. The software that complies with the opening and closing principle has high stability and strong continuity, so it is easy to expand and maintain.

Advantages of the principle of opening and closing

  • Improve code reusability
  • Improve maintainability
  • Increase flexibility
  • Easy to test
#include <iostream>

class Number {
protected:
    double pi = 3.1415926;
public:
    virtual double getCircularArea(int d) = 0;
    virtual double getRectangularArea(int a, int b) = 0;

};

class NumberArea : public Number {
public:
    double getCircularArea(int r) {
        return ((double)r * r) * this->pi;
    }
    double getRectangularArea(int a, int b) {
        return (double)a * b;
    }
};


int main()
{   
    NumberArea num;
    double cricular = num.getCircularArea(1);
    std::cout << cricular << std::endl;
   
}

Second, the single responsibility principle

Single responsibility definition

Single responsibility principle (SRP: Single responsibility principle), also known as single function principle, is one of the five basic object-oriented principles (SOLID). It stipulates that a class should have only one reason for the change. This principle was given by Robert C. Martin in the book "Agile Software Development: Principles, Patterns and Practices". Martin stated that this principle was developed based on the cohesion principle in the works of Tom DeMarco and Meilir Page-Jones.

The so-called responsibility refers to the reason for class changes. If a class has more than one motivation to be changed, then this class has more than one responsibility. The single responsibility principle means that a class or module should have one and only one reason for change.

The role of single responsibility

As far as a class is concerned, there should be only one reason for its change. There should be only one responsibility.
Each responsibility is an axis of change. If a class has more than one responsibility, these responsibilities are coupled together. This can lead to fragile designs. When one responsibility changes, it may affect other responsibilities. In addition, multiple responsibilities are coupled together, which will affect reusability. For example: To achieve the separation of logic and interface.

Advantages of single responsibility

The advantages of the single responsibility principle are as follows:

  • Reduce the complexity of the class;
  • Improve the readability of the class;
  • Improve the maintainability and reusability of the code;
  • Reduce the risks caused by changes.
#pragma once
#include<string>

using namespace std;
class ProductFactory {
public:
    virtual string product(string material) = 0;
};

class ConsumeFactory {

public:
    virtual void consume(string product) = 0;
};
#include<iostream>
#include"./factroy.h"

using namespace std;
class ProductFactoryimpl :ProductFactory{
public:
    string product(string material) {
        return material  ;
    }
};
class ConsumeFactoryimpl : ConsumeFactory {
public :
    void consume(string product) {
        cout << "消费了" << product << endl;
    }
};


int main() {

    ProductFactoryimpl p;
    string data = p.product("goods");
    ConsumeFactoryimpl c;
    c.consume(data);


    return 0;
}

Violation of the Single Responsibility Principle

One interface implements multiple behavior methods

#pragma once
#include<string>

using namespace std;
class ConsumeFactory {
public:
    virtual string product(string material) = 0;
     virtual void consume(string product) = 0;
};

class ConsumeFactory :ProductFactory{
public:
    string product(string material) {
        return material  ;
    }
     void consume(string product) {
        cout << "消费了" << product << endl;
    }
};


int main() {

    ConsumeFactoryimpl c;
    string data = c.product("goods");
    
    c.consume(data);


    return 0;
}

Third, rely on the principle of inversion

Definition of dependency inversion

The inversion in the dependency inversion principle refers to the completely opposite way of thinking in general OO design.
In process-oriented development, the upper layer calls the lower layer, and the upper layer depends on the lower layer. When the lower layer changes drastically, the upper layer will also change, which will reduce the reusability of modules and greatly increase the cost of development.
The core idea of the dependency inversion principle is interface-oriented programming , and the dependency inversion principle is the basic principle behind the component design models such as JavaBean, EJB and COM.

High-level modules should not rely on low-level modules, they should rely on abstraction.
Abstraction should not depend on details; details should depend on abstractions.
Even if the implementation details are constantly changing, as long as the abstraction remains the same, the client program does not need to change. This greatly reduces the degree of coupling between the client program and the implementation details.

image.png

Dependence on the role of the principle of inversion

The principle of dependency inversion can effectively reduce the coupling between classes, improve system stability without reducing risks in parallel development, and improve code maintainability and readability.
The essence of the dependency inversion principle is to make each class or module independent of each other through abstraction (interface), without affecting each other, and loose coupling between the practice modules
image.png

The dependencies between modules occur through abstraction, and there is no direct dependency between the implementation classes, and the dependencies are generated through interfaces or abstract classes;

  1. The interface or abstract class does not depend on the implementation class;
  2. The implementation class depends on the interface or abstract class.
  3. Try to use polymorphism
  4. No class should be derived from a concrete class
  5. Try not to override base class methods

    case analysis

    The living things in it are dependent on plants and animals
    Scenario problem: Class A directly depends on class B. If you want to change class A to depend on class C, you must modify the code of class A to achieve this. In this scenario, class A is generally a high-level module responsible for complex business logic; class B and class C are low-level modules responsible for basic atomic operations; if class A is modified, it will bring unnecessary risks to the program.
    image.png

#pragma once
#include<string>
class AnimalInterface
{
public:
    virtual std::string get() = 0; //食物
};
#include".\Factroy.h"
#include<string>
using std::string;

class AnimalImpl_1 : public AnimalInterface {

public:

    string get() {
        return "草";
    }
};

class AnimalImpl_2 :public AnimalInterface {

public:

    string get() {
        return "水";
    }
};
#include<iostream>
#include<string>

#include"abstraction.cpp"

using namespace std;

class pasture1 {

public:

    void get(AnimalImpl_1 a) {
        vector<string> list{ "牛" ,"羊" ,"猪" ,"马" };
        for (auto i : list) {
            cout  << i << "被喂食了 " << a.get() << endl;
        }        
    }
};

class pasture2 {
public:
    void get(AnimalImpl_2 a) {
        vector<string> list{ "牛" ,"羊" ,"猪" ,"马" };
        for (auto i : list) {
            cout << i << "被喂食了 " << a.get() << endl;
        }
    
    }
};


int main() {


    pasture1 p;
    p.get(AnimalImpl_1());
    pasture2 p2;
    p2.get(AnimalImpl_2());
    return 0;
}

If I want to add some animals or do some screening, do I need to modify the code?

We modify the code in accordance with the principle, the original abstraction layer remains unchanged, and the high-level code is modified

#pragma once
#include<string>
class AnimalInterface
{
public:
    virtual std::string get() = 0; //食物
};



#include".\Factroy.h"
#include<string>
using std::string;

class AnimalImpl_1 : public AnimalInterface {

public:

    string get() {
        return "草";
    }
};

class AnimalImpl_2 :public AnimalInterface {

public:

    string get() {
        return "水";
    }
};


class pasture
{
public:
    void get(AnimalInterface *a){
        vector<string> list{ "牛" ,"羊" ,"猪" ,"马" };
        for (auto i : list) {
            cout << i << "被喂食了 " << a->get() << endl;
        }
            
    }

};


int main() {


    pasture p;
    AnimalInterface* ai = new AnimalImpl_1();
    AnimalInterface* ai2 = new AnimalImpl_2();
    p.get(ai);
    p.get(ai2);
    return 0;
}

Fourth, the principle of Richter substitution

The focus of the Richter substitution principle is not to affect the original function, rather than not to cover the original method

The Liskov substitution principle is translated from the Liskov substitution principle. Liskov is a computer scientist, that is, Barbara Liskov, a professor at the Massachusetts Institute of Technology, and the first female PhD in computer science in the United States. He studied under the Turing Award winner Professor John McCarthy, who proposed the concept of artificial intelligence.

Definition of Richter's Substitution Principle

The Liskov substitution principle was described in a paper published by Barbara Liskov and Jeannette Wing in 1994:

If S is a declared subtype of T, objects of type S should behave as objects of type T are expected to behave, if they are treated as objects of type T

If S is a subtype of T, for any object of type S, if they are regarded as objects of type T, the behavior of the object should be consistent with the expected behavior.

The origin of the problem: There is a function P1, which is completed by class A. The function P1 needs to be expanded, and the expanded function is P, where P is composed of the original function P1 and the new function P2. The new function P is completed by the subclass B of the class A, and the subclass B may cause the failure of the original function P1 while completing the new function P2.
Simply put, when class B inherits A, in addition to the new method, try not to rewrite the method of the parent class, otherwise the original function will be changed

Mandatory of the Richter's Substitution Principle

  • Subclasses can implement the abstract methods of the parent class, but cannot override the non-abstract methods of the parent class.
    You can add your own unique methods to subclasses.
  • When the method of the subclass overloads the method of the parent class, the preconditions of the method (that is, the formal parameters of the method) are more relaxed than the input parameters of the parent method.
  • When the method of the subclass implements the abstract method of the parent class, the post-condition of the method (that is, the return value of the method) is stricter than that of the parent class.

The solution is polymorphic: new unique method

The advantages and disadvantages of the Richter substitution principle

advantage:

  • Code contribution, reduce creation work
  • Improve code reusability
  • Improve code scalability
  • Improve project openness

The corresponding disadvantages also include:

  • Inheritance is invasive.
  • Reduce the flexibility of the code.
  • Enhanced coupling.

    Violation of Richter's Substitution Principle

class Math {
public:
    virtual int sum(int a, int b) = 0;

    virtual float halve(int a) = 0;
};
#include"Factroy.h"
#include<iostream>
using namespace std;
class math :Math {
public:
    int sum(int a, int b) {
        return a + b;
    }
    float halve(int num) {
        return num >> 1;
    }
};

class math2 :math {
private:
    int add  = 100;

public:
    int sum(int a, int b) {
        return a + b + add;
    }
    float halve(int num) {
        return num >> 1;
    }
};

int main() {
    math m;
    int sum = m.sum(2, 2);
    float halve = m.halve(sum);
    cout << "sum(2,2)" << sum << endl;
    cout << "halve(4)" << halve << endl;

    math2 m2;
    int sum2 = m2.sum(2, 2);
    float halve2 = m2.halve(sum2);
    cout << "sum(2,2)" << sum2 << endl;
    cout << "halve(4)" << halve2 << endl;


    return 0;
 }

case analysis

class Math {

public:
    virtual int sum(int a, int b) = 0;

    virtual float halve(int a) = 0;
};
#include"Factroy.h"
#include<iostream>
using namespace std;
class math :Math {
public:
    int sum(int a, int b) {
        return a + b;
    }
    float halve(int num) {
        return num >> 1;
    }
};

class math3 : public math {
private:
    int add = 100;
public:
    int sum(int a, int b) {
        return a + b + add;
    }
    float halve(int num) {
        return num >> 1;
    }
};

int main() {
    math* math;
    math3 m3;
    math = &m3;
    int sum3 = math->sum(2, 2);
    float halve3 = math->halve(sum2);
    cout << "sum(2,2)" << sum3 << endl;
    cout << "halve(4)" << halve3 << endl;
    
    return 0;
}

Five, the principle of interface isolation

Definition of interface isolation principle

The interface isolation principle is a principle that restricts the use of interfaces, and the specific meaning is as follows

  1. The client should not rely on interfaces that it does not need. One interface corresponds to one role, and different roles should not be assigned to the same interface. This will form a huge interface, which also leads to interface pollution.
  2. Should not force interfaces to depend on interfaces they don’t need
  3. The dependencies between classes should be established on the smallest interface

Advantages of the interface isolation principle:

  1. Avoid interface contamination and prevent unforeseen risks
  2. Provide flexibility and maintainability of the logic inside the class
  3. Provides the cohesion of the system and reduces the degree of coupling
  4. Reduce code redundancy

Code

image.png

#pragma once
#include<string>
#include<vector>
using std::string;
using std::vector;

/*封装了vector*/
template<class T> class Container :public  vector<T> {
};

template<class T> class Crud {
public:
    virtual void insert(T t) = 0;
    virtual T get( int index) = 0;
    virtual void delete_(int index) = 0;
    virtual int size() = 0;
};

//应用
template<class T, class V> class Apply {
public:
    virtual V apply(Crud<T>* c) = 0;
};
#include <iostream>
#include"Interface.h"
using std::cout;
using std::endl;

class Applylmp : public Apply<string,string>, public Crud<string>, public Container<string> {
private:
    Container<string> list;

public:
    void insert(string s) {
        list.insert(list.end(),s);
        cout << "插入了" << s << endl;
    }
    string get(int index) {
        return list.operator[](index);
    }
    void delete_(int index) {
        list.operator[](index) = "";
    }
    int size() {
        return list.size();
    }

    string apply() {
        string s;
        for (int i = 0; i < this->size(); i++) {
            s.append(this->get(i));
        }
        return s;
    }
};

int main()
{
    Applylmp apply;
    string  s[] = { "a","b","c" };
    for (int i = 0; i < (sizeof(s)/ sizeof(string)); i++) {
        apply.insert(s[i]);
    }
    string data =  apply.apply();
    cout << data;
    return 0;
  
}

Sixth, Dimit's Principle (Minimal Knowledge Principle)

Definition of the Dimit principle

The Law of Demeter (LOD), also known as the "Least Knowledge Principle".

Dimit’s rule was originally used as a rule of object-oriented system design style. It was developed by Ian in 1987.
Holland proposed for a project called Dimit at Northeastern University, so it is called Dimit's Law. This rule is actually the guiding design principle of many well-known systems, such as the Mars landing software system and the software system of Jupiter's Europa satellite orbiting spacecraft.

It is defined as: A software entity should interact with other entities as little as possible . In this way, when a module is modified, other modules will be affected as little as possible, and expansion will be relatively easy. Dimit's law is a restriction on the communication between software entities. It sets requirements on the width and depth of communication between software entities. Dimit’s other expressions are:
Only communicate with your direct friends.
Don't talk to "strangers".
Each software unit has minimal knowledge of other units, and is limited to those software units that are closely related to the unit.
The conditions for "friends" are:
current object itself (this);
is the object passed in as a parameter of the current object's method;
Any object created or instantiated by the method of the current object;
Any component of the current object (any object referenced by the instance variable of the current object) .

Advantages of the Dimit principle

  • Reduce the relationship between classes
  • Improve the weak coupling of the code
  • Improved code reusability

Code

在这里插入图片描述

#include <iostream>
class Stranger {

public:
    void call() {
        std::cout << "我是陌生人" << std::endl;
    }
};


class Friend {
private:
    Stranger* stranger;
public:
    void forward() {
        std::cout << "我给你介绍一下" << std::endl;
        stranger->call();
    }
    void call() {
        std::cout << "我是朋友" << std::endl;
    }

};


class Someone {

public:
    void call(Friend* f) {
        std::cout << "你好!" << std::endl;
        f->forward();
        
    }

};

int main()
{
    Someone someone;
    someone.call(new Friend());
}

用户bPcVmzR
1 声望4 粉丝

下一篇 »
函数式编程