依赖注入和第三方API - 带PI或Wrapper的扩展方法

时间:2014-10-20 19:05:45

标签: c# design-patterns dependency-injection extension-methods composition

我正在开发一个位于第三方CMS之上的C#项目。该团队正在利用依赖注入来促进类之间的松散耦合。

我需要使用多个页面中使用的常用功能“扩展”CMS的api 令人感兴趣的是这些常见功能具有多个依赖关系。 在这种情况下,使用静态扩展方法或创建新接口扩展此功能更合适吗?

上下文

假设第三方有两个与IContentLoader个对象一起使用的接口IPageSecurityPage

namespace 3rdParty.Api
{
    public class Page{}

    public interface IContentLoader{
        T LoadItem<T>(Guid id) where T : Page;
    }

    public interface IPageSecurity
    {
        bool CurrentUserCanReadPage(Page p);   
    }
}

我想写一个常见的方法,如:

public IEnumerable<T> LoadAllChildPagesTheUserCanRead(Guid id) where T:Page
{
    //load page, recursively collect children, then
    //filter on permissions
}

(我承认这个例子有点陈词滥调)

扩展方法

我可以使用Property Injection创建一个静态扩展方法:

public static class IContentLoaderExtensions
{
   public static Injected<IPageSecurity> PageSecurity {get;set;}

   public static IEnumerable<T> LoadAllChildItems(
      this IContentLoader contentLoader, Guid id){}
}

此方法非常容易被发现,我们经常使用IContentLoader,因此团队成员更容易找到它。但是,我read that Property Injection通常不如构造函数注入有用,应尽可能避免使用。

包装

另一方面,我可以创建一个包装器:

public class AdvancedContentLoader
{        
     public AdvancedContentLoader(IContentLoader c, IPageSecurity p){
       //save to backing fields
     }

     IEnumerable<T> LoadAllChildItems(Guid id){}  
}

这种方法允许构造函数注入,它避免了Property Injection的潜在危险,但使得该方法不易被发现。消费者需要知道依赖AdvancedContentLoader而不是使用他们习惯使用的IContentLoader

摘要

在这种情况下,方法具有多个依赖关系,是否可以通过使用扩展方法来提升可发现性,并采用属性注入可能带来的任何脆弱性?或者构造注入是否有利于我创建一个包装类,代价是让方法更难找到?

3 个答案:

答案 0 :(得分:3)

我会更倾向于包装类,但我会为它创建另一个接口。我将其命名为类似,以便开发人员可以找到它。

public interface IContentLoaderWithPageSecurity : IContentLoader
{
    IEnumerable<T> LoadAllChildItems<T>(IContentLoader contentLoader, Guid id) { }
}

新界面但起始名相同,因此intellisense可以帮助开发人员。此接口还必须实现第三方接口。

我会更改您的AdvancedContentLoader类来实现此接口,并将对IContextLoader的所有调用链接到第三方实现,并仅处理它需要处理的特定方法。

public class AdvancedContentLoader : IContentLoaderWithPageSecurity 
{
    private readonly IContentLoader _internalContentLoader;
    private IPageSecurity _pageSecurity;

    public AdvancedContentLoader(IContentLoader contentLoader, IPageSecurity pageSecurity)
    {
        _internalContentLoader = contentLoader;
        _pageSecurity = pageSecurity;
    }

    // Chain calls on the interface to internal content loader
    public T LoadItem<T>(Guid id) where T : Page
    {
        return _internalContentLoader.LoadItem<T>(id);
    }

    public IEnumerable<T> LoadAllChildItems<T>(IContentLoader contentLoader, Guid id)
    {
        // do whatever you need to do here
        yield break;
    }
}

这样做的好处是,如果您使用的是DI Container,只需将IContentLoaderWithPageSecurity接口注册到该类,您仍然可以编写接口。

如果类的名称空间在using指令中,则命名约定可帮助开发人员使用intellisense找到它。

新接口实现旧接口,因此需要IContentLoader的现有代码库仍然可以将IContentLoaderWithPageSecurity传递给这些方法。

如果我不需要新的依赖关系,我只会倾向于扩展方法,而且可能只是已经存在的东西 - 否则你必须得到“智能”并进行属性注入或类似于ConditionalWeakTable来保持额外的状态班级。

我同意Wiktor Zychla认为这开始成为人们的主观意见。

答案 1 :(得分:2)

我建议装饰内容加载器。这种方法遵循SRP原则,你不混合责任 - 我仍然有一个内容加载器,当我想实现加载多个元素时,我将它委托给另一个类。

 public class DecoratedContentLoader : IContentLoader
 {        
     IContentLoader c;
     IPageSecurity p;

     public DecoratedContentLoader(IContentLoader c, IPageSecurity p)
     {
         this.c = c;
         this.p = p;           
     }

     public T LoadItem<T>(Guid id) where T : Page
     {
         var page = c.LoadItem<T>( id );
         if ( p.CanUserReadPage( p ) )
            return p;
         // throw or return null
     }
 }

如您所见,这使用了安全提供程序,但仍然实现了单个项目提供程序接口。

因此,负责加载多个项目的另一个类可以将IContentProvider作为参数,并使用裸的或者装饰的,而不区分两者。

 public class AdvancedContentLoader
 {    
     // no need for additionak parameters, works
     // with any loader, including the decorated one    
     public AdvancedContentLoader( IContentLoader c )
     {
        //save to backing fields
     }

     IEnumerable<T> LoadAllChildItems(Guid id){}  
  }     

答案 2 :(得分:1)

所以,我对此的初步反应是,这里可能会有一些过度思考。如果我正确理解您的问题,您正在尝试找出扩展第三方API的最简单方法。在这种情况下,API有一个您喜欢的界面IContentLoader,您的目标是在此界面中添加另一个方法来启用它:

  • 除了加载给定页面(由Guid定义),
  • 也递归加载所有子页面,
  • 只要用户具有权限(由IPageSecurity负责)。

根据Microsoft

  

扩展方法使您可以向现有类型“添加”方法,而无需创建新的派生类型,重新编译或以其他方式修改原始类型。

如果我理解的话,那正是你在这里要做的。我承认IPageSecurity的结构和功能对我来说没有多大意义,这可能是造成混乱的原因。最重要的是,你有没有理由选择走这条路?或许你的目的很复杂。