如何在内部访问修饰符

时间:2016-08-02 16:20:40

标签: 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类本身被设置为" internal。" 我尝试了属性setter注入,但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 Dependency Injection。“

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

3 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

我不知道这是否是一个好的解决方案,但它似乎正在努力实现我想要实现的目标。 回顾一下,我想要一个程序集,通过将类的访问修饰符设置为内部,可以无法访问内部工作,但是会提供一些公共类,它们将为那些使用程序集的人提供一种控制台用户界面功能。 问题是如果公共类具有使用内部访问修饰符设置的后端字段,则其他内部类不能将值注入这些字段。 我的解决方案是在程序集中创建两个名称空间。一个名称空间将所有类设置为内部访问修饰符。这些是程序集内部逻辑的类。 第二个命名空间将其类设置为public。它将有一个引用第一个命名空间的using语句。 然后,第二个名称空间中的那些公共类可以从第一个名称空间中的类请求对象。然后,第二个名称空间中的类将这些对象包装起来,并为最终用户提供包装器。

以下是程序集第一个命名空间的示例:

// This is the first namespace with all classes set to internal.
namespace ControlClient
{
    internal class ClientStarter
    {
        internal IArcManagerConsole mngrConsole;

        internal ClientStarter() { }

        internal IManagerConsole GetSrvcManagerConsole(/*. all the parameters . */)
        {
            ConsoleBuildDirector cnslBldDirector = new ConsoleBuildDirector();

            IConsoleBuilder fedBuilder = new Federated_ConsoleBuilder();
            cnslBldDirector.DirectBuildingConsole(fedBuilder);

            mngrConsole = fedBuilder.GetSrvcManagerConsole();
            return mngrConsole;
        }
    }
}

以下是程序集的第二个命名空间中的示例,其中公共类可由最终程序员访问。

using ControlClient; // referencing the first name space

// This is the second namespace with public classes.
namespace ClientUserInterface 
{
    /// <summary>
    /// SrvcManagerConsole wraps the mngrConsole.
    /// They both implement the IManagerConsole interface.
    /// </summary>
    public class SrvcManagerConsole
    {
        IManagerConsole srvcManager;

        public SrvcManagerConsole()
        {
            // this.srvcManager = srvcManager;
            ClientStarter ccs = new ClientStarter();
            this.srvcManager = ccs.GetSrvcManagerConsole();
        }

        // The DoThis() method of this wrapper class will
        // call the DoThis() method of the object from the 
        // first namespace that implements the same
        // interface. The end user can't see the 
        // srvcManager object.
        public void DoThis()
        {
            srvcManager.DoThis();            
        }
    }
}

以下是最终用户项目的示例。

using ClientUserInterface;  // This is the second namespace
using ControlClient; // This is the first namespace (all classes set to internal).

namespace EndUser
{
    class EndUser
    {
        public EndUser()
        {       
            // The end user can access public classes from the assembly's
            // second namespace, ArcClientUserInterface as seen below.      
            SrvcManagerConsole sMC = new SrvcManagerConsole();
            sMC.DoThis();      

            // Trying to access anything here from the assembly's 
            // first namespace, ArcControlClient will fail.   
            // Now, all the inner workings of the assembly are undetectable.
        }
    }
}

最终用户将程序集添加到项目引用中,然后可以尝试使用这两个名称空间;但只有第二个命名空间才能提供可以访问的项目。最终用户只能使用第二个命名空间中的类(包括包装器)。从第一个命名空间接收并由包装器操纵的对象对最终用户是不可访问的。 然后,第一个程序集中的项目对最终用户的项目是不可访问的,并且不可见,因为它们都设置为内部访问修饰符。 最终用户操纵包装器,包装器依次操纵从第一个命名空间接收的对象。 因此,在内部访问修饰符上实现setter依赖项注入的方法是将一个名称空间中的所有类设置为内部参与setter依赖项注入。然后提供第二个命名空间,该命名空间使用第一个命名空间中的对象,将其包装,然后将公共类提供给最终用户程序员。 任何反馈和建议将不胜感激。提前谢谢。

答案 2 :(得分:0)

您可以使用InternalVisibleToAttribute允许集成代码操纵内部的setter,但不能操纵其他前端程序集。

此外,我开发的依赖项注入不是基于构造函数注入或成员注入,而是通过指定lambda表达式来告诉您如何创建基础结构实例,从而实现了完全自定义注入。

您可能会在这里找到解决方案的一部分:Puresharp API .net 4.5.2+