【问题标题】:Influencing AOP with attributes via IoC; code-smell or elegant?通过 IoC 用属性影响 AOP;代码气味或优雅?
【发布时间】:2010-02-10 08:06:10
【问题描述】:

我目前正在使用 StructureMap,通常使用基于约定 (Scan()) 的自动配置,并且我希望将基于装饰器的缓存添加到管道中。

如果我手动配置它就可以了,但是Scan() 在您获得大量依赖项时非常方便...我正在玩弄注意缓存建议 在接口上,例如:

public interface IFoo {
    [CacheDuration(20)] // cache for 20 minutes
    string[] DoSomethingReusable();

    SomeType DoSomethingNonReusable(int key); // not cached
}

的想法是,通过在StructureMap 的扫描中添加自定义“约定”(非常简单),它可以发现一个或多个方法被装饰用于缓存,并自动将生成的缓存装饰器注入其中类型的管道(从接口/方法名称和参数值生成缓存键)。

从好的方面来说,它使添加缓存变得非常轻松 - 只需稍微装饰一下界面即可;但是代码有味道吗?和/或我是否在复制已经解决的问题?

【问题讨论】:

    标签: c# .net inversion-of-control structuremap aop


    【解决方案1】:

    如果您知道两个主要因素,属性就可以了:

    • 固有的去中心化结构,而不是在一个地方,您的元数据散布在它所接触的每个组件上

    • 刚性编译器常量结构。如果你突然决定你的缓存持续时间应该是 10,而不是 20,你必须重新编译你的所有代码。您可以将属性路由到诸如配置之类的东西,但这是围绕属性工作的,此时您应该真正重新考虑首先使用它们是否是最好的主意。

    只要您了解这些问题并接受它们,就可以继续。

    【讨论】:

    • +1 这个答案提出了我没有想到的几点。更多针对属性的弹药:)
    • 再第二个;对于我们的环境,重建实体与重建注册/配置事物的顶级代码没有什么不同。
    • 它可能不是,重要的部分是 - 它们是编译时常量,具有随之而来的所有限制。
    【解决方案2】:

    据我了解,您主要考虑添加属性以使容器注册更容易。您仍在使用装饰器设计模式实现缓存,即 IMO,实现缓存的正确方法。

    我们在 Safewhere 中执行相同的操作,但我们使用基于约定的注册来代替。我们也使用 Castle Windsor,所以我不知道 StructureMap 是否可行,但我们只需在名为 Caching*Repository 的任何内容之后扫描适当的程序集,并将它们注册为真实存储库的装饰器。我们还有一个基于约定的单元测试,用于验证是否存在所有必需的缓存存储库。

    添加自定义属性是否是代码异味取决于您对代码的可重用程度。我的经验法则是,我希望能够 将所有内容与Poor Man's DI 联系起来。我仍然使用 DI 容器,但这条规则为我提供了健全性检查。

    一般来说,我不喜欢将我的代码耦合到特定容器,但我无法确定您是否在这里这样做。这取决于您是否需要引用 StructureMap 来定义自定义属性。如果您必须参考结构图,我会认为它是一种气味。

    【讨论】:

    • 不,该属性不是特定于容器的。
    【解决方案3】:

    我在这里看到的唯一缺点是没有缓存对象的中心点。通常有一个完成缓存的特定层。以你的技术,情况并非如此。

    我还认为,决定是否缓存某些内容是您的代码的责任,而不是类/接口的责任。

    但要回答您的问题,这是代码异味吗?我会说不,但是一旦在很多地方使用这个系统,新开发人员可能会感到困惑。

    【讨论】:

      【解决方案4】:

      我不能说我完全理解了一切,但是对于我的 2 美分来说,这似乎可以说是可以的;但我只是想知道是否很容易在不重新编译的情况下更改它。也许您希望通过配置条目而不是编译来更改缓存持续时间。我可能会将这些数据存储在我可以更轻松地配置的地方。 (也许我理解错了……)

      【讨论】:

      • 那里的东西; 所有更改(代码/配置/等)都经过相同的过程(构建服务器、验证等);通过系统推送代码更改与更改配置文件一样简单。除了在代码中我有静态类型检查;-p
      • @Marc 确实;但也许您可能想在运行时更改它(即该值可能存储在数据库中?)。我想我喜欢在同一种区域中拥有所有“配置”,然后你可以轻松地改变它。 IE。如果你想把你所有的 20 多岁改成 40 多岁,或者类似的。反正我第一个想到的就是。我不讨厌这个想法,但如果是我,我想我会让它在某处可配置(db,如果你的底层应用程序使用一个)。
      【解决方案5】:

      我倾向于同意关于缓存必须由您的代码决定的说法,但我看到您可能需要考虑的另一种情况是“需要缓存什么,整个对象?

      您可能想玩弄拥有属性来设置您不想缓存对象的哪些成员的想法。

      也许是一个属性

      [Cache(Duration=20, Location(...))]
      

      ?

      【讨论】:

      • 我认为这会很快失控(更多属性)。在这种情况下,您最好设置特定的类来处理特定的缓存“方法”,这些“方法”接受具有各种接口的对象,指定它们希望如何被缓存(或一些这样的方案)。
      • 我完全同意 :) 感谢您的洞察力。
      【解决方案6】:

      我认为,每当您使用属性时,需要考虑的是意图与类的耦合。关键是您不能在一个缓存持续时间为 10 秒的地方使用该实体,而在另一个缓存持续时间为 60 秒的地方使用该实体。恕我直言,这始终是与任何类型属性的权衡。

      【讨论】:

        【解决方案7】:

        我参加这个聚会有点晚了,但我最近一直在思考这个问题,总的来说,使用属性进行缓存是一种代码味道

        以下是几个原因:

        1. 按属性缓存可能会让您陷入一种“摇尾巴”的场景,因为它有可能在您实现适合自己使用的方法时更改代码的设计缓存属性,特别是当您进入复杂场景时。

        2. 按属性缓存是危险的,因为虽然将项目添加到缓存中的过程通常是相同的(即 cache.Add(key, value)),但确定键和要插入的项目的过程并不总是相同的,通常需要某种并不总是通用的逻辑。比如经常想到使用参数值来生成缓存键,但是考虑一下使用了DateTime参数,调用者传入DateTime.Now的场景……返回值可能总是一样的,但是参数生成的key会不同,每次调用都会生成一个新的缓存对象。然后,您可以更改方法的设计,但您将完全陷入 1

        3. 中描述的问题
        4. 一般来说,缓存是用来优化一个特定场景的,很少原因一个item进入缓存是一样的。假设您有一个很少更改的项目目录,您可能决定在应用程序启动时将整个目录加载到缓存中并将其缓存 12 小时,但您可能决定仅在用户登录后缓存用户的用户详细信息第一次。检索这些项目并将它们添加到缓存所需的逻辑完全不同,并且不适合在属性中捕获。

        5. 如果您有缓存属性,那么您还需要一种在缓存项更改时使它们过期的方法。假设您有一个缓存属性,您很可能有一个“CacheExpiry”属性,它会遇到上述相同的问题,但是您还必须想出一种在方法之间生成缓存键的通用方法,因为它会您更新或删除对象的参数与将对象添加到缓存的参数相同的可能性极小。

        底线是,从表面上看,按属性缓存似乎是个好主意,但在实践中,缓存解决的问题的性质意味着它不适合由属性驱动。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-30
          • 1970-01-01
          • 2014-03-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-30
          相关资源
          最近更新 更多