【问题标题】:Property initialisation anti-pattern属性初始化反模式
【发布时间】:2013-01-15 00:27:08
【问题描述】:

我不时地以这些行结束代码,在其中创建一些对象,然后循环遍历它们以使用另一个类初始化一些属性...

ThingRepository thingRepos      = new ThingRepository();
GizmoProcessor  gizmoProcessor  = new GizmoProcessor();
WidgetProcessor widgetProcessor = new WidgetProcessor();

public List<Thing> GetThings(DateTime date)
{
    List<Thing> allThings = thingRepos.FetchThings();

    // Loops through setting thing.Gizmo to a new Gizmo
    gizmoProcessor.AddGizmosToThings(allThings);

    // Loops through setting thing.Widget to a new Widget
    widgetProcessor.AddWidgetsToThings(allThings);

    return allThings;
}

...只是,嗯,感觉不对。

  1. 这是个坏主意吗?
  2. 是否有我在这里使用的反模式的名称?
  3. 有哪些替代方案?


编辑:假设GizmoProcessorWidgetProcessor 都必须进行一些计算,并从其他表中获取一些额外数据。它们不仅仅是存储在存储库中的数据。他们正在基于每个 Thing 创建新的 Gizmo 和小部件,并将它们分配给 Thing 的属性。

这让我觉得奇怪的原因是Thing 不是一个自治对象;它不能创建自己和子对象。它需要更高级别的代码来创建一个完全完成的对象。我不确定这是否是一件坏事!

【问题讨论】:

  • 乍一看并没有错,但由于我们不知道在毯子下会发生什么,所以很难说如何改进它
  • 尝试搜索控制反转 (IoC)。如果您还没有听说过,您可能会觉得它很有启发性。
  • 您可能想了解对象关系映射器 (ORM)。基本上,Thing 存储库应根据需要使用 Gizmo 和 Widget 填充事物的属性。如果涉及显着的性能成本,大多数 ORM 系统都允许您为某些属性指定延迟加载。

标签: c# design-patterns initialization anti-patterns


【解决方案1】:

ThingRepository 应该是获取Thing 集合的单一访问点,或者至少这是开发人员直观地看到的地方。出于这个原因,GetThings(DateTime date) 应该由另一个对象提供感觉很奇怪。我宁愿将该方法放在ThingRepository 本身中。

GetThings(DateTime date) 返回的 Thing 是不同的,比 ThingRepository.FetchThings() 返回的那些“更胖”的动物也让人觉得尴尬和违反直觉。如果GizmoWidget 确实是Thing 实体的一部分,那么您应该能够在每次拥有Thing 的实例时访问它们,而不仅仅是GetThings(DateTime date) 返回的实例。

如果GetThings() 中的日期参数不重要或可以在其他时间收集,我将使用Thing 上的计算属性来实现对GizmoWidget 的按需访问:

public class Thing
{
  //...

  public Gizmo Gizmo
  {
    get 
    { 
       // calculations here 
    }
  }

  public Widget Widget
  {
    get 
    { 
       // calculations here 
    }
  }
}

请注意,只要执行的计算成本不太高,这种方法就有效。不推荐使用昂贵处理的计算属性 - 请参阅http://msdn.microsoft.com/en-us/library/bzwdh01d%28VS.71%29.aspx#cpconpropertyusageguidelinesanchor1

但是,这些计算不必在 getter 中内联实现 - 它们可以委托给第三方 Gizmo/Widget 处理器,可能带有缓存策略等。

【讨论】:

    【解决方案2】:

    如果您有复杂的初始化,那么您可以使用策略模式。这是改编自this strategy pattern overview的快速概述

    创建一个策略接口来抽象初始化

    public interface IThingInitializationStrategy
    {
      void Initialize(Thing thing);
    } 
    

    策略可以使用的初始化实现

    public class GizmosInitialization
    {
      public void Initialize(Thing thing)
      {
         // Add gizmos here and other initialization
      }
    }
    
    public class WidgetsInitialization
    {
      public void Initialize(Thing thing)
      {
        // Add widgets here and other initialization
      }
    }
    

    最后是一个以抽象方式接受策略实现的服务类

    internal class ThingInitalizationService
    {
      private readonly IThingInitializationStrategy _initStrategy;
    
      public ThingInitalizationService(IThingInitializationStrategy initStrategy)
      {
         _initStrategy = initStrategy;
      }
    
      public Initialize(Thing thing)
      {
        _initStrategy.Initialize(thing);
      }
    }
    

    然后你可以像这样使用初始化策略

    var initializationStrategy = new GizmosInitializtion();
    var initializationService = new ThingInitalizationService(initializationStrategy);
    
    
    List<Thing> allThings = thingRepos.FetchThings();
    
    allThings.Foreach ( thing => initializationService.Initialize(thing) );
    

    【讨论】:

      【解决方案3】:

      唯一真正的潜在问题是您多次迭代同一个循环,但如果您需要访问数据库以获取所有小工具和小部件,那么批量请求它们可能会更有效,因此通过您的 Add... 方法的完整列表是有意义的。

      另一种选择是考虑在第一个存储库调用中返回 gizmo 和小部件(假设它们驻留在同一个存储库中)。它可能会使查询更复杂,但它可能会更有效。当然,除非你在取东西时并不总是需要获取小工具和小部件。

      【讨论】:

      • 谢谢,我在问题中添加了更多细节。让我们假设性能不是真正的问题,我的问题更多是关于在创建对象后完成对象的对错。
      【解决方案4】:

      回答您的问题:

      1. 这是个坏主意吗?

        • 根据我的经验,在您需要更改之前,您很少知道这是一个好主意还是坏主意。
        • IMO,代码是:过度设计设计不足不可读
        • 与此同时,您尽最大努力并坚持最佳做法(KISS、单一职责等)
        • 就个人而言,我认为处理器类不应该修改任何事物的状态。
        • 我也不认为应该为处理器类提供要修改的事物的集合。
      2. 是否有我在这里使用的反模式的名称?

        • 抱歉,帮不上忙。
      3. 有哪些替代方案?

      就我个人而言,我会这样写代码:

      public List<Thing> GetThings(DateTime date)
      {
          List<Thing> allThings = thingRepos.FetchThings();
      
          // Build the gizmo and widget for each thing
          foreach (var thing in allThings)
          {
              thing.Gizmo = gizmoProcessor.BuildGizmo(thing);
              thing.Widget = widgetProcessor.BuildWidget(thing);
          }
      
          return allThings;
      }
      

      我的理由是:

      1. 代码位于“获取事物”的类中。所以从逻辑上讲,我认为它遍历每个 Thing 对象并对其进行初始化是可以接受的。
      2. 意图很明确:我正在为每个Thing 初始化属性,然后再返回它们。
      3. 我更喜欢在中心位置初始化 Thing 的任何属性。
      4. 我认为 gizmoProcessor 和 widgetProcessor 类不应该与 Collection of Things 有任何关系
      5. 我更喜欢处理器有一种方法来构建和返回单个小部件/gizmo

      但是,如果您的处理器类同时构建多个属性,那么我只会重构每个处理器的属性初始化。

      public List<Thing> GetThings(DateTime date)
      {
          List<Thing> allThings = thingRepos.FetchThings();
      
          // Build the gizmo and widget for each thing
          foreach (var thing in allThings)
          {
              // [Edited]
              // Notice a trend here: The common Initialize(Thing) interface
              // Could probably be refactored into some 
              // super-mega-complex Composite Builder-esque class should you ever want to
              gizmoProcessor.Initialize(thing);
              widgetProcessor.Initialize(thing);
          }
      
          return allThings;
      }
      

      附:

      • 我个人不太关心(反)模式名称。
      • 虽然在更高的抽象层次上讨论问题会有所帮助,但我不会将每个(反)模式名称都记在心里。
      • 当我遇到我认为有用的模式时,我只会记住它。
      • 我很懒惰,我的理由是:如果我只使用少数几个,为什么还要记住每个模式和反模式?

      [编辑]

      注意到已经给出了关于使用策略服务的答案。

      【讨论】:

        猜你喜欢
        • 2013-12-16
        • 2018-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-21
        • 2013-01-06
        • 2017-09-29
        相关资源
        最近更新 更多