[设计模式]面向对象的六大原则

转载自:http://www.cnblogs.com/liuconglin/p/6551020.html
转载自:https://zhuanlan.zhihu.com/p/23382265
转载自:https://blog.csdn.net/hfreeman2008/article/details/52289571
转载自:https://blog.csdn.net/lovelion/article/details/7563445

单一职责原则

定义:

就一个类而言,应该仅有一个引起它变化的原因。
如果你能想到多余一个的动机去改变一个类,那么这个类就具有对于一个的职责,就应该考虑类的职责分离。

职责过多的坏处:

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其它职责的变化,这些耦合会导致脆弱的设计,当发生变化时,设计会遭受意想不到的破坏。

开发中:

软件设计的重要部分,就是发现职责,并把职责相互分离。

例子:

违反单一职责的情况:

[设计模式]面向对象的六大原则_第1张图片
Rectangle类具有两个职责:计算和绘图。 如果有两个程序(绘图程序、计算程序)分别依赖这个类,导致的问题:计算程序中需要包含GUI代码;绘图需求的改变会导致类的改变,这时由于内在的耦合,计算程序也是需要重新测试和构建的。

合适的改造:

[设计模式]面向对象的六大原则_第2张图片
将Rectangle类中的计算部分迁移到Geometric Rectangle类中,实现两个完全不同的类来保证单一职责。

开放封闭原则

定义:

软件实体(类,函数,模块等),应该可以扩展,但是不可以修改。
面对需求的变化,对程序的改动是通过增加新代码,而不是修改现有代码。

何时应对变化

在编写代码时,假设变化不会发生,或者猜测可能发生的变化种类;当发生小变化时,想办法应对更大的变化;有变化立刻采取行动,创建抽象隔离以后发生的同类变化;达到可维护、可扩展、可复用的目的。

例子:

违反开放封闭原则的情况:

在这个计算器的例子中,如果需要增加计算符号,我们需要修改Calc类和CalcUtils类的实现。

class Calc{
    double Add(double Number1,double Number2);
    double Sub(double Number1,double Number2);
    ... 
};

class CalcUtils{
    double Calc(char Operation,double Number1,double Number2){
        Calc calc;
        double Result=0.0;
        switch(Operation){
            case '+':
            Result=calc.Add(Number1,NUmber2);
            break;
        ... 
        }
    return Result;
    }
}

合适的改造:

如果需要增加计算符号,我们需要只需要添加新类,并CalcUtils类的实现,不会影响原有的运算符类。

class Operation
{
    virtual double Calc(double Number1,double Number2)=0;
};

class AddOperation:public Operation
{
    double Calc(double Number1,double Number2);
};
...
class CalcUtils
{
    double Calc(char Operation,double Number1,double Number2)
    {
        Operation* operation=NULL;
        double Result=0.0;
        switch(Operation)
        {
            case '+':
            operation=new AddOperation();
            ...
        }
        Result=operation->Calc(Number1,Number2);
        delete operation;
        return Result;
    }
}

依赖倒转原则

定义:

  1. 高层模块不应该依赖于低层模块,两者都依赖于抽象。
  2. 抽象不应该依赖于细节,细节应该依赖于抽象。
  3. 针对接口编程,不应该针对实现编程。
    也可以说高层模块,低层模块,细节都应该依赖抽象
    “面向接口编程”—OOD(Object-Oriented Design,面向对象设计)的精髓之一

例子:

大家都喜欢阅读,阅读文学经典滋润自己的内心心灵,下面是小明(XiaoMing)同学阅读文学经典(LiteraryClassic)的一个类图:

我们的实现,小明同学可以阅读文学经典了。小明同学看了一段文学经典后,忽然他想看看看小说来放松一下自己,我们实现一个小说类(Novel)。
我们看小明类,此类是一个高层模块,并且是一个细节实现类,此类依赖的是一个文学经典LiteraryClassic类,而文学经典LiteraryClassic类也是一个细节实现类。
这是不是就与我们说的依赖倒置原则相违背呢?依赖倒置原则是说我们的高层模块,实现类,细节类都应该是依赖与抽象,依赖与接口和抽象类。
为了解决小明同学阅读小说的问题,我们根据依赖倒置原则先抽象一个阅读者接口,下面是完整的uml类图:

至此,小明同学是可以阅读文学经典,又可以阅读小说了,目的达到了。

class IRead{
    virtual void read()=0;
}

class IReader{
    vitrual void read(IRead* read)=0;
}

class Novel:public IRead{
    void read();
}

class LiteraryClassic:public IRead{
    void read();
}

class XiaoMing:public IReader{
    void read(IRead* read);
}

void main()
{
    XiaoMing xiaoMing=new XiaoMing();
    IRead* literaryClassic=new LiteraryClassic();
    xiaoMing.read(literaryClassic);
    IRead* novel=new Novel();
    xiaoMing.read(novel);
}

为什么依赖抽象的接口可以适应变化的需求?

这就要从接口的本质来说,接口就是把一些公司的方法和属性声明,然后具体的业务逻辑是可以在实现接口的具体类中实现的。所以我们当依赖对象是接口时,就可以适应所有的实现此接口的具体类变化。

里氏代换原则

定义:

子类型必须能够替换掉它们的父类型
在程序中,把父类型全部替换成子类型,程序的行为没有变化时,父类才能真正的被复用,而子类也能在父类的基础上增加新的行文

例如

生物学中,企鹅是鸟类,但是程序设计中,子类拥有父类所有非private的属性和行为,企鹅不能当做鸟类的子类,因为企鹅不能飞,

合成复用原则

定义:

在一个对象中通过关联关系,包括组合和聚合关系,来使用一些已有的对象,使之成为新对象的一部分,从而扩展新功能
推荐多使用关联关系,以减少类继承的复杂度

合成

强拥有关系,A对象包含B对象,B对象包含A对象,他们的生命周期时一样的,比如大雁和它的翅膀

聚合

弱拥有关系,A对象可以包含B对象,B对象不包含A对象,比如企鹅需要知道气候的变化

迪米特法则

定义:

一个软件实体应当尽可能少地与其他实体发生相互作用。

好处:

如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。

如果两个类不必直接通信,那么这两个类就不应该发生直接的相互作用。如果一个类需要调用另一个类的方法时,可以通过第三者转发这个调用。强调类之间的耦合,类应当尽量降低成员的的访问权限,信息的隐藏促进了软件的复用。

好处:

迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
例如

小李第一天去公司上班, 需要找it部门的人安装电脑, 小李找人事, 人事给it部门的小王, 小王很忙没时间,人事又找到小赵, 最后是小赵帮忙安装电脑

由于it部门是抽象的,小李不需要认识it部门的人靠关系获得帮忙,即使it部门的员工换了也没事,小李只需通过人事转发就行了

你可能感兴趣的:(设计模式)