2009-11-03 17 views
43

Czy można określić, że klasa zagnieżdżona może być dostępna dla klasy otaczającej, ale nie dla innych klas?Jak ograniczyć dostęp do zagnieżdżonego elementu klasy do otaczającej klasy?

Oto ilustracja problemu (oczywiście mój rzeczywisty kod jest nieco bardziej skomplikowana ...):

public class Journal 
{ 
    public class JournalEntry 
    { 
     public JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    // ... 
} 

Chciałbym zapobiec kod klienta z tworzenia wystąpień JournalEntry, ale musi być Journal w stanie je stworzyć. Jeśli udostępniam konstruktor jako publiczny, każdy może tworzyć instancje ... ale jeśli zrobię to prywatnie, Journal nie będzie mógł!

Należy pamiętać, że klasa JournalEntry musi być publiczna, ponieważ chcę mieć możliwość wyświetlania istniejących wpisów w kodzie klienta.

Każda sugestia zostanie doceniona!


UPDATE: Dzięki wszystkim za wkład, w końcu poszedł do publicznej IJournalEntry interfejs, realizowanego przez prywatnego JournalEntry klasy (pomimo ostatniego wymogu moje pytanie ...)

+1

Można dokonać JournalEntry (Object) konstruktor 'wewnętrzne'; to uniemożliwiłoby innym zespołom tworzenie instancji kroniki, ale inne klasy w tym samym zespole nadal mogą je tworzyć; jednak jeśli jesteś autorem tego zgromadzenia, może to być wystarczająco dobre. –

+0

tak, myślałem o tym, ale wolałbym nie móc tworzyć instancji nawet w tym samym zespole ... i tak dziękuję! –

Odpowiedz

33

Jeśli klasa nie jest zbyt skomplikowane, można użyć interfejsu, który jest publicznie widoczny i sprawiają, że rzeczywista klasa realizacji prywatnych lub można zrobić chroniony konstruktor dla klasy JornalEntry i prywatnej klasy JornalEntryInstance pochodzącej z JornalEntry z publicznym konstruktorem, który jest faktycznie tworzony przez twoją Journal.

public class Journal 
{ 
    public class JournalEntry 
    { 
     protected JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    private class JournalEntryInstance: JournalEntry 
    { 
     public JournalEntryInstance(object value): base(value) 
     { } 
    } 
    JournalEntry CreateEntry(object value) 
    { 
     return new JournalEntryInstance(value); 
    } 
} 

Jeśli rzeczywisty klasa jest zbyt złożona, aby wykonać jedną że można uciec z konstruktor nie jest całkowicie niewidoczny, można dokonać konstruktor wewnętrzny jest więc widoczny tylko w zespole.

Jeśli to też jest niemożliwe zawsze można dokonać konstruktora odbicie prywatnych oraz wykorzystuje zadzwonić z twojej klasy Journal:

typeof(object).GetConstructor(new Type[] { }).Invoke(new Object[] { value }); 

Teraz, gdy o tym myślę, inna możliwość użyłby prywatną delegata w zawierający klasy, która jest ustawiona od wewnętrznej klasy

public class Journal 
{ 
    private static Func<object, JournalEntry> EntryFactory; 
    public class JournalEntry 
    { 
     internal static void Initialize() 
     { 
      Journal.EntryFactory = CreateEntry; 
     } 
     private static JournalEntry CreateEntry(object value) 
     { 
      return new JournalEntry(value); 
     } 
     private JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    static Journal() 
    { 
     JournalEntry.Initialize(); 
    } 

    static JournalEntry CreateEntry(object value) 
    { 
     return EntryFactory(value); 
    } 
} 

To powinno dać żądane poziomy widoczności bez konieczności uciekania się na powolne odbicie lub wprowadzenie dodatkowych klas/interfejsów

+0

Interfejs to dobre rozwiązanie, nie wiem, dlaczego o tym nie myślałem ... Dzięki! –

+3

Pierwsze podejście ma dużą wadę: inne klasy mogą dziedziczyć również z JournalEntry, a następnie wywoływać konstruktora. –

2

W tym przypadku może albo:

  1. bądź konstruktor wewnętrzny - to przestaje spoza tego zgromadzenia tworzenia nowych instancji albo ...
  2. Refactor klasa JournalEntry używanie interfejs publiczny i uczyń rzeczywistą klasę JournalEntry prywatną lub wewnętrzną. Interfejs może być następnie odsłonięty dla kolekcji, podczas gdy rzeczywista implementacja jest ukryta.

Wspomniałem wewnętrzny jako poprawny modyfikator powyżej, jednak w zależności od wymagań, prywatna może być lepiej dopasowaną alternatywą.

Edytuj: Przepraszam, że wspomniałem o prywatnym konstruktorze, ale już poradziłeś sobie z tym punktem w swoim pytaniu. Przepraszam, że nie przeczytałem tego poprawnie!

1

chciałbym zrobić konstruktora JournalEntry wewnętrzny:

public class Journal 
{ 
    public class JournalEntry 
    { 
     internal JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    // ... 
} 
15

Utwórz JournalEntry jako prywatny zagnieżdżony typ . Wszyscy członkowie publiczni będą widoczni tylko dla typu obejmującego.

public class Journal 
{ 
    private class JournalEntry 
    { 
    } 
} 

Jeśli trzeba dokonać JournalEntry obiektów dostępnych dla innych klas, wystawiać je za pośrednictwem interfejsu publicznego:

public interface IJournalEntry 
{ 
} 

public class Journal 
{ 
    public IEnumerable<IJournalEntry> Entries 
    { 
     get { ... } 
    } 

    private class JournalEntry : IJournalEntry 
    { 
    } 
} 
64

Właściwie nie jest kompletne i proste rozwiązanie tego problemu, które nie wiąże się modyfikowania kod klienta lub tworzenie interfejsu.

To rozwiązanie jest w rzeczywistości szybsze niż rozwiązanie oparte na interfejsie w większości przypadków i łatwiejsze do kodowania.

public class Journal 
{ 
    private static Func<object, JournalEntry> _newJournalEntry; 

    public class JournalEntry 
    { 
    static JournalEntry() 
    { 
     _newJournalEntry = value => new JournalEntry(value); 
    } 
    private JournalEntry(object value) 
    { 
     ... 
+3

Ładne i oryginalne podejście ... Muszę to zapamiętać. Dzięki ! –

+0

To bardzo miłe, cenny kawałek kodu! –

+2

Spiffy. Przyjęta odpowiedź ma tę samą ogólną ideę, ale pojawia się na samym dole wielu alternatyw, więc spójrz tutaj! – aggieNick02

10

Prostszym sposobem jest po prostu użyć konstruktora internal, ale sprawiają, że rozmówca udowodnić, kim są poprzez dostarczanie odniesienie że tylko uzasadnione rozmówca mógł wiedzieć (nie musimy się martwić o zakaz - publiczna refleksja, ponieważ jeśli dzwoniący ma dostęp do niepublicznej refleksji, to już przegrywamy walkę - mogą uzyskać bezpośredni dostęp do konstruktora private); na przykład:

class Outer { 
    // don't pass this reference outside of Outer 
    private static readonly object token = new object(); 

    public sealed class Inner { 
     // .ctor demands proof of who the caller is 
     internal Inner(object token) { 
      if (token != Outer.token) { 
       throw new InvalidOperationException(
        "Seriously, don't do that! Or I'll tell!"); 
      } 
      // ... 
     } 
    } 

    // the outer-class is allowed to create instances... 
    private static Inner Create() { 
     return new Inner(token); 
    } 
} 
+0

Dobry pomysł! Będę pamiętać o tej sztuczce, na wszelki wypadek ... –

0

przypadku generycznych zagnieżdżonego class =)

wiem, że to jest stary pytanie ma już zaakceptowane odpowiedź, niemniej jednak dla tych pływaków google którzy mogą mieć podobny scenariusz do kopalni ta odpowiedź może pomóc.

Natknąłem się na to pytanie, ponieważ musiałem wdrożyć tę samą funkcję co OP. Dla mojego pierwszego scenariusza odpowiedzi: this i this działały dobrze. Niemniej jednak potrzebowałem również ujawnić zagnieżdżoną klasę generyczną. Problem polega na tym, że nie można ujawnić pola typu delegata (pola fabryki) z otwartymi ogólnymi parametrami bez tworzenia własnej klasy ogólnej, ale oczywiście nie jest to tym, czego chcemy, więc oto moje rozwiązanie dla takiego scenariusza:

public class Foo 
{ 
    private static readonly Dictionary<Type, dynamic> _factories = new Dictionary<Type, dynamic>(); 

    private static void AddFactory<T>(Func<Boo<T>> factory) 
     => _factories[typeof(T)] = factory; 

    public void TestMeDude<T>() 
    { 
     if (!_factories.TryGetValue(typeof(T), out var factory)) 
     { 
      Console.WriteLine("Creating factory"); 
      RuntimeHelpers.RunClassConstructor(typeof(Boo<T>).TypeHandle); 
      factory = _factories[typeof(T)]; 
     } 
     else 
     { 
      Console.WriteLine("Factory previously created"); 
     } 

     var boo = (Boo<T>)factory(); 
     boo.ToBeSure(); 
    } 

    public class Boo<T> 
    { 
     static Boo() => AddFactory(() => new Boo<T>()); 

     private Boo() { } 

     public void ToBeSure() => Console.WriteLine(typeof(T).Name); 
    } 
} 

mamy Boo jako naszego wewnętrznego klasy zagnieżdżonej z prywatnym konstruktora i mantain na naszej klasie dominującej słownika z tych rodzajowe fabryki wykorzystując dynamicznego. Tak więc, za każdym razem, gdy wywoływane jest TestMeDude, Foo szuka, czy fabryka dla T została już utworzona, jeśli nie, tworzy wywołujący ją konstruktor statyczny klasy zagnieżdżonej.

Testowanie:

private static void Main() 
{ 
    var foo = new Foo(); 

    foo.TestMeDude<string>(); 
    foo.TestMeDude<int>(); 
    foo.TestMeDude<Foo>(); 

    foo.TestMeDude<string>(); 

    Console.ReadLine(); 
} 

Wyjście jest:

enter image description here

Powiązane problemy