• 设计模式学习总结:(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.行为型:模式对类或者对象怎么交互和分配职责进行描述。

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

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

     

    如何使用设计模式:

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

  • 相关阅读:
    Node.js 常用工具 util
    jQuery 选择器
    Node.js 创建HTTP服务器
    Node.js GET/POST请求
    JavaScript 用法
    Node.js 事件
    Node.js 函数
    Bootstrap<基础二> 网格系统
    读文章《Flexbox详解》笔记
    好文要读
  • 原文地址:https://www.cnblogs.com/wuweixin/p/5417275.html
Copyright © 2020-2023  润新知