【问题标题】:Which class design is more appropriate - dependency, association, or composition哪个类设计更合适——依赖、关联或组合
【发布时间】:2012-06-26 14:37:39
【问题描述】:

我有三种可能的设计,我正在尝试确定哪一种更好,以及在哪些情况下它们会更好。所有设计的总体思路是有一个数据对象来存储一些数据,还有一个分析器对象来分析数据。

我需要在这些设计之间做出选择,我最喜欢设计 2。但是,与我一起工作的开发人员正在推动设计 3,我认为这是最糟糕的。对于任何设计,我是否缺少任何优点或缺点?让我知道是否需要澄清。

设计 1

在设计 1 中,DataAnalyzer 有一个在构造期间提供的 Data 对象。当客户端调用dataAnalyzer.analyze()时,数据被分析。在这个设计中,每个对象的职责是明确的; Data 对象只保存数据,DataAnalyzer 对象分析数据。更改存储的数据只会更改 Data 类,添加分析方法类型只会更改 DataAnalyzer 类。这种设计的一个问题是DataAnalyzer对象只能用于构建时传入的Data对象,所以如果数据对象很多,就需要创建很多DataAnalyzer。另一个缺点(从设计 3 中会更清楚)是客户端需要知道两个类,而不仅仅是一个。如果有更多类与 Data 关联,则客户端将必须使用所有这些类。

设计 2

设计 2 与设计 1 非常相似,只是现在 DataAnalyzer 是可重用的(用于不同的数据对象)。客户端仍然需要使用两个类,而不是设计 3 中的一个类。职责仍然非常明确,维护也很容易。

设计3

Design 3 允许客户使用一个对象。客户可以说data.analyze(),但对DataAnalyzer 一无所知。我不确定这是否违反单一责任规则; Data 对象有一个允许分析的接口,但责任实际上委托给了 DataAnalyzer。另一个问题是,无论数据是否需要分析,都会为每个创建的 Data 对象创建一个 DataAnalyzer。现在,如果添加更多功能,很多事情都会改变。如果创建了 DataPrinter 类(假设这比让数据打印数据本身更好),客户端不必担心创建 dataPrinter 对象并调用dataPrinter.printData(),它可以只调用data.print()。但是,通过添加这个类,我不得不改变 Data 的接口。向任何 DataXX 类添加更多方法会导致向 Data 类添加方法。

【问题讨论】:

  • 如何让 design2 变得“可重用”?
  • 可重复使用,因为一台 DataAnalyzer 可以分析多个 Data 对象。
  • 是方法重载还是 Data 对象现在是通用的?
  • 我不确定你的意思。本质上,如果我创建了一堆 Data 对象,我可以为每个 Data 对象调用dataAnalyzer.analyze(data)。在设计 1 中,我必须为每个 Data 对象创建一个新的 DataAnalyzer。
  • 不同的数据对象有没有可能有共同的功能?

标签: oop design-patterns uml class-design


【解决方案1】:

设计 1 在分析需要很长时间或需要大量资源的情况下很有用,您可以使用某种缓存来使对相同数据的顺序分析运行得更快(或使用更少的资源) .
- 如果您需要存储中间结果或缓存每个正在分析的数据实例的最终结果,请使用此选项。

设计 2 如果您想要一个静态的、线程安全的分析器,它可以为不同的数据反复调用,并且不需要资源来执行它,或者可以从每个请求获取不同的资源资源池。

如果您有多种数据类型(具有相同的基类或接口),您可以使用访问者模式、依赖注入、反射或命令模式从主类到达正确的具体数据分析器类,因此不会违反单一责任原则。

设计 3 是个坏主意,因为这意味着您将数据类型与对其进行的处理相结合 - 每次您想要添加或更改处理时,如果您决定使用这个设计:-)

在某些语言中,例如在 C# 3.0+ 中,有一个 sugar syntax 可以使设计 2 的操作使用设计 3 的语法。

【讨论】:

    【解决方案2】:

    快速解答

    从面向对象的角度很容易想到,Design 3可能会更好,因为“数据”是一个对象,而“分析”似乎不仅仅是一种操作。

    但是,在实践中,正如@Danny Varod 所说:数据的“糟糕耦合”。

    在现实生活中,分析器变化很小,而数据变化很大,从面向对象的角度来看,分析器会改变数据,所以 设计 1 似乎更适合适合你的情况。

    评论

    我在业余时间从事编译器和解析器以及他们分析的编程语言的工作。这种情况看起来与您的问题相似。 Parser 看起来像你的“数据分析器”,而要解析的源代码看起来像你的“数据”。

    关系示例:

    ................................
    ....+----------------------+....
    ....|      CPPParser       |....
    ....+----------------------+....
    ....| [+] CPPStream Source |....
    ....+----------+-----------+....
    ...............|................
    ...............|................
    ...............v................
    ....+----------+-----------+....
    ....|     <<abstract>>     |....
    ....|       CPPStream      |....
    ....+----------------------+....
    ................................
    

    但是,由于我的“数据提供者”,在这种情况下是一个流,可以被后代类替换,例如 FileStream、StringStream。

    继承图示例:

    ............................................................
    ....+----------------------+................................
    ....|      CPPStream       |................................
    ....+----------------------+................................
    ....| [+] CPPStream Source |................................
    ....+----------+-----------+................................
    ...............^............................................
    ...............|............................................
    ...............|............................................
    ...............+---------------------------+................
    ...............|...........................|................
    ...............|...........................|................
    ...............|...........................|................
    ....+----------+-----------+....+----------+-------------+....
    ....|     <<concrete>>     |....|      <<concrete>>      |....
    ....|    CPPFileStream     |....|     CPPFileStream      |....
    ....+----------------------+....+------------------------+....
    ....| [+] String Filename  |....| [+] String StringValue |....
    ....+----------------------+....+------------------------+....
    ............................................................
    

    你的场景怎么样,用一个类表示的数据可以被一个后代类替换吗?

    如果是这样,设计 1 似乎更适合。

    干杯。

    【讨论】:

      【解决方案3】:

      设计 3 不是很理想,因为数据分析器必须始终存在。我敢肯定,在很多情况下,您并不总是需要将该类附加到 DO。另外,我同意它违反了单一责任原则。

      如果不同数据对象类型的内部机制/方法不同,则设计 2 可能会出现一些问题。您每次都必须重新初始化它。我认为这也违反了单一职责原则。数据分析器应该只需要担心一种类型的数据对象。每次有新数据对象进入时,它不应该重新设置和重做其内部工作。

      设计 1 是三者中我最喜欢的,但不是我理想的设计。理想情况下,我会将数据对象传递给分析器工厂。工厂为该特定数据对象生成一个分析器,然后分析器可以完成它的工作。这可能是最易于维护的解决方案。

      【讨论】:

        【解决方案4】:

        看看您的担忧,analyze 似乎是一种静态方法。因此,您可以拥有一个 Utility 类,该类将分析作为接受数据作为输入的静态方法。 dataPrinting 可以是同一实用程序类中的另一个静态方法。如果有多种方法(算法)来分析数据,这种设计就会失败。如果需要,那么策略模式可以为您提供帮助。

        【讨论】:

        • 我认为问题在于不同的数据对象需要不同形式的分析
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-15
        • 2011-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-01
        相关资源
        最近更新 更多