面向对象设计六大原则.docx

上传人:b****6 文档编号:8555057 上传时间:2023-01-31 格式:DOCX 页数:25 大小:207.71KB
下载 相关 举报
面向对象设计六大原则.docx_第1页
第1页 / 共25页
面向对象设计六大原则.docx_第2页
第2页 / 共25页
面向对象设计六大原则.docx_第3页
第3页 / 共25页
面向对象设计六大原则.docx_第4页
第4页 / 共25页
面向对象设计六大原则.docx_第5页
第5页 / 共25页
点击查看更多>>
下载资源
资源描述

面向对象设计六大原则.docx

《面向对象设计六大原则.docx》由会员分享,可在线阅读,更多相关《面向对象设计六大原则.docx(25页珍藏版)》请在冰豆网上搜索。

面向对象设计六大原则.docx

面向对象设计六大原则

面向对象设计六大原则

面向对象设计的原则是面向对象思想的提炼,它比面向对象思想的核心要素更具可操作性,但与设计模式相比,却又更加的抽象,是设计精神要义的抽象概括。

形象地将,面向对象思想像法理的精神,设计原则则相对于基本宪法,而设计模式就好比各式各样的具体法律条文了。

面向对象设计原则有6个:

开放封闭原则,单一职责原则,依赖倒置原则,Liskov替换原则,迪米特法则和接口隔离原则或合成/聚合复用原则〔不同资料略有不同,这里对7个都做了整理〕。

1单一职责原则

Thereshouldneverbemorethanonereasonforaclasstochange.什么意思呢?

所谓单一职责原则就是一个类只负责一个职责,只有一个引起变化的原因。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化会削弱或抑制这个类完成其他职责的能力,这个耦合会导致脆弱的设计。

软件设计真正要做的许多内容,就是发现职责并把这些职责相互分离;如果能够想到多于一个动机去改变一个类,则这个类就具有多于一个职责,就应该考虑类的分离。

以调制解调器为例如下图:

从上述类图里面我们发现有四个方法Dial<拨通>,Hangup<挂>,Receive<收到信息>,Send<发送信息>,经过分析不难判断出,实际上Dial<拨通>和Hangup<挂>是属于连接的范畴,而Receive<收到信息>和Send<发送信息>是属于数据传送的范畴。

这里类包括两个职责,显然违反了SRP。

这样做有潜在的隐患,如果要改变连接的方式,势必要修改Modem,而修改Modem类的结果导致凡事依赖Modem类可能都需要修改,这样就需要重新编译和部署,不管数据传输这部分是否需要修改。

因此要重构Modem类,从中抽象出两个接口,一个专门负责连接,另一个专门负责数据传送。

依赖Modem类的元素要做相应的细化,根据职责的不同分别依赖不同的接口。

如下图:

这样以来,无论单独修改连接部分还是单独修改数据传送部分,都彼此互不影响。

总结单一职责优点:

降低类的复杂性,

提高可维护性

提高可读性。

降低需求变化带来的风险。

需求变化是不可避免的,如果单一职责做的好,一个接口修改只对相应的实现类有影响,对其它的接口无影响,这对系统的扩展性和维护性都有很大的帮助。

2里氏替换原则〔LiskovSubstitutionPrincipleLSP〕

里氏替换原则是面向对象设计的基本原则之一。

任何基类可以出现的地方,子类一定可以出现。

LSP是继承复用的基石,只有当子类可以替换基类,软件单位的功能不受影响时,基类才能真正的被复用,而子类也可以在基类的基础上增加新的行为。

Liskov提出了关于继承的原则:

Inheritanceshouldensurethatanypropertyprovedaboutsupertypeobjectsalsoholdsforsubtypeobjects.----继承必须确保超类中所拥有的性质在子类中仍然成立。

20##,软件工程大师RobertC.Martin出版了一本《AgileSoftwareDevelopmentPrinciplesPatternsandPractices》,在文中他把里氏代换原则最终简化为一句话:

"Subtypesmustbesubstitutablefortheirbasetypes"也就是说子类必须能够替换成他们的基类。

里氏替换原则讲的是基类和子类的关系,只有这种关系存在的时候里氏替换原则才能成立。

里氏替换原则是实现开放封闭原则的具体规范。

这是因为:

实现开放封闭原则的关键是抽象,而继承关系又是抽象的一种具体实现。

我们大家都打过CS的游戏,用枪射击杀人,如下类图:

枪的主要职责是射击,如何射击在各个具体的子类中定义。

注意在类中调用其他类时务必调用父类或接口,如果不能掉话父类或接口,说明类的射击已经违反了LSP原则。

如果我们有一个玩具手枪,该如何定义呢?

我们先在类图2-1上增加一个类ToyGun,然后继承于AbstractGun类,修改后的类图如下:

玩具枪是不能用来射击的,杀不死人的,这个不应该写shoot方法,在这种情况下业务的调用类就会出现问题。

为了解决这个问题,ToyGun可以脱离继承,建立一个独立的父类,为了做到代码可以服用,可以与AbstractGun建立关联委托关系,如下图:

因此,如果子类不能完整地实现父类的方法,则建议断开父子继承关系,采用依赖,聚合,组合等关系代替继承。

子类可以有自己的属性或方法。

覆盖或实现父类的方法时输入的参数可以放大。

覆盖或实现父类的方法时输出结果可以被缩小。

这是什么意思呢,父类的方法返回值是一个类型T,子类相同的方法〔覆写〕的返回值为类型S,则根据里氏替换原则就要求S必须小于等于T,也就是说要么S和T是同一个类型,要么S是T的子类型。

采用里氏替换原则的目的就是增加程序的健壮性,需求变更时也可以保持良好的兼容性和稳定性,即使增加子类,原有的子类可以继续运行。

在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同业务逻辑。

3依赖倒置原则〔DependenceInversionPrincipleDIP〕

所谓依赖倒置原则就是要依赖于抽象,不要依赖于具体。

简单的说就是对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变化时,上层也要跟着变化,这就会导致模块的复用性降低而且大大提高了开发的成本。

面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。

即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。

这大大降低了客户程序域实现细节的耦合度。

比如一个合资汽车公司现在要求开发一个自动驾驶系统,只要汽车上安装上这个系统,就可以实现无人驾驶,该系统可以在福特车系列和本田车系列上使用。

面向过程的结构图:

实现代码如下:

publicclassHondaCar

{

publicvoidRun<>{Console.WriteLine<"本田车启动了!

">;}

publicvoidTurn<>{Console.WriteLine<"本田车拐弯了!

">;}

publicvoidStop<>{Console.WriteLine<"本田车停止了!

">;}

}

publicclassFordCar

{

publicvoidRun<>{Console.WriteLine<"福特车启动了!

">;}

publicvoidTurn<>{Console.WriteLine<"福特车拐弯了!

">;}

publicvoidStop<>{Console.WriteLine<"福特车停止了!

">;}

}

publicclassAutoSystem

{

publicenumCarType{Ford,Fonda}

privateHondaCarhondcar=newHondaCar<>;

privateFordCarfordcar=newFordCar<>;

privateCarTypetype;

publicAutoSystem

{

this.type=carType;

}

publicvoidRunCar<>

{

if

{

hondcar.Run<>;

}

elseif

{

fordcar.Run<>;

}

}

publicvoidStopCar<>

{

if

{

hondcar.Stop<>;

}

elseif

{

fordcar.Stop<>;

}

}

publicvoidTurnCar<>

{

if

{

hondcar.Turn<>;

}

elseif

{

fordcar.Turn<>;

}

}

}

显然这个实现代码也可满足现在的需求。

但是如何现在公司业务规模扩大了,该自动驾驶系统还要把吉普车也兼容了。

这些就需要修改AutoSystem类如下:

publicclassAutoSystem

{

publicenumCarType{Ford,Fonda,Jeep}

privateHondaCarhondcar=newHondaCar<>;

privateFordCarfordcar=newFordCar<>;

privateJeepjeep=newJeep<>;

privateCarTypetype;

publicAutoSystem

{

this.type=carType;

}

publicvoidRunCar<>

{

if

{

hondcar.Run<>;

}

elseif

{

fordcar.Run<>;

}

elseif

{

jeep.Run<>;

}

}

publicvoidStopCar<>

{

if

{

hondcar.Stop<>;

}

elseif

{

fordcar.Stop<>;

}

elseif

{

jeep.Stop<>;

}

}

publicvoidTurnCar<>

{

if

{

hondcar.Turn<>;

}

elseif

{

fordcar.Turn<>;

}

elseif

{

jeep.Turn<>;

}

}

}

通过代码分析得知,上述代码也确实满足了需求,但是软件是不断变化的,软件的需求也是变化的,如果将来业务又扩大了,该自动驾驶系统还有能实现通用、三菱、大众汽车,这样我们不得不又要修改AutoSystem类了。

这样会导致系统越来越臃肿,越来越大,而且依赖越来越多低层模块,只有低层模块变动,AutoSystem类就不得不跟着变动,导致系统设计变得非常脆弱和僵硬。

导致上面所述问题一个原因是,含有高层策略的模块,如AutoSystem模块,依赖于它所控制的低层的具体细节的模块〔如FordCar和HondaCar〕。

如果能使AutoSystem模块独立于它所控制的具体细节,而是依赖抽象,则我们就可以服用它了。

这就是面向对象中的"依赖倒置"机制。

如下类图:

实现代码如下:

publicinterfaceICar

{

voidRun<>;

voidStop<>;

voidTurn<>;

}

publicclassHondaCar:

ICar

{

publicvoidRun<>{Console.WriteLine<"本田车启动了!

">;}

publicvoidTurn<>{Console.WriteLine<"本田车拐弯了!

">;}

publicvoidStop<>{Console.WriteLine<"本田车停止了!

">;}

}

publicclassFordCar:

ICar

{

publicvoidRun<>{Console.WriteLine<"福特车启动了!

">;}

publicvoidTurn<>{Console.WriteLine<"福特车拐弯了!

">;}

publicvoidStop<>{Console.WriteLine<"福特车停止了!

">;}

}

publicclassJeep:

ICar

{

publicvoidRun<>{Console.WriteLine<"福特车启动了!

">;}

publicvoidTurn<>{Console.WriteLine<"福特车拐弯了!

">;}

publicvoidStop<>{Console.WriteLine<"福特车停止了!

">;}

}

publicclassAutoSystem

{

privateICarcar;

publicAutoSystem

{

this.car=car;

}

publicvoidRunCar<>

{

this.car.Run<>;

}

publicvoidStopCar<>

{

this.car.Stop<>;

}

publicvoidTurnCar<>

{

this.car.Turn<>;

}

}

现在Autosystem系统依赖于ICar这个抽象,而与具体的实现细节HondaCar:

和FordCar无关,所以实现细节的变化不会影响AutoSystem.对于实现细节只要实现ICar即可。

即实现细节依赖于ICar抽象。

综上所述:

一个应用中的重要策略决定及业务正是在这些高层的模块中。

也正是这些模块包含这应用的特性。

但是,当这些模块依赖于低层模块时,低层模块的修改比较将直接影响到他们,迫使它们也改变。

这种情况是荒谬的。

应该是处于高层的模块去迫使那些低层的模块发生改变。

处于高层的模块应优先于低层的模块。

无论如何高层模块也不应该依赖于低层模块。

而且我们想能够复用的是高层的模块,只有高层模块独立于低层模块时,复用才有可能。

总之,高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。

抽象不应该依赖于具体,具体应该依赖于抽象。

 

4迪米特法则

迪米特法则〔LawofDemeter〕又叫最少知识原则〔LeastKnowledgePrincipleLKP〕,就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

对面向对象来说,一个软件实体应当尽可能的少的与其他实体发生相互作用。

每一个软件单位对其他的单位都只有最少的知识,而其局限于那些与本单位密切相关的软件单位。

迪米特法则的目的在于降低类之间的耦合。

由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块相互独立,相互之间不存在依赖关系。

应用迪米特法则有可能造成的一个后果就是,系统中存在的大量的中介类,这些类只所以存在完全是为了传递类之间的相互调用关系---这在一定程度上增加系统的复杂度。

设计模式中的门面模式〔Facade〕和中介模式〔Mediator〕都是迪米特法则的应用的例子。

狭义的迪米特法则的缺点:

在系统里面造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商业逻辑无关。

遵循类之间的迪米特法则会使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有之间的关联。

但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

广义的迪米特法则在类的设计上的体现:

优先考虑将一个类设置成不变类.

尽量降低一个类的访问权限。

尽量降低成员的访问权限。

下面的代码在方法体内部依赖了其他类,这严重违反迪米特法则

1

2

3

4

5

6

7

8

9

10

11

12

13

publicclassTeacher{

  

    publicvoidmond{

        ListlistGirls=newArrayList<>;

  

        for{

            listGirls.add>;

        }

  

        groupLeader.countGirls;

    }

  

}

 

方法是类的一个行为,类竟然不知道自己的行为与其他类产生了依赖关系,这是不允许的。

正确的做法是:

1

2

3

4

5

6

7

publicclassTeacher{

  

    publicvoidmond{

        groupLeader.countGirls<>;

    }

  

}

1

2

3

4

5

6

7

8

9

10

11

12

13

publicclassGroupLeader{

  

    privateListlistGirls;

  

    publicGroupLeader_listGirls>{

        this.listGirls=_listGirls;

    }

  

    publicvoidcountGirls<>{

        <"女生数量是:

"+listGirls.size<>>;

    }

  

}

5开放封闭原则

Softwareentitiesshouldopenforextension,butcloseformodification.

什么意思呢?

所谓开放封闭原则就是软件实体应该对扩展开发,而对修改封闭。

开放封闭原则是所有面向对象原则的核心。

软件设计本身所追求的目标就是封装变化,降低耦合,而开放封闭原则正是对这一目标的最直接体现。

开放封闭原则主要体现在两个方面:

对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。

对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

为什么要用到开放封闭原则呢?

软件需求总是变化的,世界上没有一个软件的是不变的,因此对软件设计人员来说,必须在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。

如何做到对扩展开放,对修改封闭呢?

实现开放封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。

让类依赖于固定的抽象,所以对修改就是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。

对于违反这一原则的类,必须通过重构来进行改善。

常用于实现的设计模式主要有TemplateMethod模式和Strategy模式。

而封装变化,是实现这一原则的重要手段,将经常变化的状态封装为一个类。

以银行业务员为例

没有实现OCP的设计:

publicclassBankProcess

{

//存款

publicvoidDeposite<>

{

}

//取款

publicvoidWithdraw<>

{

}

//转账

publicvoidTransfer<>

{

}

}

publicclassBankStaff

{

privateBankProcessbankpro=newBankProcess<>;

publicvoidBankHandle

{

switch

{

//存款

case"deposite":

bankpro.Deposite<>;

break;

//取款

case"withdraw":

bankpro.Withdraw<>;

break;

//转账

case"transfer":

bankpro.Transfer<>;

break;

}

}

}

这种设计显然是存在问题的,目前设计中就只有存款,取款和转账三个功能,将来如果业务增加了,比如增加申购基金功能,理财功能等,就必须要修改BankProcess业务类。

我们分析上述设计就不能发现把不能业务封装在一个类里面,违反单一职责原则,而有新的需求发生,必须修改现有代码则违反了开放封闭原则。

从开放封闭的角度来分析,在银行系统中最可能扩展的就是业务功能的增加或变更。

对业务流程应该作为扩展的部分来实现。

当有新的功能时,不需要再对现有业务进行重新梳理,然后再对系统做大的修改。

如何才能实现耦合度和灵活性兼得呢?

那就是抽象,将业务功能抽象为接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。

以下是符合OCP的设计:

首先声明一个业务处理接口

publicinterfaceIBankProcess

{

voidProcess<>;

}

publicclassDepositProcess:

IBankProcess

{

publicvoidProcess<>

{

//办理存款业务

Console.WriteLine<"ProcessDeposit">;

}

}

publicclassWithDrawProcess:

IBankProcess

{

publicvoidProcess<>

{

//办理取款业务

Console.WriteLine<"ProcessWithDraw">;

}

}

publicclassTransferProcess:

IBankProcess

{

publicvoidProcess<>

{

//办理转账业务

Console.WriteLine<"ProcessTransfer">;

}

}

publicclassBankStaff

{

privateIBankProcessbankpro=null;

publicvoidBankHandle

{

switch

{

//存款

case"Deposit":

bankpro=newDepositUser<>;

break;

//转账

case"Transfer":

bankpro=newTransferUser<>;

break;

//取款

case"WithDraw":

bankpro=newWithDrawUser<>;

break;

}

 

bankpro.Process<>;

}

}

这样当

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 表格类模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1