如何实现良好的面向对象设计秘诀

23

我是一名C++程序员,希望学习和掌握面向对象设计。我已经做了很多搜索,众所周知有大量关于如何实现良好的面向对象设计的材料、书籍、教程等等。当然,我明白良好的设计是需要通过大量经验、个人才能、智慧或甚至纯粹的运气(夸张!)来实现的。

但是,它确实始于一个坚实的开始和建立一些强大的基础。有人可以帮我指出如何从识别对象、类等阶段开始学习设计的合适材料吗?直到使用设计模式的阶段。 话虽如此,我是一名程序员,但我没有设计经验。请问你能帮助我过渡从程序员到设计师吗? 任何提示、建议、意见都会有所帮助。

[编辑]感谢提供链接和答案,我需要让自己进入那个状态:)正如我之前提到的,我是一名C++程序员,我确实理解面向对象的基本概念,例如继承、抽象、多态,并且在C++中编写代码时,也理解了一些设计模式。我不理解的是应该如何处理需求的基本思考过程。关于如何定义或得出它们之间的关系,决定应该制作哪些类,以及如何去做细节。我理解了这些概念(在一定程度上),但不知道如何应用它们,这似乎是我的问题:( 有什么建议吗?


3
“OOPS设计”是一个奇怪而有趣的术语,我建议使用更为常见的“面向对象设计(OO设计)”或简称“OOD”。 - Péter Török
8
@Peter:嗯,我认为我们大多数人都写过很多只能用“糟糕设计”来形容的代码 ;) - jalf
1
@jalf,哦,是的 :-) 虽然我不太确定OP想要实现那种壮举;-) - Péter Török
类-职责-协作卡开始。 - rwong
8个回答

20
  1. (非常)简单,但不是最简设计:K.I.S.S原则。K.I.S.S
  2. 倾向于扁平层次结构,避免深层次结构。
  3. 关注点分离是必要的。
  4. 在面向对象编程不够简单或优雅时,考虑其他"范式"。
  5. 更普遍地说:不要重复自己你不需要它帮助您实现1.

1
在C++中,这更加重要,因为您可以本地访问其他范例。在其他语言中,通常需要添加一种语言(例如C#/F#,Java / Groovy / Scala)。 - Klaim
1
@Klaim:C#拥有比C++更多的函数式特性的本地支持。 - missingfaktor
2
是的,这不违背我所说的。 :) - Klaim
2
@Missing Faktor: 那又怎样?例如,C#对泛型编程的支持并不像C++那样强大。而@Klaim并不是将C++与C#进行独家比较,而是与其他语言进行比较。 - jalf
@Klaim:谢谢你让我开始接触一些很酷的面向对象设计概念 :) - Alok Save
很酷,你已经能够应用所有这些技能了 :) - Klaim

13

没有秘密,只有付出的汗水,而非魔法。

关键不是做一件事情做对,而是平衡多个方面来确保不出差错。有时它们是协同工作的,有时它们会相互制约。设计只是众多方面之一。即使最好的设计也无法帮助项目成功(例如因为它从未发布)。

我提出的第一个规则是:

1. 没有绝对的规则 这严格遵循“平衡众多方面”的原则。D.R.Y.、Y.A.G.N.I.等仅仅是指导性的,严格遵循它们不能够保证好的设计,如果完全依照字面意思遵从,它们可能会导致你的项目失败。

例如: D.R.Y. 是最基本的原则之一,但研究表明,当小代码片段被隔离出来时,由于前/后条件检查、错误处理和泛化到多个相关情况,其复杂性可能增加3倍或更多。因此,该原则需要削弱为“D.R.Y.(至少不要过度)”——何时以及何时不是很难把握的。

第二个规则并不常见:

2. 接口必须比实现简单

听起来太过平凡,以至于不太引人注意。然而,它有很多值得探讨的地方:

对象导向(OO)的前提是管理无法再使用结构化编程进行管理的程序大小。主要机制是封装复杂性:我们可以将复杂性隐藏在更简单的接口后面,然后忘记掉它。

接口复杂度包括文档、错误处理规范、性能保证(或其缺失),等等。这意味着例如通过引入特殊情况来减少接口声明并不是降低复杂度,只是一种搬弄。

3-N 这里是我放置其他大部分提及的内容,已经被解释得非常清楚了。

按照这些准则如何构建软件?

以上的指南有助于评估代码。不幸的是,没有固定的方法可以达到这个目标。“经验”意味着您对如何构建软件有很好的感觉,而且有些决策只是感觉不好。也许所有原则只是后来的理性化。

一般的路径是将系统分解为各种职责,直到每个单独的组件都能够管理。

虽然有正式的过程,但这些过程只是在工作中运作,因为“什么构成了一个良好的、隔离的组件”是一个主观的决定。但最终,我们就是为此而付费的。

如果你对整个系统有一个大致的想法,从这些部件中的任意一个开始作为种子,逐渐发展成一个“核心”,并不会错。自上而下和自下而上并不是对立的。

实践,实践,再实践。编写一个小程序,让它运行,修改需求,让它再次运行。你不需要过多地训练“变更需求”这部分,我们有客户来做这个。

项目后评审——即使是对于个人项目,也要尝试习惯它们。在完成后评估好坏。假设源代码被丢弃了——即不要把那次会议看作“应该修复什么?”

康威定律认为:“一个系统反映了构建它的组织结构。”这适用于我见过的大多数复杂软件,并且正式的研究似乎证实了这一点。我们可以从中得出一些信息:

  • 如果结构很重要,那么你与之合作的人也很重要。
  • 或者结构并不那么重要。没有一个正确的结构(只有很多要避免的错误结构)。

解释来源:https://dev59.com/6nRA5IYBdhLWcg3wvQhh#845984

SOLID原则意味着单一职责、开放/关闭、里氏替换、接口隔离和依赖反转原则。

关注点分离和K.I.S.S.原则在IT技术中也很重要。

D.R.Y.原则指的是“不要重复自己”,这意味着将重复的代码提取到单独的函数或类中,以减少代码的杂乱性和错误。


8

我将引用Marcus Baker在这里的论坛帖子中谈到如何实现良好的面向对象设计:

http://www.sitepoint.com/forums/showpost.php?p=4671598&postcount=24 1)选择一个用例线程。 2)随意实现它。 3)选择另一个线程。 4)以任何方式实现它。 5)寻找共性。 6)将共性收集到函数中进行代码重构。以代码清晰为目标。没有全局变量,一切都要传递。 7)任何不清晰的代码块也要分组成一个函数。 8)以任何方式实现另一个线程,但如果现有函数可以直接使用,则使用它们。 9)一旦工作正常,再次进行重构以消除冗余。此时,您可能会发现从函数到函数传递类似的东西。为了消除冗余,将这些移动到对象中。 10)在您的代码完美之后再实现另一个线程。 11)反复重构以避免重复,直到厌倦为止。 现在是面向对象的部分... 12)现在应该出现一些候选的更高职位。通过类将这些功能分组为角色。 13)再次进行重构,以达到清晰的目的。没有超过几页代码的类,没有超过5行的方法。除非变化只有几行代码,否则不要使用继承。 从这一点开始,您可以循环一段时间... 14)以任何方式实现用例线程。 15)按上述方式进行重构。重构包括根据其含义演变重命名对象和类。 16)重复,直到厌倦为止。 现在是模式部分! 一旦您拥有几十个类和相当多的功能,您可能会注意到某些类具有非常相似的代码,但没有明显的分割(不要使用继承)。此时,请参考模式书籍以了解消除冗余的选项。提示:您可能需要“策略”模式。 以下部分重复... 17)以任何方式实现用例线程的另一个线程。 18)将方法缩小到5行或更少,将类缩小到2页或更少(最好更少),查找模式以删除高级别冗余,如果它确实使代码更清晰,请确保使用单独的工厂类。 19)重复,直到顶层构造函数具有很多参数,或者您发现自己在其他对象内部使用“new”来创建对象(这是不好的)。 现在我们需要清理依赖关系。任何执行工作的类都不应使用“new”在其内部创建东西。从外面传递那些子对象。不进行机械工作的类可以使用“new”运算符。他们只是组装东西-我们称它们为工厂。工厂本身是一个角色。每个类应该只有一个角色,因此工厂应该是单独的类。 20)分离工厂。 现在我们再次重复... 21)以任何方式实现用例线程的另一个线程。 22)将方法缩小到5行或更少,将类缩小到2页或更少(最好更少),查找模式以删除高级别冗余,确保使用单独的工厂类。 23)重复,直到顶层类具有过多的参数(例如8个以上)。 您可能已经完成了。如果没有,请查看依赖注入模式...... 24)使用依赖注入器创建(仅)您的顶层类。 然后您可以再次重复... 25)以任何方式实现用例线程的另一个线程。 26)将方法缩小到5行或更少,将类缩小到2页或更少(最好更少),查找模
很明显,这是一个相当简单的过程,其中包含的信息不应用于每种情况,但我认为Marcus做得很好,特别是关于设计面向对象代码所应使用的过程。一段时间后,你会开始自然地做到这一点,它将变得与生俱来。但在学习时,遵循这一系列步骤是非常好的。

7
正如你所说,经验是无可替代的。你可以阅读地球上所有现有的关于这方面的书籍,但如果不实践,你仍然不会像实践者那样熟练。
理解理论是好的,但在我看来,没有什么比经验更重要。我认为学习和完全理解事物的最佳方式是将它们应用到某些项目中。
在那里,你将面临困难,你将学会解决它们,有时可能会有一个不好的解决方案:但你仍然会学到东西。如果有任何问题困扰着你,你找不到好的解决方法,我们在SO上会帮助你! :)

2

我可以向您推荐一本书,名为“Head First Design Patterns”(在亚马逊上搜索)。它是一个很好的起点,在认真研究四人帮的经典著作之前,它展示了设计原则和最常用的模式。


1
我的看法是:尽管这是一本很好的开始学习设计模式的书,但它并不是学习如何设计的好起点。在让一本书告诉你如何做之前,最好先了解你正在设计的模式是什么。 - stefaanv
1
通常我不认为《Head First》系列对我很有帮助。这本书经常会提供太多分散注意力的内容,使核心信息和概念变得模糊。 - poseid
感谢您提到《Head First 设计模式》这本书。这是一本很棒的书! - Alok Save

2
简而言之:编码,批判,寻找一个知名的解决方案,实施它,然后回到第一步,直到你(或多或少)满意为止。
通常情况下,这种问题的答案是:取决于你学习的方式。我会告诉你我是如何做的,因为我也面临着你所描述的问题,但这并不适用于每个人,我也不会说这是“完美的答案”。
我开始编写一些东西,不要太简单,也不要太复杂。然后,我看着代码想:好吧,哪里出了问题?为此,您可以使用前三个“SOLID原则”:
单一职责原则(您的所有类是否都有唯一的目的?)
开闭原则(如果您想添加服务,则可以通过继承来扩展您的类,但没有必要修改基本函数或当前类)。
里氏替换原则(好吧,这个我无法简单地解释,我建议阅读相关资料)。
不要试图掌握它们并理解其中的所有内容。只需将它们用作指导方针来批判您的代码。主要考虑“如果现在我想这样做会怎样?”。“如果我为客户工作,他想添加这个或那个怎么办?”如果您的代码可以完全适应任何情况(这几乎是不可能的),您可能已经达到了非常好的设计。
如果不行,请考虑解决方案。你会怎么做?试着想出一个主意。然后,阅读有关设计模式的资料,并找到一个可以回答您问题的设计模式。看看它是否符合您的想法 - 通常,它就是您原本想到的,但更好地表达和发展。现在,尝试实施它。这需要时间,你经常会失败,这很沮丧,但这很正常。
设计是关于经验的,但是通过批判自己的代码来获得经验。这就是你理解设计的方式,不是作为一件很酷的东西去了解,而是作为坚实代码的基础。仅仅知道“好的代码具有这个和那个”是不够的。更好的方法是经历过失败并看到哪里出了问题。设计模式的麻烦在于它们非常抽象。我的方法是使它们对您来说不那么抽象的一种方式(可能不是唯一的,也不是最好的)。

1
这里有一个关于SOLID原则的答案:https://dev59.com/6nRA5IYBdhLWcg3wvQhh#845984,虽然我同意SLID听起来更性感 ;) - stefaanv
谢谢分享链接,我需要进去看看 :) 如我之前所提到的,我是一名C++程序员,理解面向对象编程的基本概念,如继承、抽象、多态,并且在写C++代码时了解了一些设计模式。但我不理解应该如何处理需求的基本思考过程,包括如何决定应该制作哪些类,以及如何定义或得出它们之间的关系的细节。 - Alok Save

2
  1. 不要独自工作。好的设计很少由一个人完成。与同事交流。和别人讨论你的设计。并学习。
  2. 不要太聪明。拥有10级继承的复杂层次结构很少是一个好的设计。确保你能清晰地解释你的设计如何工作。如果你无法在5分钟内解释基本原则,你的设计可能太复杂了。
  3. 向大师学习技巧:Alexandrescu,Meyers,Sutter,GoF。
  4. 优先选择可扩展性而非完美。今天编写的源代码在3年后将不足以使用。如果你现在编写完美但不可扩展的代码,你以后会遇到问题。如果你编写可扩展(但不完美)的代码,你仍然可以在以后进行适应。

0

我心中的核心概念是:

  1. 封装 - 尽可能地隐藏对象,使其不受外界窥视和干扰。
  2. 抽象 - 隐藏对象内部工作原理,使需要使用对象的代码更易理解。

所有其他概念,如继承、多态和设计模式,都是关于将上述两个概念融入其中,并仍然拥有能够解决实际问题的对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接