2016-08-02 10 views
3

Ich habe eine .NET-Assembly, die ich erstelle und ich möchte nur ein paar Klassen und Methoden für den Endbenutzer sichtbar sein. Ich erstelle die Assembly mit einem Bibliotheksklasse-Projekt in Framework 4.5 mit Visual Studio 2015. Die Assembly stellt eine Steuerkonsole der Art bereit, um einige zusammenhängende Back-End-Dienste und -Anwendungen zu verwalten. Die meiste Arbeit wird innerhalb der Baugruppe ausgeführt und ist für den Endbenutzer nicht sichtbar. Die meisten Klassen im Namespace der Assembly sind intern und für den Endbenutzer nicht sichtbar. Die Assembly stellt jedoch einige öffentliche Klassen und Methoden bereit, mit denen der Endbenutzer interagieren kann. So wie ich es mir vorstelle, stellt die Assembly eine öffentliche statische Klasse bereit, in der der Endbenutzer die von der Assembly benötigten Parameter eingibt und dann ein Objekt zurückgibt, das der Endbenutzer verwenden kann. Hier ist zum Beispiel eine Pseudocode-Vorlage, wie diese öffentliche statische Klasse konzeptionell aussehen könnte.So implementieren Sie Setter Dependency Injection auf interne Access Modifiers

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; 
    } 
} 

Der Code zeigt, oben, dass die Baugruppe eine DLL-public static Klasse stellt ControlClientStarter genannt, die eine öffentliche statische Methode namens GetSrvcManagerConsole() (...). Diese statische Methode akzeptiert mehrere Parameter und erstellt dann basierend auf diesen Parametern und verschiedenen Logikobjekten Objekte, die die ISrvc_Controller-Schnittstelle implementieren, sowie andere Objekte, die andere Schnittstellen implementieren. Diese Objekte werden in den Konstruktor des srvcMngrConsole-Objekts eingefügt und an den Endbenutzer zurückgegeben.

Der Endbenutzer würde die Steuerkonsole mit etwas konzeptionell ähnlich wie diese zuzugreifen,

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

Das Innere der Steuerkonsole etwas abstrakt wie diese Pseudo-Code aussehen könnte, wenn die Argumente in die SrvcManagerConsole bekommen weitergegeben gespeichert Schnittstelle Umsetzung von Feldern im srvcMngrConsole Objekt,

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 . . . 
} 

Wie Sie sehen können, bin ich einen Konstruktor Dependency Injection mit tight-Kopplung zu vermeiden; Aber es gibt ein Problem damit. Was, wenn ich in Zukunft einen MapPublishApp_Controller, einen VideoSrvc_Controller usw. hinzufügen möchte?

Wenn ich sie nach dem ersten Release zum Konstruktor hinzufüge, muss sich auch anderer Code ändern, der diese Assembly bereits verwendet. Wenn ich einen weiteren SrvceManagerConsole-Konstruktor mit zusätzlichen Parametern hinzufüge, die die neuen Controller übernehmen, dann breche ich den vorherigen Code nicht, aber die Konstruktorparameter werden zu zahlreich. Abhängig von den Parametern, die der Endbenutzer an die GetSrvcManagerConsole() -Methode der ControlClientStarter-Klasse sendet, möchte ich möglicherweise nicht alle Controller verwenden. Es wäre viel besser, wenn ich Methoden haben könnte, die die Controller den Feldern hinzufügen. Die Herausforderung besteht darin, dass ich nicht möchte, dass der Endbenutzer auf diese Felder zugreifen kann. Ich möchte nur die Assembly die Controller-Klassen instanziieren und die versteckten Felder zuweisen. Wenn ich diese Zuweisungsmethoden in den internen Zugriffsmodifizierern der SrvceManagerConsole-Klasse angibt, kann die GetSrvcManagerConsole() -Methode diese nicht anzeigen, da sie öffentlich ist.

Beachten Sie die Zugriffsmodifizierer auf den Schnittstellen. Der einzige, der öffentlich ist, ist derjenige, der von der SrvcManagerConsole-Klasse implementiert wird. Der Rest ist intern, da sie für den Endbenutzer nicht zugänglich sein sollte.

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

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

Diese Methoden oben wie PublishThat() werden die Felder verwenden, die in den Konstruktor injiziert, um die Objekte festgelegt wurde.Der Endbenutzerprogrammierer, der meine Assembly verwendet, wird diese Schnittstellenimplementierungsinstanzen niemals sehen. Das clientMngrConsole-Objekt, das vom Endbenutzerprogrammierer erstellt wird, verwendet diese internen Objekte, ohne dass der Programmierer weiß, wie sie verwendet werden.

Die Klassen, die Schnittstellen mit internen Zugriffsmodifikatoren implementieren wären in etwa so abstrakt sein,

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 { /*. . . */ } 

Es könnte mehr Controller-Klassen in der Zukunft sein, vielleicht auch nicht. Ich konnte nicht herausfinden, wie die Controller - Klassen den Backend - Feldern zugewiesen werden, so dass diese Felder für den Endbenutzer nicht zugänglich sind, während ein gewisses Maß an loser Kopplung beibehalten wird Zukunft. Ich habe jedoch festgestellt, dass, wenn die anderen Methoden und Eigenschaften in der ISrvceManagerConsole-Schnittstelle angegeben sind und diese Schnittstelle mit einem öffentlichen Modifikator gesetzt ist, diese Methoden und Eigenschaften in der SrvceManagerConsole-Klasse, die diese Schnittstelle implementiert, auch als public und festgelegt werden können erfolgreich zugegriffen, obwohl die SrvceManagerConsole-Klasse selbst als "internal" festgelegt wurde. Ich versuchte Property Setter Injektion, aber C# (weise) unterstützt keine Eigenschaft mit einem Interface-Backend (weil das keinen Sinn machen würde). Eine weitere Möglichkeit besteht darin, dass der SrvceManagerConsole-Konstruktor einen Parameter übernimmt, der möglicherweise alle anderen Schnittstelleninstanzen in ein Dictionary einbindet. Das habe ich noch nicht ausprobiert. Ich kann nicht die erste Person sein, die damit konfrontiert wurde. Es muss eine gemeinsame Lösung geben, die ich nicht finden konnte. Ich weiß, dass der Industriestandard ist, IoC-Container für das zu verwenden, was ich oben mache, aber das wird hier nicht gemacht. Ich habe noch nie zuvor eine solche Architektur ausprobiert. Ich weiß, dass dies wahrscheinlich nicht die beste Architektur ist; aber im Moment muss es nicht unbedingt einer übermäßig komplexen Architektur entsprechen. Ich suche ein adäquates Design, da ich mehr darüber lerne.

Edit # 1, warum das Builder-Muster hier nicht

nehme ich nicht funktioniert versuchen, mithilfe eines Builder-Muster wie der Pseudo-Code unter dem Konstruktor anti-Muster zu vermeiden.

Stellen Sie sich vor, dass die SrvcManagerConsole sieht wie folgt aus:

internal class SrvcManagerConsole : ISrvcManagerConsole 
{ 
    internal ISrvc_Controller accountController; 

    internal SrvcManagerConsole() { } 

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

Stellen ich die ControlClientStarter zu so etwas wie dies zu ändern:

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

Und dann realisiere ich das Federated_ControlBuilder wie folgt aus:

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; 
    } 
} 

Die InjectAccountController (...) Methode der Federated_Contr Die olBuilder-Klasse kann die AddAccountController (...) -Methode der SrvcManagerConsole-Klasse nicht sehen, da sie intern ist.

Der Knackpunkt meiner Frage ist nicht, wie man ein Konstruktor-Antimuster an und für sich vermeidet; vielmehr besteht die Herausforderung darin, das Anti-Pattern zu vermeiden, während bestimmte Klassen und Methoden in einer Baugruppe für den Endbenutzer nicht zugänglich sind. Daher wird das Wort "internal" der Frage "Wie setze ich Setter Dependency ein? Injection on interne Access Modifiers."

Wenn es nur darum ging, ein Konstruktor-Anti-Pattern zu vermeiden, hätte ich die Felder der SrvcManagerConsole öffentlich machen und ihnen direkt über die GetSrvcManagerConsole (...) -Methode der ControlClientStarter-Klasse hinzufügen können.

Antwort

0

Ich würde empfehlen, the builder pattern zu verwenden, um das Teleskop-Konstruktor-Anti-Pattern zu vermeiden. Dann muss Ihr Code nur Setter hinzufügen, um neue Features zu implementieren, und Sie lassen Ihren Konstruktor leer.

+0

Vielen Dank für Ihre Empfehlung. Es wird geschätzt. Ich habe eine Antwort in meinem ursprünglichen Beitrag nach dem Abschnitt "Bearbeiten # 1..." – Beebok

0

Ich weiß nicht, ob das eine gute Lösung ist oder nicht, aber es scheint für das zu arbeiten, was ich erreichen wollte. Zusammenfassend wollte ich eine Assembly, bei der die inneren Funktionen nicht zugänglich wären, indem die Zugriffsmodifizierer ihrer Klassen auf intern gesetzt würden, aber einige öffentliche Klassen bereitstellen würden, die den Benutzern, die die Assembly verwenden würden, eine Art Konsolenbenutzeroberflächenfunktionalität bieten würden. Das Problem bestand darin, dass, wenn die öffentlichen Klassen Back-End-Felder hatten, die mit internen Zugriffsmodifikatoren festgelegt wurden, die anderen internen Klassen keine Werte in diese Felder injizieren konnten. Meine Lösung war, zwei Namespaces in der Assembly zu erstellen. In einem Namespace würden alle Klassen auf den internen Zugriffsmodifikator gesetzt sein. Das sind die Klassen für die innere Logik der Versammlung. Der zweite Namespace würde seine Klassen auf public gesetzt haben. Es hätte eine using-Anweisung, die den ersten Namespace referenziert. Diese öffentlichen Klassen im zweiten Namespace können dann Objekte aus Klassen im ersten Namespace anfordern. Die Klassen im zweiten Namespace wickeln dann diese Objekte ein und stellen den Wrapper für den Endbenutzer bereit. Hier

ist ein Beispiel aus dem ersten Namensraum Montag:

// 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; 
     } 
    } 
} 

Hier ist ein Beispiel aus dem zweiten Namensraum Montag mit öffentlichen Klassen zugänglich bis Ende Programmierer.

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();    
     } 
    } 
} 

Hier ist ein Beispiel aus dem Projekt des Endbenutzers.

Der Endbenutzer fügt die Assembly seinen Projektreferenzen hinzu und kann dann versuchen, beide Namespaces zu verwenden; aber nur der zweite Namespace würde Elemente bereitstellen, auf die zugegriffen werden kann. Nur die Klassen in diesem zweiten Namespace (einschließlich des Wrappers) können vom Endbenutzer verwendet werden. Das vom ersten Namespace empfangene und vom Wrapper manipulierte Objekt ist für den Endbenutzer nicht zugänglich. Elemente der ersten Assembly sind dann für das Projekt des Endbenutzers nicht zugänglich und nicht sichtbar, da sie alle auf den internen Zugriffsmodifikator gesetzt sind. Der Endbenutzer manipuliert den Wrapper, und der Wrapper manipuliert seinerseits das vom ersten Namespace empfangene Objekt. Die Methode zur Implementierung der Setter-Abhängigkeitsinjektion für interne Zugriffsmodifikatoren besteht also darin, alle Klassen in einem Namespace auf internal zu setzen, die an der Setter-Abhängigkeitsinjektion teilnehmen. Stellen Sie dann einen zweiten Namespace bereit, der das Objekt aus dem ersten Namespace verwendet, es umschließt und anschließend die öffentlichen Klassen den Endbenutzerprogrammierern zur Verfügung stellt. Jede Rückmeldung und Beratung wird geschätzt. Danke im Voraus.

+0

In einem Ihrer Code-Kommentare sagen Sie "Sie implementieren beide die IManagerConsole-Schnittstelle", aber das sehe ich nicht im Code. Bin ich blind? In jedem Fall war dies eine interessante Lektüre, da ich mit der Kombination von Dependency Injection, Entkopplung von Assemblys und Implementation Hiding (Ihren internen Klassen) zu kämpfen hatte. Mental kam ich zu einer ähnlichen Schlussfolgerung, dass ein Wrapping/Decorator ein guter Weg zur öffentlichen Usability mit interner Implementierung sein könnte. – bubbleking