点击上方“
程序员大咖
”,选择“置顶公众号”
关键时刻,第一时间送达!
简介
我老婆
Farhana
想要继续软件开发生涯(之前因为我们的第一个孩子出生,她不得不放弃)。我已经有了一些软件设计和开发的经验,所以这几天我就在试着帮助她学习OOD。
由于我早年在软件开发的经验,我总是发现无论一个技术问题看上去多么难搞,只要从现实生活的角度去解释或用对话的方式去讨论总能让它变得更简单。关于OOD,我们已经有了许多成果丰硕的讨论,我觉得有人可能发现这是一个学习OOD有趣的方式,所以我想我应该分享出来。
下面是我们的谈话步骤:
话题:介绍面向对象设计
丈夫
:亲爱的,让我们开始学习面向对象设计。你了解面向对象规范,对吗?
妻子
:你是指封装,继承和多态吗?是的,我了解这些规范。
丈夫
:行,我想你已经知道怎么用类和对象了。今天我们来学习面向对象设计。
妻子
:等等。了解面向对象规范对面向对象编程来说难道不够吗?我的意思是,我能够定义类,封装属性和方法。我能够根据它们的关系定义类的继承。那还有什么呢?
丈夫
:很好的问题。面向对象规范和面向对象编程完全是两码事。让我展示一个现实生活中的例子来帮助你理解它们。
我们从牙牙学语起,都是先从字母表学起的,对吧?
妻子
: 嗯。
丈夫
: 好,然后你就能认单词了,还能通过不同的字母拼写出不同的单词来。慢慢的,你能通过一些基本的语法把这些单词串成一句话。为了使句子时态正确且没有语病,你需要用一些介词,连词,等等。。看下面这句话
"I" (代词) "want" (动词) "to" (介词) "learn" (动词) "OOD" (名词)
通过把几个单词摆放妥当一句话就好了,然后用个关键词来说明一下这句话的重点。
妻子
: 亲爱的,你闲扯这些到底要说明什么呢
丈夫
: 我说的这个例子跟面向对象规范很类似,面向对象规范为面向对象编程定义了基本的规范,它是面向对象编程的主要思想。面向对象规范好比基本的英语语法,这些语法教会了你怎么用一个个单词拼凑出一句句话来,而面向对象规范教你怎么用类,怎么把一些属性和方法封装在一个类里,怎么串出类之间的继承关系。
妻子
: 啊哈,我知道了,那么,面向对象适用于哪里呢。
丈夫
: 听我慢慢道来。现在,假设你想写点有内容有题材的文章。你当然还希望写点你比较擅长的题材的书,就会简单造几个句子是远远不够的,对吧。你需要笔耕不辍写出一些长篇大论,你还需要学习怎么可以让读者很容易就看懂你写的这些长篇大论。。。
妻子
:嗯,有那么点意思。。。继续吧
丈夫
:现在,假如你想写本关于面向对象设计的书,你需要把这个大的课题拆分成一些小题目。把这些小题目分几个章节写,还得写前言,简介,说明,举例,一篇里还有很多段落。你需要设计一整本书,还得练习一些写作技巧,让文章读起来浅显易懂。这就是综观全局。
在软件开发中,OOD就是用来解决从全局出发考虑问题,在设计软件的时候,类和代码可以模块化,可重复使用,可灵活应用,现在已经有很多前人总结出的类和对象的设计原理了,我们直接拿来用就行了,总之,历史的车轮已经碾压出一条清晰的车轮印,我们只要照着走就可以了。
妻子:
哎,懂了点皮毛,还有很多要学呢。
丈夫
:不用担心,你很快就会上手的,让我们接着来吧。
话题:为什么要进行面向对象设计?
作者
:有个很重要的问题,既然我们能够很快的创建几个类,编写程序并提交,为什么我们还要关注
面向对象设计
?这样不够么?
妻子
:恩,以前我不知道面向对象设计,我也能开发提交项目。有什么关系?
丈夫
:好吧,先让我给你看一个经典的引述:
"需求不变的程序开发会同行走在冰上一样简单。"
-
Edward V.
Berard
妻子
:你是指软件开发说明书会被不断修改?
丈夫
:非常正确!软件开发唯一的真理是“软件必然修改”。为什么?
要知道,你的软件解决的是现实世界中的问题,而现实生活不是一成不变的。
可能你的软件现在运行良好。但它能灵活的支持“变化”吗?如果不能,那它就不是一个敏捷设计的软件。
妻子
:好,那你就解释一下什么叫做“敏捷设计的软件”!
丈夫
:“一个敏捷设计的软件能轻松应对变化,能被扩展和复用。”
而应用“面向对象设计”是做到敏捷设计的关键。那么,什么时候你可以说你的程序应用了面向对象设计?
妻子
:我也正想问呢。
丈夫
:如果代码符合以下几点,那么你就在“面向对象设计”:
-
面向对象
-
复用
-
变化的代价极小
-
无需改代码即可扩展
妻子
:然后呢?
丈夫
:不只我们。很多人也花了很多时间和精力思考这个问题上,他们尝试更好的进行“面向对象设计”,并为“面向对象设计”指出几条基本的原则(你可以用在你的“面向对象设计”中)。他们也确实总结出了一些通用的设计模式(基于基本的原则)。
妻子
:你能说出一些吗?
丈夫
:没问题。现在有许多设计原则,但是最基本的,就是SOLID(缩写),这五项原则。(感谢鲍勃叔叔,伟大OOD导师)。
S = 单一责任原则
O = 开闭原则
L = Liscov替换原则
I = 接口隔离原则
D = 依赖倒置原则
在下面的讨论中,我们将详细了解这些。
话题:单一功能原则
作者:
让我们先来看图,我们应该感谢制作这张图的人,因为它们真的太有趣了。
单一功能原则图
它的意思是:“如果你可以在一个设备中实现所有的功能,你却不能这样做”。为什么呢?因为从长远来看它增加了很多的可管理性问题。
从面向对象角度解释是:
"
导致类变化的因素永远不要多于一个。
"
或者换行个说法:"一个类有且只有一个职责"。
妻子
:可以解释一下么?
丈夫
:当然,这个原则是说,如果有多于一个原因会导致你的类改变(或者它的职责多余一个),你就需要根据其职责把这个类拆分为多个类。
妻子
:嗯...这是不是意味着在一个类里不能有多个方法?
丈夫
:当然不是。你当然可以在一个类中包含多个方法。问题是,他们都是为了一个目的。那么,为什么拆分很重要的?
那是因为:
-
每个职责都是轴向变化;
-
如果类包含多个职责,代码会变得耦合;
妻子
:给个例子呗?
丈夫
:木有问题啊,瞅瞅下面类的结构。其实,这个例子是 Bob 叔叔那儿来的,得谢谢他。
违反SRP原则的类层次结构
这里,Rectangle 类干了下面两件事:
计算矩形面积;
而且,有两个程序使用了 Rectangle 类:
-
计算几何应用程序用这个类计算面积;
-
图形程序用这个类在界面上绘制矩形;
这违反了SRP原则(单一职责原则)!
妻子
:肿么回事?
丈夫
:你瞅瞅,Rectangle 类干了俩不相干的事。一个方法它计算了面积,另外一个它返回一个表示矩形的 GUI资源。这问题就有点乐了:
妻子
:是很乐。就是说,咱得根据类的职责分开写呗?
丈夫
:必须滴。猜猜怎么干?
妻子
:我想想,我寻思这得这么办:
我瞅着得按职责拆成两个类:
丈夫
:很好。这么个,计算几何应用使 Rectangle 类,图形应用使 RectangleUI 类。咱还可以把这俩类分到俩单独的 DLL 中,然后改的时候就不用管另一个了。
妻子
:谢了,我大概明白SRP 原则了一句话:SPR就是把东西分到不能再分了,再集中化管理和复用。囔,在方法层面上,咱不也得用 SPR 原则?我是说,咱写的方法里有很多干不同事儿的代码,这也不符合SPR原则吧。
丈夫
:你说地不差。方法也得分开,一个方法干一个活。这么着你复用方法,要是改了,也不用改太多。
话题:开闭原则
作者
:“开闭原则“图示如下:
图:开闭原则图
让我来解释一下,设计规则如下:
“软件实体(类,模块,函数等)应该对扩展开放,对修改关闭。”
这意味着在最基本的层面上,你可以扩展一个类的行为,而无需修改。这就像我能够穿上衣服,而对我的身体不做任何改变,哈哈。
妻子
: 太有意思啦. 你可以通过穿不同的衣服来改变你的外貌, 但是你不必为此改变自己的身体.所以你是对扩展开放的,对吧?
丈夫
: 是的. 在面向对象设计中, 对扩展开放意味着模块/类的行为可以被扩展,那么当需求变化时我们可以用各种各样的方法制定功能来满足需求变更或者新需求
妻子
: 除此之外你的身体是对修改关闭的. 我喜欢这个例子. 所以,对于核心模块或类的代码在需要扩展的时候不应该被修改. 你能结合具体例子解释下吗?
丈夫
: 当然了, 先看下面的例子.这个就不支持 "开放-关闭" 原则:
类的层次结构已经表明了这是违反"开放-关闭"原则的.
你看, 客户端类和服务端类都是具体的实现类. 因为, 如果某些原因导致服务端实现改变了, 客户端也需要相应变化.