Six principles of design patterns (C ++ description)

Foreword

In recent learning some basic design patterns, design patterns learned that there are six principles, I think the idea is still very important in coding, so wrote a blog to summarize. After have the opportunity to write down some design patterns blog ( Gugu Gu ........

Six principles of design patterns

1. Single Responsibility Principle
2. Open - Closed Principle
3. Dependency Inversion Principle
4. Richter Substitution Principle (LSP)
5. Interface Segregation Principle
6. Dmitry principle (the principle of the least known)

1. Single Responsibility Principle

Accurate analysis: On a category, it should only be one causes of change

When a change in duties will not result in a change to another class class responsibilities.

Advantages: class complexity can be reduced, to improve readability

2. Open - Closed Principle

Accurate analysis: software entities (classes, templates, functions, etc.) should be extended, but can not be modified

Opening and closing principle is at the heart of object-oriented design; staff should only open the program showed frequent changes in
those parts to make the abstract.

3. Dependency Inversion Principle

Accurately parse:. A high-level template (stable) should not rely on the underlying template (change) should depend on two abstract (stable).
B. abstract (stable) should not rely on implementation details (change). Detail (change) should not rely on abstract (stable).

Or whether changes should rely on stable stable

To put it plainly: to face the interface programming, not to implement programming.

#include<iostream>
class Book
{
    public:void look()
    {
        ....
    }
    .....
}
class Man
{
   puclic:void Action(Book book)
   {
      book.look();
   }
   ....
}

int main()
{
    Man man=new Man();
    Book book=new book();
    Man->Action(book);
    ....
}

Shown above is the behavior of people reading

So I want people assuming that existing behavior were watching a video, a video class code is as follows:

class Video
{
    public:void Video()
    {
        ....
    }
    .....
}

So I not only want to modify this class of people, as well as modifications to the code of the main function; if there is a lot of need, then this modification process will be very painful, because the coupling of the book and the people too.

Next, use the Dependency Inversion principle to solve the current pain, can reduce the coupling of books and people

Books and videos we can look at things as a ILOOK as an interface class, and books and video inherit this class

class ILOOK
{
    public:virtual void look()=0; 
}

class Bookpublic ILOOk
{
    public:void look()
    {
        ....
    }
    .....
}

class Video:public ILOOk
{
    public:void look()
    {
        ....
    }
    .....
}
class Man
{
   puclic:void Action(ILOOK ilook)
   {
      ilook.look();
   }
   ....
}

int main()
{
    Man man=new Man();
    ILOOK ilook=new book();
    Man->Action(ilook);
    ILOOK ilook2=new video();
    Man->Action(ilook2);
    ....
}

This realization of a simple dependency inversion, people rely on ILOOK this class, and books and videos are also dependent on ILook (ie, the top and bottom should rely on abstract

This is a simple programming interface to the face.

The Dependency Inversion principle will run through all design patterns, which is why this principle must have a clear understanding

4. Richter Substitution Principle (LSP)

Accurate analysis: subtype must be able to replace their parent type
That white is another expression of one of the IS-A

For example: birds are a parent, there are fly () This virtual function, Swallow is a bird, because it can fly, so it can be inherited bird;

企鹅不能飞,所以它不能继承鸟类,即使他在生物学上是鸟类,但它在编程世界中不能够继承鸟类

这里说出LSP的一个特点:只有当子类可以替换掉父类,软件单位的功能不受影响时,父类才能够被复用,而子类也能够在父类的基础上增加新的行为

通俗来说:子类可以扩展父类的功能,但不能改变父类原来的功能。

包括4层含义:1.子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2.子类中可以增加自己特有的方法。
3.当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

4种含义不展开讲,但用下面的一个例子来简单说明

#include<iostream>

class A
{
public:
    int fun1(int a, int b) {
        return a - b;
    }
};

class B :public A
{
public:
    int fun1(int a, int b) {
        return a + b;
    }
    int fun2(int a, int b)
    {
         return fun1(a, b)-100;   //想要a-b-100,但现实是a+b-100
    }
};
int main()
{
    int a = 100, b = 20;
    B* m_b=new B();
    std::cout << m_b->fun2(a, b) << std::endl;
}

上面显示的结果会是20,因为B类中的fun1()覆盖到了A类中的fun1();所以fun2()中调用的是B类的fun1(),这便违反了里氏替换原则

不遵循里氏替换原则的后果是:出问题的概率会大大提高

5.接口隔离原则

准确解释:不应该强迫客户程序依赖他们不用的方法;接口应该小而完备

class I
{
    public:
    void method1()=0;
    void method2()=0;
    void method3()=0;
    void method4()=0;
    void method5()=0;
}

class A
{
    public:
    void depend1(I i)
    {
        i.method1();
    }
     void depend2(I i)
    {
        i.method2();
    }
     void depend3(I i)
    {
        i.method3();
    }
}
class B:public I
{
    public:
    void method1()
    {
        std::cout<<"B实现方法1"<<std::endl;
    }
    void method2()
    {
        std::cout<<"B实现方法2"<<std::endl;
    }
    void method3()
    {
        std::cout<<"B实现方法3"<<std::endl;
    }
    //B类种方法4和5不是必须的
    //但方法4和5因为继承的原因仍让需要空实现
    void method4(){}
    void method5(){}
}

class C
{
    public:
    void depend1(I i)
    {
        i.method1();
    }
     void depend2(I i)
    {
        i.method4();
    }
     void depend3(I i)
    {
        i.method5();
    }
}
class D:public I
{
    public:
    void method1()
    {
        std::cout<<"B实现方法1"<<std::endl;
    }
    void method4()
    {
        std::cout<<"B实现方法4"<<std::endl;
    }
    void method5()
    {
        std::cout<<"B实现方法4"<<std::endl;
    }
    //B类种方法2和3不是必须的
    //但方法2和3因为继承的原因仍让需要空实现
    void method2(){}
    void method3(){}
}
上面便没有使用接口隔离原则
下面便使用了接口隔离,所以一些无关的方法就可以不用去实现

class I1
{
    public:
    void method1()=0;
}
class I2
{
    public:
    void method2()=0;
    void method3()=0;
}
class I3
{
    public:
    void method4()=0;
    void method5()=0;
}
class A
{
    public:
    void depend1(I1 i)
    {
        i1.method1();
    }
     void depend2(I2 i)
    {
        i2.method2();
    }
     void depend3(I2 i)
    {
        i2.method3();
    }
}
class B:public I1,public I2
{
    public:
    void method1()
    {
        std::cout<<"B实现I1方法1"<<std::endl;
    }
    void method2()
    {
        std::cout<<"B实现I2方法2"<<std::endl;
    }
    void method3()
    {
        std::cout<<"B实现I2方法3"<<std::endl;
    }
}

class C
{
    public:
    void depend1(I1 i)
    {
        i1.method1();
    }
     void depend2(I2 i)
    {
        i3.method4();
    }
     void depend3(I2 i)
    {
        i3.method5();
    }
}
class D:public I1,public I3
{
    public:
    void method1()
    {
        std::cout<<"B实现I1方法1"<<std::endl;
    }
    void method4()
    {
        std::cout<<"B实现I3方法4"<<std::endl;
    }
    void method5()
    {
        std::cout<<"B实现I3方法4"<<std::endl;
    }
    
}
使用接口隔离原则时应注意:
1.接口尽量小,但是要有限度。如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
2.为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。
3.提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
这个原则可以在实践多花时间思考,才可以准确地使用它

6.迪米特原则(最少知道原则)

准确解释:一个对象应该对其他对象保持最少的了解

因为类之间的关系最紧密,耦合度越高,一个类变化时对另一个类的影响也大

我们使用迪米特原则就是要降低类之间的耦合度

C++中一个重要的特性:高内聚,低耦合.高内聚,低耦合.高内聚,低耦合.(重要的事情说三遍)
#include<iostream>
#include<list>
#include<string>

class Employee
{
private:
    std::string m_id;
public:
    Employee(){}
    Employee(std::string id) :m_id(id) {}
    std::string get_id()
    {
        return m_id;
    }
};
class SubEmployee
{
private:
    std::string m_id;
public:
    SubEmployee() {

    }
    SubEmployee(std::string id) :m_id(id) {}
    std::string get_id()
    {
        return m_id;
    }
};
class SubCompanyManager
{
public:
    std::list<SubEmployee> getAllEmployee()
    {
        std::list<SubEmployee> list(100);
        for (int i = 0; i < 100; i++)
        {
            SubEmployee emp("分公司" + std::to_string(i));
            list.push_back(emp);
        }
        return list;
    }
};
class CompanyManager
{
public:
    std::list<Employee> getAllEmployee()
    {
        std::list<Employee> list(30);
        for (int i = 0; i < 30; i++)
        {
            Employee emp("总公司"+std::to_string(i));
            list.push_back(emp);
        }
        return list;
    }
    void printALLEmployee(SubCompanyManager sub)
    {
        std::list<SubEmployee> list1(100);
        list1 = sub.getAllEmployee();
        std::list<SubEmployee>::iterator itor= list1.begin();
        for (; itor != list1.end(); itor++)
        {
            std::cout << itor->get_id();
        }
        std::list<Employee> list2(30);
        list2= getAllEmployee();
        std::list<Employee>::iterator itor2 = list2.begin();
        for (; itor2 != list2.end(); itor2++)
        {
            std::cout << itor2->get_id();
        }
    }
};

int main()
{
    CompanyManager* e = new CompanyManager();
    SubCompanyManager s;
    e->printALLEmployee(s);
    system("pause");
    return 0;
}
上面的代码违反了迪米特原则

根据迪米特法则,只与直接的朋友发生通信,而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。

class SubCompanyManager
{
public:
    std::list<SubEmployee> getAllEmployee()
    {
        std::list<SubEmployee> list(100);
        for (int i = 0; i < 100; i++)
        {
            SubEmployee emp("分公司" + std::to_string(i));
            list.push_back(emp);
        }
        return list;
    }
    void printALLEmployee()
    {
        std::list<SubEmployee> list = getAllEmployee();
        std::list<SubEmployee>::iterator itor = list.begin();
        for (; itor != list.end(); itor++)
        {
            std::cout << itor->get_id();
        }
    }
};
class CompanyManager
{
public:
    std::list<Employee> getAllEmployee()
    {
        std::list<Employee> list(30);
        for (int i = 0; i < 30; i++)
        {
            Employee emp("总公司" + std::to_string(i));
            list.push_back(emp);
        }
        return list;
    }
    void printALLEmployee(SubCompanyManager sub)
    {
        sub.printALLEmployee();
        std::list<Employee> list2(30);
        list2 = getAllEmployee();
        std::list<Employee>::iterator itor2 = list2.begin();
        for (; itor2 != list2.end(); itor2++)
        {
            std::cout << itor2->get_id();
        }
    }
};

为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

另外切记不要过分使用迪米特原则,否则会产生大量的这样的中介和传递类,增加系统的复杂性

六点原则总结

单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
我们在实践时应该根据实际情况灵活使运用,才能达到良好的设计

参考博客:
http://www.uml.org.cn/sjms/201211023.asp#2

参考视频:
https://www.bilibili.com/video/av22292899

参考书籍:< <大话设计模式> >

Guess you like

Origin www.cnblogs.com/Ligo-Z/p/11161911.html