转载自: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
就一个类而言,应该仅有一个引起它变化的原因。
如果你能想到多余一个的动机去改变一个类,那么这个类就具有对于一个的职责,就应该考虑类的职责分离。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其它职责的变化,这些耦合会导致脆弱的设计,当发生变化时,设计会遭受意想不到的破坏。
软件设计的重要部分,就是发现职责,并把职责相互分离。
Rectangle类具有两个职责:计算和绘图。 如果有两个程序(绘图程序、计算程序)分别依赖这个类,导致的问题:计算程序中需要包含GUI代码;绘图需求的改变会导致类的改变,这时由于内在的耦合,计算程序也是需要重新测试和构建的。
将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;
}
}
大家都喜欢阅读,阅读文学经典滋润自己的内心心灵,下面是小明(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部门的员工换了也没事,小李只需通过人事转发就行了