【问题标题】:Proper Architecture for Application-Level Collections应用程序级集合的适当架构
【发布时间】:2016-03-03 10:48:47
【问题描述】:

鉴于应用程序范围内的对象集合,以及许多需要频繁访问这些对象的不相关类,提供上述访问权限的最佳方式是什么?

例子:

// Object A, stored in collections, used to do useful things
class A
{
  ...
public:
  QString property(const QString& propertyName) {return m_properties.value(propertyName);}

protected:
  QHash<QString,QString> m_properties;
}

// Collection class, contains methods to:
// - Access members of collections
// - Add/Remove members from collection
class GlobalCollection
{
public:
  // Accessors to collection/collection members
  static A* getAs() {return aHash;}
  static QHash<QString,A*> getAByKey(const QString& key) {return aHash.value(key);}
  static QList<A*> getAsMatchingCriteria(const QString& property, const QString& value)
  {
    QHash<A*> subsetOfA;

    foreach(A* pA, aHash.values())
    {
      if (pA->property(property) == value)
        subsetOfA << pA;
    }

    return subsetOfA;
  }

protected:
  QHash<QString,A*> aHash;
}

// Example client class that uses A's to do its job
class Client
{
public:
  // This is tied to a button click, and is executed during run-time at the user's whim
  void doSomethingNonTrivialWithAs()
  {
    // Get A* list based on criteria, e.g. "color" == "green"
    QList<A*> asWeCareAbout = ???;

    // Draw all the "green" A's in a circle holding hands
    foreach(A* pA, asWeCareAbout)
    {
      // Draw a graphical representation of pA
      // If pA has "shape" == "square", get a list of all the non-"green" "square" A's and draw them looking on jealously from the shadows
      // Else if pA has "shape" == "circle", draw the non-"green" "circles" cheering it on
    }
  }
}

假设:

  • 优先考虑小型、轻量级的类,因此客户端对象数量众多
  • 客户端对象可能在 GlobalCollection 的“对等体”内部有好几层,而中间层不依赖于 A* 或 GlobalCollection
  • 目前这是作为单例实现的

其他解决方案的设计要求和问题:

  • 依赖注入看起来像是调用代码的不合理负担(考虑到分层),并且为了我的喜好牺牲了太多的清晰度
  • 我不反对静态类而不是单例,但感觉并不比单例好多少
  • 修改集合的代码是隔离的,所以我现在不担心
  • 该解决方案需要在 GlobalCollection 和 A 中提升线程安全性(假设多个客户端最终可能在同一个 A* 上工作。)目前这是通过一个互斥锁和过度锁定来实现的,很大程度上是因为它是很难管理对 A 的访问。
  • 我正在尝试迭代以实现可测试性,而当前的设计使得客户端的几乎每个测试都需要首先正确设置 GlobalCollection。
  • 在生产代码中,我们有多个 GlobalCollections(用于 A、B、C 等),因此欢迎使用模板解决方案。

虽然我正在重构遗留代码来做到这一点,但我主要关心的是首先设计正确的架构。这似乎是一个非常常见的逻辑概念,但我看到的所有解决方案都未能解决将其用于生产的某些重要方面,或者存在明显的缺陷/权衡。也许我太挑剔了,但根据我的经验,适合这项工作的工具在这种情况下的缺点为零。

【问题讨论】:

    标签: c++ design-patterns architecture refactoring legacy-code


    【解决方案1】:

    有一个干净、可维护和可测试的解决方案,但您在要求中拒绝它:

    依赖注入看起来像是调用代码的不合理负担(考虑到分层),并且为了我的喜好牺牲了太多的清晰度

    我现在将忽略该要求。如果您真的想避免依赖注入(我不推荐),请参阅我的答案的结尾。

    设计集合对象

    • 围绕实际集合创建一个包装器(就像您已经做的那样)是一个好主意。它使您可以完全控制客户端与集合的交互(例如关于锁定)。
    • 不要让它成为静态的。以您可以实例化集合、使用它并最终删除它的方式设计它。毕竟,标准库和 Qt 的所有集合也是如此。
    • 为集合对象引入一个接口。

    设计集合访问机制

    解决方案需要提升线程安全

    这需要一个类似工厂的中介:创建一个提供对集合的访问的工厂。然后,工厂可以决定何时退回新系列或现有系列。确保客户在收集完成后将其归还,以便您知道有多少客户在使用它。

    现在所有客户端都通过工厂访问集合。他们只看到接口,看不到真正的实现。

    获取对工厂的引用

    现在我们介绍了工厂,客户不再需要知道直接(静态)访问集合。但是,他们仍然需要控制工厂本身。

    通过将工厂注入到客户端的构造函数中,使工厂成为依赖项。这种设计清楚地传达了客户依赖工厂的信息。它还使您能够在测试期间关闭工厂,例如用模拟替换它。

    请注意,使用依赖注入并不意味着您需要使用 DI 框架。重要的是拥有一个干净、定义明确的composition root

    避免 DI

    正如我已经说过的,不建议这样做。 DI 是强调可测试性的干净、解耦设计的基础。

    如果还想避免DI,那么将上面的设计修改如下:

    • 创建一个提供工厂访问权限的单例。
    • 从所有客户端通过该单例访问工厂。
    • 保持集合和工厂不变,即非静态且不知道任何单例。

    补充说明

    你的收藏和使用听起来很像repository pattern。我上面的设计建议与这种相似性一致(例如,以狭窄范围的方式访问集合并“将它们归还”)。我认为阅读存储库模式将帮助您正确设计。

    【讨论】:

    • 抱歉,后续跟进晚了。存储库模式也是我考虑过的,但是 Fowler 的页面没有提供任何东西,除了它是完美的还是矫枉过正的。我在其他地方运气不佳,但我可能还没有专门搜索它。
    • Re: DI,组合根是使用集合的东西的根吗?因为客户端示例类可能是在单击按钮时创建的,我不确定如何使用该概念来改进此上下文中的代码。我觉得这会导致许多类存储一个指向工厂的成员指针,其唯一目的是将其传递给子类。它可能仍然更干净,但我觉得它意味着当只存在间接依赖时它意味着直接依赖。
    • @JohnNeuhaus 如果某物的生命周期较短,那么处理这种情况的正确方法是通过工厂。像这样,您依赖于工厂和Client 接口。如果你更新它,你依赖Client 的构造机制,这是一个实现细节。所以从这个角度来看,DI 解决方案也更清洁。
    • 公平积分。我想我只是在避免 DI,因为其他架构问题正在让它变得痛苦。
    猜你喜欢
    • 2011-03-12
    • 1970-01-01
    • 1970-01-01
    • 2017-09-02
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 2012-01-31
    • 2014-04-11
    相关资源
    最近更新 更多