设计模式学习总结:(1)面向对象五大基本原则

最近学习了李建忠老师设计模式教程,感觉有一种豁然开朗的感觉。

"每一个模式描述了在我们周围不断重复发生变化的问题,以及该问题的解决方案的核心。这样,你就能一次又一次的使用该方案而不必重复的劳动"——christopher alexander

当然,设计模式的有本很著名的书,常考教程:

书名:设计模式:可复用面向对象软件的基础

当然,我不是推销书的。主要是书名很明确的给出了,软件设计模式中使用的手法,常用就是面向对象,确实,在我第一次听到面向对象的时候,我的理解是完全不懂,在我的二次学习面向对象的时候,我的理解是封装,继承,多态。而到现在,我觉得,我们的最终目的,还是编写可复用,可扩展,便于维护的软件。当然,如何把面向对象的特性运用到极致,我想还得深入去理解设计模式,不然,很多时候,即使我们用了面向对象语言编程,往往出现适得其反的效果。

这里先给出面向对象五大基本原则:

(DIP)依赖倒置原则:

  1. 高层模块(稳定)不应该依赖于底层模块(变化),二者都应该依赖于抽象(稳定)。

  2. 抽象(稳定)不应该依赖于实现细节(变化),实现细节依赖于抽象(稳定)。

  我的理解:总结为,稳定的东西不应该依赖于变化的东西。在面向对象设计过程中,可能有时候,我会把一些具体实现依赖另外一些具体实现,但是我们知道,具体实现是容易变化的,如果依赖的实现发现变化,意味着上层也将发生变化。这里的变化,不是说具体的逻辑变动,而是接口变动,简单的说就是,面向对象应该面向抽象接口来实现,而不是面向具体的逻辑,这个抽象接口应该是十分稳定的,依赖于一个稳定的接口的好处,就是,我们具体实现和耦合性会比较低。这意味着我们需要很敏感的发现系统的稳定点和变化点,将稳定点变成抽象接口,把变化点变成扩展实现。

我自己想了一个坏栗子,不知道合不合适:

class Waiter //服务生
{


}

class Cater //计算员
{

}

class Guard //保安
{

}
class Bank
{
public:
  vector<Waiter> waiterList;
  vector<Cater> caterList;
  vector<Guard> guardList;
  
protect:
  void doWork();  
}

void Bank::doWord()
{
for(auto a in waiterList)
{
a.doWord();
}
for(auto a in caterList)
{
a.doWord();
}
for(auto a in guardList)
{
a.doWord();
}

作为伪代码,随意看看吧,我想意思很简单,首先有个员工文件,里面我定义了三种员工,然后银行定义了员工的LIst,当然,实际可能还需要添加员工犯法,dowork方法用来让他的员工工作,代码冗余问题虽然还可以解决,但是,最大的问题是,你应该发现,员工种类我们假设是需要扩展的,这种方式,就是具体依赖于具体。你可以假设如果现在多了一种员工,叫做数据库管理员,那么你对于上面的代码应该做那些修改。我想你应该,写一个类,然后,在银行类里面,添加一个数据库管理员list对象,最后,还要在员工在工作里面在添加方法。是不是很不利。

我想,好的方式是这样:

class Staff
{
public:
    void doWork()=0;
    void ~Staff()
    {}
}

class Waiter:public Staff //服务生
{
public:
    void doWork();

}

class Cater:public Staff //计算员
{
    void doWork();
}

class Guard:public Staff //保安
{
    void doWork();
}
class Bank
{
public:
 vector<Staff *> stafflList;
  
protect:
  void doWork();  
}

void Bank::doWord()
{
for(auto a in staffList)
{
   a.doWork();
}

 你现在可以想象下,如果这种情况下,老板让你添加一种新的员工,叫做数据库,管理员,那么是不是方便,你只需要,在写一个类,然后继承抽象基类staff即可。这也就是面向抽象基础编程。

开放封闭原则(OCP)

  1. 对扩展开放,对更改封闭。

  2. 类模块应该可扩展的,但是不可更改。

我的理解:开放封闭原则,是最为重要的设计原则。而后面的Liskov替换原则和合成/聚合复用原则为开放封闭原则的实现提供保证。

我这里无耻的引用百度百科的例子:

class BusyBankStaff
{
    private BankProcess bankProc = new BankProcess();
// 定义银行员工的业务操作
    public void HandleProcess(Client client)
    {
        switch (client.ClientType)
        {
            case "存款用户":
                bankProc.Deposit();
               break;
             case "转账用户":
                bankProc.Transfer();
               break;
             case "取款户":
                bankProc.DrawMoney();
                break;
        }
    }
}

其实这代码跟第一个原则差不多,对扩展开发怎么理解,这段代码问题主要在于,如果有新的业务出现,那么扩展,将在switch中进行修改,实际上违背了对修改封闭的原则,也就是说,如果出现新的业务,我们需要的是扩展,而不是修改之前的类。那么好的实现方法可以是这样的:

class IBankProcess
{
    virtual void Process()=0;
}

<-接口基类

class DepositProcess : IBankProcess
{
//IBankProcess Members

    public void :
    Process()
    {
    // 办理存款业务
    }
}
class TransferProcess : IBankProcess
{
//IBankProcess Members

    public :
    void Process()
    {
    // 办理转账业务

    }
}
class DrawMoneyProcess : IBankProcess
{
//IBankProcess Members

    public :

    void Process()
    {
    // 办理取款业务 
    }
}

<-不同业务

class EasyBankStaff
{
    private :
        IBankProcess *bankProc = NULL;
    public :
    void HandleProcess(Client client)
    {
         bankProc = client.CreateProcess();
         bankProc.Process();
    }
}

<-用户切换

class BankProcess
{
    public:
    void Main()
    {
        EasyBankStaff bankStaff = new EasyBankStaff();
        bankStaff.HandleProcess(new Client("转账用户"));
    }
}

处理业务,就这个方法而言,对于银行业务扩展,我们只需要定义新的类,这叫对扩展开放,而不需要修改原先类的代码,这叫对修改封闭。这样一个原则,是不是很像一个衡量标准呢。

单一职责原则(SRP)

  1. 一个类应该仅有一个引起它变化的原因。

  2. 变化的方向隐含着类的责任。

这是相对比较好理解的吧,"如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。此原则的核心就是解耦和增强内聚性。"

一个类有且只有一个可以改变的理由。

liskov替换原则(LSP)

  1. 子类必须能够替换它们的基类(IS-A)。

  2. 继承表达类型抽象。

接口隔离原则(ISP)

  1. 不可以强迫客户程序依赖他们不用的方法。

  2. 接口应该小而完备。

几个额外的建议:

组合复用原则(carp):

优先使用对象组合,而不是类继承

  1. 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。

  2. 继承在某种程度上破坏了封装性,之类父类耦合度高。

  3. 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低

封闭变化点

    使用封装来创建对象之间的分界层。让设计者可以在变化一侧进行修改,而不会影响另一侧产生不良影响,从而实现松耦合。

针对接口编程

   针对接口编程,而不是针对实现编程

    1.不将变量类型声明为某个特定的具体类,而是声明为某个接口

    2.客户程序不需要获知对象的具体类型,只需要知道对象所具有的接口。

    3.减少系统中各部分的依赖关系,从而实现“高内聚,松耦合”的设计类型。

对设计模式按照目的分类可以分为以下三种:

1.创建型:与对象创建有关。

2.结构型:处理类或对象的组合。

3.行为型:模式对类或者对象怎么交互和分配职责进行描述。

按照其作用范围又可分为类模式对象模式 ,前者主要处理类和子类的关系,通过继承建立,具有静态,在编译时刻就稳定下来。后者处理对象之间的关系,这些关系可能是运行时刻变化的,具有动态性。

具体模式可以参考书上的表格:

 

如何使用设计模式:

我想大概是要达到对症下药的层次吧,在此之前,我们更应该关注原则。

原文地址:https://www.cnblogs.com/wuweixin/p/5417275.html