c# - 如何在内部访问修饰符上实现Setter依赖注入(inject)

标签 c# .net .net-assembly

我有一个正在构建的.NET程序集,并且我希望最终用户只能看到一些类和方法。我正在使用Visual Studio 2015的框架4.5中的库类项目创建程序集。
该程序集将提供各种控制台来管理一些相互关联的后端服务和应用程序。大部分工作将在装配体内进行,并且对最终用户是不可见的。
程序集名称空间中的大多数类都是内部的,因此对最终用户不可见;但是程序集将提供一些公共类和最终用户可以与之交互的方法。
按照我的设想,程序集将提供一个公共静态类,最终用户将在其中传递程序集所需的参数,然后返回最终用户可以利用的对象。
例如,这是公共静态类在概念上看起来像的伪代码模板。

public static class ControlClientStarter
{
    public static ISrvcManagerConsole GetSrvcManagerConsole(/*. all the parameters . */)
    {
        // Some code that parses the parameters . . . 

        ISrvc_Controller accountController = new AccountSrvc_Controller( /*. . . */ );
        ISrvc_Controller contractController = new ContractSrvc_Controller( /*. . . */ );
        ISrvc_Controller imageController = new ImageSrvc_Controller( /*. . . */ );
        ICatalogApp_Controller calatlogController = new CatalogApp_Controller( /*. . . */ );
        IPortal_Controller portalController = new PortalSrvc_Controller( /*. . . */ );

        ISrvcManagerConsole srvcMngrConsole = new SrvceManagerConsole(
            accountController,
            contractController,
            imageController,
            calatlogController,
            portalController);

        return srvcMngrConsole;
    }
}


上面的代码演示了程序集.dll提供了一个称为ControlClientStarter的公共静态类,该类具有一个名为GetSrvcManagerConsole()(..)的公共静态方法。
该静态方法接受几个参数,然后基于这些参数和各种逻辑,创建实现ISrvc_Controller接口的对象以及实现其他接口的其他对象。
这些对象被注入到srvcMngrConsole对象的构造函数中,并将其返回给最终用户。

最终用户将使用类似概念的内容访问控制台,

ISrvcManagerConsole clientMngrConsole = ControlClientStarter.GetSrvcManagerConsole(  /*. . . */ );
clientMngrConsole.DoThis(  /*. . . */ );
clientMngrConsole.StopThis(  /*. . . */ );
clientMngrConsole.GetThat(  /*. . . */ );
clientMngrConsole.PublishThat(  /*. . . */ );


控制台内部可能看起来像这样的伪代码,其中传递到SrvcManagerConsole的参数保存到srvcMngrConsole对象内的接口实现字段中,

internal class SrvcManagerConsole : ISrvcManagerConsole
{
    ISrvc_Controller accountController ;
    ISrvc_Controller contractController ;
    ISrvc_Controller imageController ;
    ICatalogApp_Controller calatlogController;
    IPortal_Controller portalController ;

    internal SrvcManagerConsole(
        ISrvc_Controller accountController,
        ISrvc_Controller contractController,
        ISrvc_Controller imageController,
        ICatalogApp_Controller calatlogController,
        IPortal_Controller portalController)            
    {
        this.accountController = accountController;
        this.contractController = contractController;
        this.imageController = imageController;
        this.calatlogController = calatlogController;
        this.portalController = portalController;
    }

    public void DoThis( /*. . . */ )
    {  /*. . . */ }
    public void StopThis( /*. . . */ )
    {  /*. . . */ }
    public void GetThat( /*. . . */ )
    {  /*. . . */ }
    public void PublishThat( /*. . . */ )
    {  /*. . . */ }

    // private and-or internal methods and properties . . . 
}


如您所见,我正在使用构造函数依赖项注入来避免紧密耦合。但这是有问题的。如果将来我想添加MapPublishApp_Controller,VideoSrvc_Controller等怎么办?

如果在第一个发行版之后将它们添加到构造函数中,那么也必须更改已经使用此程序集的其他代码。
如果我添加另一个带有附加参数的SrvceManagerConsole构造函数,这些参数采用新控制器,那么我不会破坏以前的代码,但是构造函数参数将开始变得过多。
另外,根据最终用户发送到ControlClientStarter类的GetSrvcManagerConsole()方法的参数,我可能不想使用所有控制器。如果我可以使用将控制器添加到字段中的方法,那就更好了。
面临的挑战是,我不希望最终用户能够访问这些字段。我只希望程序集实例化控制器类并分配隐藏字段。如果我在SrvceManagerConsole类的内部访问修饰符中提供了那些分配方法,则GetSrvcManagerConsole()方法将看不到它们,因为它是公共的。

注意接口上的访问修饰符。唯一公开的是由SrvcManagerConsole类实现的。其余的是内部的,因为最终用户不应该访问它们。

internal interface ISrvc_Controller { /*. . . */ }
internal interface ICatalogApp_Controller { /*. . . */ }
internal interface IPortal_Controller { /*. . . */ }

public interface ISrvcManagerConsole
{
    void DoThis( /*. . . */ );
    void StopThis( /*. . . */ );
    void GetThat( /*. . . */ );
    void PublishThat( /*. . . */ );
}


像PublishThat()这样的上述方法将使用已设置为注入到构造函数中的对象的字段。使用我的程序集的最终用户程序员将永远不会看到那些实现实例的接口。最终用户程序员创建的clientMngrConsole对象将使用这些内部对象,而程序员甚至不知道它们的用法。

用内部访问修饰符实现接口的类将抽象为如下形式:

class AccountSrvc_Controller : ISrvc_Controller { /*. . . */ }
class ContractSrvc_Controller : ISrvc_Controller { /*. . . */ }
class ImageSrvc_Controller : ISrvc_Controller { /*. . . */ }
class CatalogApp_Controller : ICatalogApp_Controller { /*. . . */ }
class PortalSrvc_Controller : IPortal_Controller { /*. . . */ }


将来可能会有更多的控制器类,也许没有。
我一直无法弄清楚如何将控制器类分配给后端字段,以使最终用户无法访问这些字段,同时保持某种程度的松散耦合,以防将来需要更改这些控制器的实现。
但是,我发现,如果在ISrvceManagerConsole接口中声明了其他方法和属性,并且使用public修饰符设置了该接口,则实现该接口的SrvceManagerConsole类中的那些方法和属性也可以设置为public并成功尽管将SrvceManagerConsole类本身设置为“内部”,但仍可访问。
我尝试了属性设置器注入,但是C#(明智地)不支持带有接口后端的属性(因为这没有任何意义)。
另一种可能性是让SrvceManagerConsole构造函数采用一个参数,该参数可能将所有其他接口实例包装在字典中。我还没有尝试过。
我不能成为第一个遇到这个问题的人。我必须找不到一个通用的解决方案。
我知道行业标准是将IoC容器用于上述操作,但这不是在这里要做的。
我以前从未尝试过这种架构。我知道这可能不是最佳架构。但目前不必完全按照过于复杂的架构进行。我正在寻求适当的设计,因为我会对此有所了解。

编辑#1,为什么Builder模式在这里不起作用

假设我尝试通过使用类似下面的伪代码的构建器模式来避免构造器反模式。

想象一下SrvcManagerConsole看起来像这样:

internal class SrvcManagerConsole : ISrvcManagerConsole
{
    internal ISrvc_Controller accountController;

    internal SrvcManagerConsole() { }

    internal void AddAccountController(ISrvc_Controller accountController)
    {
        this.accountController = accountController;
    }
}


想象一下,我将ControlClientStarter更改为以下内容:

public static class ControlClientStarter
{
    public static ISrvcManagerConsole GetSrvcManagerConsole(/*. all the parameters . */)
    {
        Federated_ControlBuilder fedBuilder  = new Federated_ControlBuilder();
        ISrvcManagerConsole srvcMngrConsole = fedBuilder.GetSrvcManagerConsole();
        return srvcMngrConsole;
    }
}


然后像这样实现Federated_ControlBuilder:

internal class Federated_ControlBuilder : IControl_Builder
{
    internal ISrvcManagerConsole srvcMngrConsole;

    internal Federated_ControlBuilder()
    {
        srvcMngrConsole = new SrvcManagerConsole();
    }

    public void InjectAccountController(ISrvc_Controller accountController)
    {
        // The srvcMngrConsole object can not see the InjectAccountController method.
        // srvcMngrConsole.
    }

    internal ISrvcManagerConsole GetSrvcManagerConsole()
    {
        return srvcMngrConsole;
    }
}


Federated_ControlBuilder类的InjectAccountController(…)方法看不到SrvcManagerConsole类的AddAccountController(…)方法,因为它是内部的。

我的问题的重点不是如何本身避免构造函数的反模式;而是如何避免构造函数的反模式。但是,挑战在于,如何避免使用反模式,同时使最终用户程序员无法访问程序集中的某些类和方法,因此,使用“如何在以下位置实现Setter依赖注入”问题的“内部”一词内部访问修饰符。”

如果仅是避免使用构造函数反模式的问题,我可以将SrvcManagerConsole的字段公开,并通过ControlClientStarter类的GetSrvcManagerConsole(…)方法直接添加到它们。

最佳答案

我建议使用the builder pattern以避免伸缩构造函数反模式。然后,您的代码只需要添加setter即可实现新功能,而将构造函数留空。

关于c# - 如何在内部访问修饰符上实现Setter依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38725767/

相关文章:

c# - 将字符串数据表示为树

c# - 从 JsonContract 中读取 JSON 属性名称

c# - Azure sb lite 连接字符串和事件中心兼容名称

c# - 没有给定的参数对应于所需的形式参数 - .NET 错误

c# - 从其他项目打开 wpf 窗口

c# - 类是否还应该负责将自身的数据存储到数据库中?

c# - 如果我为操作系统版本不支持的 API 使用 DllImport (C# .NET),会发生什么情况

.net - 在 WPF 中,如何从包含的 ListBox 的 DataTemplate 内部将数据绑定(bind)到 Window DataContext?

win-universal-app - 如何从 UWP 中的路径加载程序集

sql - 在 SQL Server 2012 中注册 CLR 函数(基于 WCF)