面向对象原则、OOD和OOP概述

面向对象原则和OOD实际上是两个不同的方面。

 面向对象原则:封装、继承、多态。

OOP指的是面向对象编程的基本原则和核心思路。在这里,OOP可以比作英语基础语法,这些语法教你如何用单词构造有意义且正确的句子,OOP教你在代码中构造类,并在类里封装属性和方法,同时构造他们之间的层次关系。

现在假定你需要就某些主题写几篇文章或随笔。你也希望就几个你擅长主题写几本书。对写好文章/随笔或书来说,知道如何造句是不够的。
为了使读者能更轻松的明白你讲的内容,你需要写更多的内容,学习以更好的方式解释它。
如果你想就某个主题写一本书,如学习OOD,你知道如何把一个主题分为几个子主题。你需要为这些题目写几章内容,也需要在这些章节中写前言,简介,例子和其他段落。 你需要为写个整体框架,并学习一些很好的写作技巧以便读者能更容易明白你要说的内容。这就是整体规划。

 

在软件开发中,OOD是整体思路。在某种程度上,设计软件时,你的类和代码需能达到模块化,可复用,且灵活
,这些很不错的指导原则不用你重新发明创造。而且有些原则你在自己的代码中可能用到了。

为什么要OOD? 

软件开发唯一的真理是“软件一定会变化”。为什么?因为你的软件解决的是现实生活中的业务问题,而现实生活中的业务流程总是在不停的变化。

假设你的软件在今天工作的很好。但它能灵活的支持“变化”吗?如果不能,那么你就没有一个设计敏捷的软件(一个设计敏捷的软件能轻松应对变化,能被扩展,并且能被复用)
。并且应用好OOD是做到敏捷设计的关键。

 

做到OOD的代码需要满足的条件:

1、面向对象

2、复用

3、能以最小的代价满足变化

4、不用改变现有代码满足扩展

OOD最基本的原则

最基本的是叫做SOLID的5原则(感谢Uncle Bob,伟大OOD导师)。

S = 单一职责原则 Single Responsibility Principle

O = 开放闭合原则 Opened Closed Principle

L = Liscov替换原则 Liscov Substitution Principle

I = 接口隔离原则 Interface Segregation Principle

D = 依赖倒置原则 Dependency Inversion Principle

单一职责原则 Single Responsibility Principle

单一职责原则海报

海报上说:"并不是因为你能,你就应该做"。为什么?因为长远来看它会带来很多管理问题。

 

从面向对象角度解释为:"引起类变化的因素永远不要多于一个。"或者说"一个类有且只有一个职责"。这个原则是说
,如果你的类有多于一个原因会导致它变化(或者多于一个职责),你需要依据它们的职责把这个类拆分为多个类。

当然可以在一个类中包含多个方法。问题是,他们都是为了一个目的。如今为什么拆分很重要?

 

那是因为:

 1、每个职责是轴向变化的;

 2、如果类包含多个职责,代码会变得耦合;

 

看一下下面的类层次。

违反单一职责原则的类结构图

 

这里,Rectangle类做了下面两件事:

 1、计算矩形面积;

 2、在界面上绘制矩形;

 

并且,有两个应用使用了Rectangle类:

 1、计算几何应用程序用这个类计算面积;

 2、图形程序用这个类在界面上绘制矩形;

 

Rectangle类做了两件事。在一个方法里它计算了面积,在另外一个方法了它返回一个表示矩形的GUI。这会带来一些有趣的问题:

1.在计算几何应用程序中我们必须包含GUI。也就是在开发几何应用时,我们必须引用GUI库;

2.图形应用中Rectangle类的变化可能导致计算几何应用变化,编译和测试,反之亦然;

 

应该依据职责拆分这个类,拆分职责到两个不同的类中,如:

Rectangle:这个类应该定义Area()方法;

RectangleUI:这个类应继承Rectangle类,并定义Draw()方法。

 

   SRP是把事物分离成分子部分,以便于能被复用和集中管理。我们也可以把SRP应用到方法之中,
应当分解你的方法,让每个方法只做某一项工作。那样允许你复用方法,并且一旦出现变化,你能购以修改最少的代码满足变化。

开放闭合原则 Opened Closed Principle

从面向对象设计角度看,它可以这么说:"软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。"

通俗来讲,它意味着你应当能在不修改类的前提下扩展一个类的行为。就好像我不需要改变我的身体而可以穿上衣服。

客户端和服务段都耦合在一起。那么,只要出现任何变化,服务端变化了,客户端一样需要改变。

 在这个例子中,添加了一个抽象的服务器类,客户端包含一个抽象类的引用,具体的服务类实现了抽象服务类。那么,因任何原因引起服务实现发生变化时,客户端都不需要任何改变。

 

这里抽象服务类对修改是闭合的,实体类的实现对扩展是开放的。

 

抽象是关键,基本上,你抽象的东西是你系统的核心内容
,如果你抽象的好,很可能在扩展功能时它不需要任何修改(就像服务是一个抽象概念)。如果在实现里定义了抽象的东西(比如IIS服务器实现的服务),代码要尽可能以抽象(服务)为依据。这会允许你扩展抽象事物,定义一个新的实现(如Apache服务器)而不需要修改任何客户端代码。

Liscov替换原则 Liscov Substitution Principle

这个原则意思是:"子类型必须能够替换它们父类型。"或者换个说法:"使用父类引用的函数必须能使用子类的对象而不必知道它。"

在基本的面向对象原则里,"继承"通常是"is a"的关系。如果"Developer" 是一个"SoftwareProfessional",那么"Developer"类应当继承"SoftwareProfessional"类。在类设计中"Is a"关系非常重要,但它容易冲昏头脑,结果使用错误的继承造成错误设计。

 

"Liskov替换原则"正是保证继承能够被正确使用的方法。

这里,KingFisher类扩展了Bird基类,并继承了Fly()方法,这看起来没问题。

 

   现在看下面的例子:

Ostrich(鸵鸟)是一种鸟(显然是),并从Bird类继承。它能飞吗?不能,这个设计就违反了LSP。

 

所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类,Ostrich应该继承与它。

 

为什么LSP这么重要:

如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;

如果没有LSP,适用与基类的单元测试将不能成功用于测试子类;

接口隔离原则 Interface Segregation Principle

它的意思是:"客户端不应该被迫依赖于它们不用的接口。"

假设你想买个电视机,你有两个选择。一个有很多开关和按钮,它们看起来很混乱,且好像对你来说没必要。另一个只有几个开关和按钮,它们很友好,且适合你使用。假定两个电视机提供同样的功能,你会选哪一个?答:
第二个,因为我不需要那些看起来混乱又对我没用的开关和按钮。

以便
外部能够知道这些类有哪些可用的功能,客户端代码也能根据接口来设计.现在,如果接口太大,包含很多暴露的方法,在外界看来会很混乱.接口包含太多的方法也使其可用性降低,像这种
包含了无用方法的"胖接口"会增加类之间的耦合
.你通过接口暴露类的功能,同样地,假设你有一些类,如果一个类想实现该接口,那么它需要实现所有的方法,尽管有些对它来说可能完全没用.所以说这么做会在系统中引入不必要的复杂度,降低可维护性或鲁棒性.

 

接口隔离原则确保实现的接口有他们共同的职责,它们是明确的,易理解的,可复用的.

 

接口应该仅包含必要的方法,而不该包含其它的。

注意到IBird接口包含很多鸟类的行为,包括Fly()行为.现在如果一个Bird类(如Ostrich)实现了这个接口,那么它需要实现不必要的Fly()行为(Ostrich不会飞).

所以,这个"胖接口"应该拆分未两个不同的接口,IBird和IFlyingBird,IFlyingBird继承自IBird.

 这里如果一种鸟不会飞(如Ostrich),那它实现IBird接口。如果一种鸟会飞(如KingFisher),那么它实现IFlyingBird.

 

   
所以回头看包含了很多开关和按钮的电视机的例子,电视机制造商应该有一个电视机的图纸,开关和按钮都在这个方案里。不论任何时候,当他们向制造一种新款电视机时,如果他们想复用这个图纸,他们将需要在这个方案里添加更多的开关和按钮。那么他们将没法复用这个方案
。如果他们确实需要复用方案,它们应当把电视机的图纸份为更小部分,以便在任何需要造新款电视机的时候复用这点小部分。

依赖倒置原则 Dependency Inversion Principle

它的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象。

考虑一个现实中的例子。你的汽车是由很多如引擎,车轮,空调和其它等部件组成。

它们没有一个是严格的构建在一个单一单元里;换句话说,它们都是可插拔的,因此当引擎或车轮出问题时,你可以修理它(而不需要修理其它部件),甚至可以换一个。

在替换时,你仅需要确保引擎或车轮符合汽车的设计(如汽车能使用任何1500CC的引擎或任何18寸的车轮)。

 

当然,汽车也可能允许你在1500CC引擎的地方安装一个2000CC的引擎,事实上对某些制造商(如丰田汽车)是一样的。

如果你的汽车的零部件不具备可插拔性会有什么不同?那会很可怕!因为如果汽车的引擎出故障了,你可能修理整部车或者需要买一个新的。

 

如何做到"可插拔性"呢?关键就是抽象。

在现实中,汽车是高级模块或实体,它依赖于低级模块或实体,如引擎或车轮。相比直接依赖于引擎或车轮,汽车应依赖于某些抽象的有规格的引擎或车轮,以便于如果任何引擎或车轮符合抽象,那么它们都能组合到汽车中,汽车也能跑动。

注意到上面Car类有两个属性,它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。

 

如果代码中不用依赖倒置,我们将面临如下风险:

1、使用低级类会破环高级代码;

2、当低级类变化时需要很多时间和代价来修改高级代码;

3、产生低复用的代码;

其他面向对象原则

除SOLID原则外还有很多其它的面向对象原则。如:

"组合替代继承":这是说相对于继承,要更倾向于使用组合;

"笛米特法则":这是说"你的类对其它类知道的越少越好";

"共同封闭原则":这是说"相关类应该打包在一起";

"稳定抽象原则":这是说"类越稳定,越应该由抽象类组成";

 

设计模式只是对一些经常出现的场景的一些通用设计建议。这些灵感主要来自于面向对象原则。你可以把设计模式看作"框架",把OOD原则看作"规范".

技术
©2019-2020 Toolsou All rights reserved,
数字滚动抽奖小程序VaR - 风险价值 - 蒙特卡罗法 - Python百度网盘偷偷更新,终于实现免费不限速了! Chrome OS,对程序员和Windows意味着什么?,互联网营销华为Mate 40 Pro+ 5G曝光:徕卡电影镜头、陶瓷机身Qt学习2——.pro文件和.h文件介绍python:将一个文件转换为二进制文件(binary)第十一届蓝桥杯C/C++ 大学 B 组大赛软件类省赛网站手机号码抓取方式蚂蚁集团香港IPO获得中国证监会批准