设计模式(二)--六大设计原则

1.书本链接:前言 · 设计模式之禅(第2版) · 看云

本文引用博客:设计模式六大原则(1):单一职责原则_割韭韭的博客-CSDN博客_单一职责原则

2.第一部分:六大设计原则

  1. 单一职责原则,SRP

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

// 命令模式:顾客,服务员,厨师:https://www.cnblogs.com/chenqionghe/p/4749915.htm
动物呼吸:https://blog.csdn.net/yabay2208/article/details/7380047

// 创建对象
control = new cookControl;    // 顾客找服务员
cook = new cook;              // 服务员准备好,厨师
// 确定对象直接的关系,聚合,组合,依赖
mealcommand = new MealCommand($cook);    // 菜单准备
drinkcommand = new DrinkCommand($cook);  // 菜单准备
control->addCommand($mealcommand,$drinkcommand);    // 加入菜单
// 执行动作
control->callmeal();    // 顾客点餐
control->calldrink();


 

// iphone是包含了协议管理(dial, handup)和数据传输(chat)
// 两个变化都会触发变化
class IPhone
{
public:
    virtual void dial(string number) = 0;
    virtual void chat(object o) = 0;
    virtual void handup() = 0;
};

class IPhone
{
public:
    IManger m;
    IDataTransfer d;
};

    2.里氏替换原则

定义:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。2.所有引用基类的地方必须能透明地使用其子类的对象。
问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替
继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
做法: 里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    class Father
    {
    private:
        /* data */
    public:
        collecation cal(Hashmap map)
        {
            return map.values();
        }
    };
    
    class son : public Father
    {
    private:
        /* data */
    public:
        // 子类缩小父类的参数,不可以
        // collecation cal(HashmapSon map)
        // 子类放大父类的参数,可以
        collecation cal(map map)
        {
            return map.values();
        }
    };
    
    
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
    class Father
    {
    private:
        /* data */
    public:
        long cal(Hashmap map)
        {
            return map.values();
        }
    };
    
    class son : public Father
    {
    private:
        /* data */
    public:
        // return int < long
        int cal(map map)
        {
            return map.values();
        }
    };
    

例子:加减扩展的例子

    3.依赖倒置原则

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率
做法:依赖倒置原则的核心思想是面向接口编程

  • 低层模块尽量都要有抽象类或接口,或者两者都有。
  • 变量的声明类型尽量是抽象类或接口。
  • 使用继承时遵循里氏替换原则。

例子:妈妈读报纸/童话,开宝马/奔驰
依赖的三种写法:构造函数传递依赖对象、设置Setter方法声明依赖关系、接口的方法中声明依赖对象,如
private ICar car; public Driver(ICar _car){this.car = _car;}
private ICar car; public Driver(ICar _car){ this.car = _car;}
public void drive(ICar car){car.run();}
1.函数的命名不能暴露实现细节:uploadToAliyun->upload
2.封装具体的细节:aliyun特有的token.
3.抽象公共的接口。

    4.接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
做法: 

  • 接口尽量小,是“小”是有限度的,首先就是不能违反单一职责原则,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

例子:不要定义一个通用的接口,美女何其多,观点各不同,图书查询类图特殊权限

class IPettyGril
{
public:
    virtual void goodLooking() = 0;
    virtual void niceFigure() = 0;
    virtual void greatTemperatment() = 0;
};

class PettyGirl : public IPettyGril
{
private:
    /* data */
public:
    virtual void goodLooking()
    {
    }
    virtual void niceFigure()
    {
    }
    virtual void greatTemperatment()
    {
    }
};

class AbSearcher
{
private:
    IPettyGril mGirl;
public:
    AbSearcher(IPettyGril girl)
    {
        mGirl = girl;
    }
protected:
    virtual void show();
};


class Searcher : public AbSearcher
{
private:
    IPettyGril mGirl;

public:
    Searcher(IPettyGril girl)
        : AbSearcher(girl)
    {
        mGirl = girl;
    }
protected:
    virtual void show()
    {
        mGirl.goodLooking();
        mGirl.niceFigure();
        mGirl.greatTemperatment();
    }
};

int main ()
{
    IPettyGril girl = new PettyGirl();
    AbSearcher searcher = new Searcher(girl);
    searcher.show();
    return 0;
}

class IPettyGrilgoodLooking
{
public:
    virtual void goodLooking() = 0;
};

class IPettyGrilniceFigure
{
public:
    virtual void niceFigure() = 0;
};

class IPettyGrilgreatTemperatment
{
public:
    virtual void greatTemperatment() = 0;
};

class PettyGirl : public IPettyGrilgoodLooking, IPettyGrilniceFigure, IPettyGrilgreatTemperatment
{
private:
    /* data */
public:
    virtual void goodLooking()
    {
    }
    virtual void niceFigure()
    {
    }
    virtual void greatTemperatment()
    {
    }
};

class AbSearcher
{
private:
public:
    AbSearcher()
    {
    }
protected:
    virtual void show();
};


class Searcher : public AbSearcher
{
private:
    IPettyGrilgoodLooking mgoodLookingGirl;
    IPettyGrilniceFigure mNiceGirl;
    IPettyGrilgreatTemperatment mTempGril;

public:
    Searcher(IPettyGrilgoodLooking girl, IPettyGrilniceFigure mNiceGirl,
             IPettyGrilgreatTemperatment mTempGril)
    {
        mGirl = girl;
        mNiceGirl = mNiceGirl;
        mTempGril = mTempGril;
    }
protected:
    virtual void show()
    {
        mgoodLookingGirl.goodLooking();
        mNiceGirl.niceFigure();
        mTempGril.greatTemperatment();
    }
};

int main ()
{
    IPettyGril girl = new PettyGirl();
    AbSearcher searcher = new Searcher(girl);
    searcher.show();
    return 0;
}

class IPettyGrilMind
{
public:
    virtual void mind() = 0;
};

class Pretty3 : public IPettyGrilMind
{
private:
    /* data */
public:
    virtual void mind()
    {
    }
};

int main ()
{
    IPettyGril girl = new PettyGirl();
    AbSearcher searcher = new Searcher(girl);
    searcher.show();

    IPettyGril girl = new Pretty3();
    AbSearcher searcher = new Searcher(girl);
    searcher.show();
    return 0;
}

    5.迪米特法则

定义:一个对象应该对其他对象保持最少的了解。最少知道原则
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方案:尽量降低类与类之间的耦合。 虽然可以避免与非直接的类通信,但是要通信,必然会通过一个“中介”来发生联系,例如本例中,总公司就是通过分公司这个“中介”来与分公司的员工发生联系的。过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大
做法

  • 迪米特法则还有一个更简单的定义:只与直接的朋友通信,其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
    class Girl
    {
    };
    
    class GroupLeader
    {
    private:
        /* data */
    public:
        GroupLeader(/* args */);
        void count(List<Girl> girls);
    }
    
    class Teacher
    {
    private:
        /* data */
    public:
        void command(GroupLeader leader)
        {
            // Teacher 里面和Girls没有必要
            List<Girl> girls = new List();
            leader.count();
        }
    };
    
    int main ()
    {
        GroupLeader leader = new GroupLeader();
        Teacher t = new Teacher();
        t->command(leader);
        return 0;
    }
    
    class Girl
    {
    };
    
    class GroupLeader
    {
    private:
        /* data */
    public:
        GroupLeader(List<Girl> girls);
        void count();
    }
    
    class Teacher
    {
    private:
        /* data */
    public:
        void command(GroupLeader leader)
        {
            leader.count();
        }
    };
    
    int main ()
    {
        List<Girl> girls = new List;
        girls.append(new Girl());
        girls.append(new Girl());
    
        GroupLeader leader = new GroupLeader(girls);
        Teacher t = new Teacher();
        t->command(leader);
        return 0;
    }
    
  • 尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限
    class Wizzd
    {
    private:
        /* data */
    public:
        int first()
        {
        }
        int second()
        {
        }
        int third()
        {
        }
    };
    
    class installSoftware
    {
    private:
        /* data */
    public:
        void process(Wizzd w)
        {
            if (w.first() > 0)
            {
                if (w.second() > 0)
                {
                    if (w.third() < 0)
                    {
                    }
                }
            }
        }
    };
    
    class Wizzd
    {
    private:
        /* data */
    public:
        int first()
        {
        }
        int second()
        {
        }
        int third()
        {
        }
    
        void procee(Wizzd w)
        {
            if (w.first() > 0)
            {
                if (w.second() > 0)
                {
                    if (w.third() < 0) { }
                }
            }
        }
    };
    
    class installSoftware
    {
    private:
        /* data */
    public:
        void process(Wizzd w)
        {
            w.procee();
        }
    };
    

  • 如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中,一个类跳转两次以上才能访问到另一个类,就需要想办法进行重构了

例子:总公司经理和分公司员工的关系,体育老师,体育委员,女生,Wizard类把太多的方法暴露给InstallSoftware类

    6.开闭原则

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
做法

  • 抽象约束:如果再增加计算机书,增加接口和实现类,ComputerBook类必须实现IBook的三个方法,是通过IComputerBook接口传递进来的约束,也就是我们制定的IBook接口对扩展类ComputerBook产生了约束力,正是由于该约束力,BookStore类才不需要进行大量的修改,如果再增加计算机书,增加接口和实现类,接口或抽象类一旦定义,就应该立即执行,不能有修改接口的思想
  • 元数据,控制模块行为,参数可以从文件中获得,也可以从数据库中获得

例子:买书和降价(不是通过修改接口,修改实现类,而是扩展一个子类),
 

class IBook
{
};

class NoveBook : public IBook
{
private:
    /* data */
public:
    double getPrice()
    {
        return price;
    }
};
// 需求:新增加一个获取打折后价格的功能
// 1.增加getOffPrice()接口, 但是IBook应该是稳定的
// 2.修改getPrice(); 之前的功能degree
// 3.增加一个子类OffPrice
class OffNoveBook : public NoveBook
{
private:
    /* data */
public:
    double getPrice()
    {
        return price * 0.8;
    }
};

3.总结 

恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

这6个原则的首字母(里氏替换原则和迪米特法则的首字母重复,只取一个)联合起来就是SOLID(solid,稳定的)
架构师设计一套系统不仅要符合现有的需求,还要适应可能发生的变化,这才是一个优良的架构。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值