【问题标题】:Question regarding the "Tell, don't Ask" idea关于“告诉,不要问”想法的问题
【发布时间】:2010-10-20 23:51:34
【问题描述】:

有一句名言是这样说的

然后程序代码获取信息 做出决定。面向对象的代码 告诉对象做事。 — 亚历克 锋利

这篇文章的主题正是关于这个。

假设我们正在开发一个游戏,其中我们有一个Game,其中有一个Board。 当面临决定在Board类上实现哪些方法的问题时,我总是想到两种不同的方法:

第一种方法是

getSize()getPieceAt(x, y)setPieceAt(x, y, piece) 填充Board 类。这看起来很合理,并且通常可以在库/框架中找到。 Board 类具有一组想要共享的内部特性,并具有一组允许类的客户端按照他的意愿控制类的方法。客户应该询问他需要的东西并决定做什么。如果他想将所有棋子设置为黑色,他将“手动”迭代它们以实现该目标。

第二种方法是关于

寻找Board 的依赖类,看看他们“告诉”它做什么。 ClassA 想计算有多少块是红色的,所以我会实现一个calculateNumberOfRedPieces()ClassB 打算清除Board 上的所有部分(例如,将它们全部设置为NullPiece),所以我将在Board 类中添加一个clearBoard() 方法。这种方法不太通用,但在其他方面允许更大的灵活性。如果我在IBoard 接口后面“隐藏”Board,并决定我想要一个无限大小的板,以第一种方式做,我会被卡住,因为我必须迭代无限数量的物品!另一方面,通过这种方式,我可以做得很好(例如,我可以假设所有片段都为空,而不是包含在哈希表中的片段!)。

所以...

我知道如果我打算创建一个库,我可能会坚持第一种方法,因为它更通用。另一方面,我想知道当我完全控制将使用Board 类的系统时,应该采用哪种方法——当我将同时设计所有将使用Board 的类。目前和将来(如果以后我决定添加依赖于具有不同“愿望”的Board 的新类,第二种方法不会引发问题吗?)。

【问题讨论】:

  • 他在问什么更有意义。选项 1、选项 2、选项 3(其他)
  • 我不明白(常见的)“面向对象”代码如何不仅仅是带有对象的过程代码。
  • @pst。你说的很对,顶层总是有一个main函数被调用,另外还有静态方法。但是,OO 设计的目标是尽可能多地删除程序代码,使它们更易于管理。

标签: c# java oop


【解决方案1】:

这句话真的是在警告您远离对它们所持有的数据不做任何事情的数据结构。因此,第一种方法中的 Board 类可能可以被通用集合取消并替换。

无论如何,Single Responsibility Principle 仍然适用,因此您需要谨慎对待第二种方法。

我要做的是调用 YAGNI(你不会需要它)并尝试看看使用泛型集合而不是 Board 类我能走多远。如果您以后发现确实需要 Board 类,到那时它的职责可能会更加清晰。

【讨论】:

  • -1 我不同意。 YAGNI 没那么简单,恕我直言。您所描述的方法可能仅适用于只有一个开发人员的非常小的项目,但是即使有一个额外的开发人员,它也会惨遭失败,因为在团队中工作时,您迟早会拥有同意某种 类型的架构,经过几周的开发,你肯定不会说“不,这不起作用,让我们在这里和那里添加一些类”。 YAGNI 是关于保持简单,而不是开始盲目地奔跑,如果你走错了路,就回到起点!
  • YAGNI 背后的核心理念是尽可能推迟做出决定,以便在您真正需要做出这些决定时,您可以获得最好的信息来为这些决定提供信息。当然,团队环境会影响何时需要做出决定。就个人而言,我发现关键决策点发生的时间比许多人预期的要晚得多。
  • 引用this source:“这并不意味着您应该避免在代码中构建灵活性。这意味着您不应该根据您认为以后可能需要的东西过度设计某些东西。”但是,我认为如果您决定将 Board 建模为通用集合,这正是您要做的:放弃大量的灵活性,这意味着您以后可能必须重写大量代码。
  • C2 是一个很好的资源,我建议大家在某个阶段看看。但 YAGNI 即使在那里也有些争议,所以在这里提供规范的解释肯定超出了我的能力。
  • Board 对象及其 getter 和 setter 以及没有与游戏相关的逻辑已经遵循 SRP:它的职责是表明它所持有的数据在一个板上集中在一起。将业务逻辑放入其中会违反 SRP。我会将Board 类称为一种数据结构,您可以请求数据而不是告诉它如何处理数据。每个系统在某些时候都需要这些数据结构。
【解决方案2】:

让我提供逆向观点。我认为第二种方法有腿。我同意单一责任原则,但在我看来,Board 班级有一个合理的单一任务/关注点:维护竞争环境。

我可以想象一组非常合理的方法例如getSize()getPiece(x,y)setPiece(x, y, color)removePiece(x, y)movePiece(x1,y1,x2,y2)clear()countPieces(color)listPiecePositions(color)read(filename)write(filename)等有明确的共同使命。以抽象的方式处理这些棋盘管理问题将允许其他类更干净地实现游戏逻辑,并且BoardGame 将来更容易扩展。

YAGNI 一切都很好,但我的理解是,它敦促您不要开始建造美丽的大厦,希望有一天它们会被有用地占用。例如,我不会花任何时间来研究未来无限的游戏表面、3D 游戏表面或可以嵌入球体的游戏表面的可能性。如果我想非常认真地对待 YAGNI,在需要它们之前,我不会编写简单的 Board 方法。

但这并不意味着我会放弃 Board 作为一个概念组织或可能的类。这当然并不意味着我根本不会考虑如何在我的程序中分离关注点。至少在我的世界中,YAGNI 不需要您从最低级别的数据结构开始,很少或根本不需要封装,以及完全程序化的方法。

我不同意第一种方法更通用(以任何有用的方式)的观点,或者认为应该“只看在不抽象任何东西的情况下你能走多远”的共识。老实说,这听起来像是我们解决eight queens 的方式。 1983 年。在帕斯卡。

YAGNI 是一个伟大的指导原则,它有助于避免很多 second system effect 和类似的自下而上的错误,我们可以这样做,所以我们应该这样做。但是越过Agile Practice Stupidity Threshold 的 YAGNI 并不是美德。

【讨论】:

    【解决方案3】:

    CurtainDog 是对的,调用 Yagni 并找出您现在真正需要的东西,实现它,然后确保它不会妨碍将来可能需要的任何功能。

    第二种方法违反了超类不应该知道它的每个子类的原则。我认为您缺少的元素是基类可以定义模板方法,例如 getBoardSize、countRedPieces、countBlackPieces,它们可以被子类覆盖,并且您的超类具有使用这些模板方法的代码,因此告诉其子类该做什么,但不是怎么做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-19
      • 1970-01-01
      • 2011-01-06
      相关资源
      最近更新 更多