什么应该在一个有助于某人开发优秀的OO软件的清单上?

几年前我使用过OO编程语言和技术(主要是在C ++上),但是在这段时间内,OO没有做太多。

我开始在C#中创建一个小实用程序。 我可以简单地编写它而不使用良好的OO练习,但对我来说应用OO技术将是一个很好的复习。

就像数据库规范化水平一样,我正在寻找一个清单,它会让我想起一个“好的”面向对象程序的各种经验法则 – 一个简明的是/否列表,我可以在设计和实现过程中偶尔阅读以防止我从思考和程序上的工作。 如果它包含适当的OO术语和概念将更加有用,以便任何检查项目都可以轻松搜索以获取更多信息。

什么应该在一个有助于某人开发优秀的OO软件的清单上?

相反,可以应用哪些’测试’来显示软件不是OO?

听起来你想要一些基本的是/否问题来问自己。 每个人都给了一些伟大的“做这个”和“想那样”的列表,所以这里是我的一些简单的是/否的破解。

我可以对所有这些回答是吗?

  • 我的课程代表我所关注的名词吗?
  • 我的类是否提供了它可以执行的动作/动词的方法?

我可以回答所有这些问题吗?

  • 我是否可以将全局状态数据放入单例中或存储在与其一起使用的类实现中?
  • 我可以删除类上的任何公共方法并将它们添加到接口或将它们设置为private / protected以更好地封装行为吗?
  • 我应该使用接口将行为与其他接口或实现类分开吗?
  • 我是否有相关类之间重复的代码,我可以移动到基类中以获得更好的代码重用和抽象?
  • 我是在测试某种类型的东西来决定要采取的行动吗? 如果是这样,这种行为可以包含在所讨论的代码使用的基本类型或接口上,以允许更有效地使用抽象,还是应该重构代码以使用更好的基类或接口?
  • 我是否反复检查一些上下文数据以确定要实例化的类型? 如果是这样,可以将其抽象为工厂设计模式,以便更好地抽象逻辑和代码重用?
  • 这是一个非常庞大的类,具有多个function的焦点? 如果是这样,我可以将它分成多个类,每个类都有自己的单一目的吗?
  • 我是否有从相同基类inheritance的不相关类? 如果是这样,我可以将基类划分为更好的抽象,还是可以使用合成来获取function?
  • 我的inheritance层次结构是否变得可怕? 如果是这样,我可以通过接口或分割function将其展平或分离吗?
  • 我对inheritance层次结构过分担心?
  • 当我向橡皮鸭解释设计时,我觉得愚蠢的设计或愚蠢与鸭子说话?

只是一些快速的我的头顶。 我希望它有所帮助,OOP可以变得非常疯狂。 我没有包含任何是/否的更高级的东西,这些东西通常与较大的应用程序有关,例如dependency injection,或者你应该将某些东西拆分成不同的服务/逻辑层/组件….当然我希望你至少将UI与逻辑分开。

  • 物体做事。 (整个OOP中最重要的一点!)不要将它们视为“数据持有者” – 向他们发送消息来做某事。 我class上应该有什么动词? “责任驱动设计”思想学派对此非常出色。 (参见对象设计:角色,责任和合作 ,Rebecca Wirfs-Brock和Alan McKean,Addison-Wesley 2003,ISBN 0201379430.)
  • 对于系统必须做的每件事, 提出一系列具体的场景,描述对象如何相互通信以完成工作 。 这意味着根据交互图进行思考并执行方法调用。 – 不要从类图开始 – 这是SQL思维而不是OO思维。
  • 学习测试驱动的开发。 没有人能够预先获得他们的对象模型,但是如果你做了TDD,那么你就需要做好基础工作,以确保你的对象模型能够满足它的需要,并在事情发生变化时安全地进行重构。
  • 只针对你现在的要求进行构建 – 不要过分关注“重复使用”或“以后有用”的东西。 如果你现在只构建你需要的东西,你就可以保留以后可以做的事情的设计空间。
  • 在对对象进行建模时忘记inheritance。 这只是实现通用代码的一种方式。 当您对对象进行建模时,只是假装您通过界面查看每个对象,该界面描述了可以要求执行的操作。
  • 如果方法需要加载参数,或者如果需要重复调​​用一堆对象来获取大量数据,则该方法可能位于错误的类中。 方法的最佳位置就在它在同一个类(或超类……)中使用的大多数字段旁边
  • 阅读适合您语言的设计模式书。 如果是C#,请尝试Steve Metsker撰写的“C#中的设计模式”。 这将教你一系列技巧,你可以用它来划分对象之间的工作。
  • 不要测试一个对象以查看它是什么类型,然后根据该类型采取行动 – 这是对象可能正在进行工作的代码味道。 这是一个提示,你应该调用该对象并要求它完成工作。 (如果只有某些类型的对象可以完成这项工作,你可以在某些对象中简单地“无所事事”实现……这是合法的OOP。)
  • 将方法和数据放在正确的类中会使OO代码运行得更快(并为虚拟机提供更好地优化的机会) – 它不仅仅是美学或理论上的。 Sharble和Cohen研究指出了这一点 – 请参阅http://portal.acm.org/citation.cfm?doid=159420.155839 (参见“每个方案执行的指令数量”的指标图表)

从各种书籍,着名的C#程序员和一般建议中收集(如果这些是我的,那就不多了;从某种意义上说,这些是我在开发过程中问自己的各种问题,但就是这样):

  • 结构或类? 你正在创建它自己的值的项目,使它成为一个结构。 如果它是具有属性和子值,方法以及可能状态的“对象”,则将其作为对象。
  • 密封类 :如果您要创建一个类,并且您没有明确需要能够inheritance它,那么它就会被密封。 (我是为了假设的性能增益而做的)
  • 不要重复自己 :如果你发现自己复制过去(a / e)代码,你应该( 但不总是 )重新考虑你的设计,以尽量减少代码重复。
  • 如果您不需要为给定的抽象类提供基本实现,请将其转换为接口。
  • 专业化原则 :你拥有的每个对象应该只做一件事。 这有助于避免“上帝对象”。
  • 使用属性进行公共访问 :这一直在争论,但它确实是最好的事情。 属性允许您通过字段执行通常无法执行的操作,并且还允许您更好地控制对象的获取和设置方式。
  • 单身人士 :另一个有争议的话题,这就是这个想法:只有当你绝对需要时才使用它们。 大多数情况下,一堆静态方法可以满足单例的目的。 (虽然如果你绝对需要一个单身模式,请使用Jon Skeet的优秀模式)
  • 松耦合 :确保你的class级尽可能少地相互依赖; 确保您的图书馆用户可以轻松地将图书馆的部分内容与其他人(或自定义部分)交换出来。 这包括在必要时使用接口,封装(其他人已经提到过),以及本答案中的大多数其他原则。
  • 设计时考虑到简单性 :与蛋糕结霜不同,现在设计简单的东西比现在设计复杂并稍后删除更容易。

如果我是这样的话,我可能会把一些或所有这些扔出门外:

  • 写个人项目
  • 真的急于完成某件事(但我稍后会再回来……有时…..;))

这些原则有助于指导我的日常编码,并在某些方面大大提高了我的编码质量! 希望有所帮助!

史蒂夫麦康奈尔的代码完成2包含许多随时可用的清单,以便进行良好的软件构建。

Robert C. Martin 在C#中的敏捷原则,模式和实践包含了许多优秀OO设计的原则。

两者都将为您提供坚实的基础。

  • 数据属于在其上运行的代码 (即进入同一类)。 这提高了可维护性,因为许多字段和方法可以是私有的( 封装 ),因此在查看组件之间的交互时在某种程度上不考虑。
  • 使用多态而不是条件 – 每当你必须根据对象是什么类做不同的事情时,尝试将该行为放入一个不同类以不同方式实现的方法中,这样你所要做的就是调用该方法
  • 谨慎使用inheritance,更喜欢组合 – inheritance是面向对象编程的一个显着特征,通常被视为OOP的“本质”。 事实上,它严重过度使用,应归类为最不重要的特征

其中一个最好的资源是Martin Fowler的“重构”一书,其中包含面向对象代码气味的列表(和支持细节),您可能需要考虑重构。

我还建议罗伯特马丁的“清洁代码”中的清单。

  • 我有明确的要求吗? 可能没有必要提供正式的需求文档,但在开始编码之前,您应该有一个清晰的愿景。 如果您不需要正式文档,思维导图工具和原型或设计草图可能是不错的选择。 在软件过程中尽早与最终用户和利益相关者合作,以确保您实现他们所需的。

  • 我重新发明轮子了吗? 如果您正在编码以解决常见问题,请寻找已解决此问题的强大库。 如果您认为您可能已经在代码中的其他地方解决了问题(或者同事可能已经解决了),请首先查看现有解决方案。

  • 我的对象有明确的单一目的吗? 遵循封装原则,对象应该具有与其操作的数据一起的行为。 一个对象应该只有一个主要责任。

  • 我可以编码到接口吗? Design By Contract是一种很好的方法,可以实现unit testing,记录详细的,类级别的需求,并鼓励代码重用。

  • 我可以对我的代码进行测试吗? 测试驱动开发(TDD)并不总是那么容易; 但unit testing对于重构非常有用,并且在进行更改后validation回归行为。 与Design By Contract一起携手并进。

  • 我是在过度设计吗? 不要尝试编写可重用组件的代码。 不要试图预测未来的要求。 这些东西看似违反直觉,但它们会带来更好的设计。 第一次编写代码时,只需尽可能直接地实现它,并使其工作。 第二次使用相同的逻辑,复制和粘贴。 一旦您有两个具有通用逻辑的代码工作部分,您就可以轻松地重构而无需预测未来的需求。

  • 我是否介绍了还原剂代码? 不要重复自己(DRY)是重构的最大驱动原则。 仅使用复制和粘贴作为重构的第一步。 不要在不同的地方编写相同的东西,这是一个维护噩梦。

  • 这是一种常见的设计模式,反模式还是代码味? 熟悉OO设计问题的常见解决方案,并在编码时查找它们 – 但不要试图强迫问题适合某种模式。 注意代码属于常见的“坏习惯”模式。

  • 我的代码是否紧密耦合? 松散耦合是一种尝试减少两个或更多类之间的相互依赖性的原则。 一些依赖是必要的; 但是你越依赖另一个class级,你在class级改变时就必须修复得越多。 不要让一个类中的代码依赖于另一个类的实现细节 – 仅根据其合同使用对象。

  • 我暴露了太多信息吗? 练习信息隐藏。 如果方法或字段不需要公开,请将其设为私有。 仅公开对象履行合同所需的最小API。 不要使客户端对象可以访问实现细节。

  • 我在防守方面编码吗? 检查错误情况,并快速失败。 不要害怕使用exception,让它们传播。 如果您的程序达到意外状态,则中止操作,记录堆栈跟踪以供您使用并避免下游代码中的不可预测行为要好得多。 遵循清理资源的最佳实践,例如using() {}语句。

  • 我能在六个月内阅读此代码吗? 良好的代码是可读的,文档很少。 必要时发表评论; 但也要编写直观的代码,并使用有意义的类,方法和变量名。 练习良好的编码风格; 如果您正在开展团队项目,团队的每个成员都应该编写看起来相同的代码。

  • 它仍然有效吗? 早期测试,经常测试。 引入新function后,请返回并触摸可能受影响的任何现有行为。 让其他团队成员进行同行评审并测试您的代码。 重新运行单元在进行更改后进行测试,并使其保持最新状态。

  • 固体
  • TDD
  • inheritance的构成

请务必阅读并理解以下内容

  • 封装
    • (确保只公开最小状态和function以完成工作)
  • 多态性
    • (派生对象的行为与父母一样)
  • 和接口和抽象类之间的区别
    • (抽象类允许function和状态与其后代共享,接口只是实现function的承诺)

我喜欢这个列表,虽然它可能有点密集用作清单。

UML – 统一建模语言,用于对象建模和定义类的结构和关系

http://en.wikipedia.org/wiki/Unified_Modeling_Language

然后当然是OO的编程技术(大多数已经提到过)

  • 信息隐藏
  • 抽象化
  • 接口
  • 封装
  • inheritance/多态

有些规则与语言无关,有些规则因语言而异。

以下是一些与之前发布的规则相矛盾的规则和注释:

  • OO有4个原则:抽象,封装,多态和inheritance。
    阅读它们并记住它们。

  • 建模 – 您的类应该在问题域中为实体建模:
    将问题划分为子域(包/命名空间/程序集)
    然后将每个子域中的实体划分为类。
    类应包含对该类型的对象进行建模的方法。

  • 使用UML,考虑需求,用例,然后是类图,它们是序列。 (主要适用于高级设计 – 主要类别和流程。)

  • 设计模式 – 任何语言的良好概念,语言之间的实现不同。

  • 结构与类 – 在C#中,这是通过值或引用传递数据的问题。

  • 接口与基类,是一个基类,做一个接口。

  • TDD – 这不是OO,事实上,它可能导致设计不足并导致大量重写。 (例如Scrum建议反对它,对于XP来说这是必须的)。

  • C#的reflection在某些方面优先于OO(例如基于reflection的序列化),但它对于高级框架是必要的。

  • 确保类不“知道”其他类,除非它们必须分成多个程序集并且缺少引用帮助。

  • AOP(面向方面​​编程)以一种非常具有革命性的方式增强OOP(例如,参见PostSharp),你至少应该查看它并观察它们的剪辑。

  • 对于C#,阅读MS指南(在VS的MSDN帮助索引中查找指南),它们有很多有用的指导方针和约定

  • 推荐书籍:
    框架设计指南:可重用.NET库的约定,惯用语和模式
    果壳中的C#3.0