2012-11-28 21 views
9

Próbuję znaleźć dobry sposób podejścia do tego. Mam klasę Klienta, która implementuje interfejs ICustomer. Ten interfejs ma wiele właściwości:Ustawodawcy nieruchomości wewnętrznych w C#

public interface ICustomer 
{ 

    string FirstName {get; set;} 
    string LastName {get; set;} 
} 

Chcę tylko, aby niektóre klasy miały możliwość ustawienia tych właściwości; mianowicie te klasy w projekcie. Więc pomyślałem o zrobieniu ustawiająca internal:

public class Customer : ICustomer 
{ 

    string FirstName {get; internal set;} 
    string LastName {get; internal set;} 
} 

Chciałbym zaznaczyć, że seter jako wewnętrzny w interfejsie, jednak tak nie ma szans ktoś realizuje ICustomer i ktoś spoza zespołu modyfikuje te właściwości. Czy jest to dobry sposób na zrobienie tego?

Odpowiedz

16

Właściwości w interfejsie powinny być tylko do odczytu. Jest to dopuszczalne dla konkretnej klasy, która implementuje interfejs, aby mieć ustawnik, nawet jeśli żaden nie jest zdefiniowany w interfejsie.

public interface ICustomer 
{ 
    string FirstName { get; } 
    string LastName { get; } 
} 

public class Customer : ICustomer 
{ 
    public string FirstName { get; internal set; } 
    public string LastName { get; internal set; } 
} 

Jeśli to jest naprawdę ważne, że seter być narażeni poprzez interfejs, zamiast interfejsu jest całkowicie tylko do odczytu, można użyć coś takiego:

public interface IReadCustomer 
{ 
    string FirstName { get; } 
    string LastName { get; } 
} 

internal interface IWriteCustomer 
{ 
    string FirstName { set; } 
    string LastName { set; } 
} 

internal interface IReadWriteCustomer : IReadCustomer, IWriteCustomer 
{ } 

public class Customer : IReadWriteCustomer 
{ 
    private string _firstName; 
    private string _lastName; 

    public string FirstName 
    { 
     get { return _firstName; } 
     internal set { _firstName = value; } 
    } 
    public string LastName 
    { 
     get { return _lastName; } 
     internal set { _lastName = value; } 
    } 

    string IReadCustomer.FirstName 
    { 
     get { return FirstName; } 
    } 

    string IReadCustomer.LastName 
    { 
     get { return LastName; } 
    } 

    string IWriteCustomer.FirstName 
    { 
     set { FirstName = value; } 
    } 

    string IWriteCustomer.LastName 
    { 
     set { LastName = value; } 
    } 
} 
+0

Dzięki za pomoc. Jeśli jednak to zrobię, a klasa konsumująca korzysta z ICustomer, nie może dotrzeć do setera. – larryq

+2

@larryq To jest cały punkt, ponieważ interfejs nie zobowiązuje się do ustawiacza. Jeśli chcesz zaimplementować drugi wewnętrzny interfejs, który doda setera, możesz. Często mam serię interfejsów: to jest prawdziwy przykład: 'IDimensionedMap' zatwierdza tylko wymiary jednostki 2D; 'IReadMap' z czytelnymi właściwościami,' IReadMap: IDimensionedMap' dla map tylko do odczytu lub obliczonych, a następnie 'IWriteMap: IReadMap'. Ale jest to złożona rodzina rozszerzalnych klas użytkowych. (Są to 2d przestrzenie Hilberta, a nie słowniki, jeśli jesteś ciekawy.) – SAJ14SAJ

+1

@larryq Chociaż nie jestem pewien, czy tego potrzebujesz, czy nie, dodałem alternatywną implementację, w której interfejs definiuje metodę wewnętrznego zestawu. – Servy

6

ja lubię oznaczać ten seter jako wewnętrzny w interfejsie, więc nie ma szans, że ktoś implementuje ICustomer, a ktoś poza zespołem modyfikuje te właściwości. Czy jest to dobry sposób na zrobienie tego?

Nie. Użytkownicy nieruchomości to zawsze publicznych, niestety. Dodatkowo, kłopoty z poziomami dostępu do właściwości, w których część jest określona na interfejsie, stają się bolesne, IIRC. Co ty może zrobić to w ten sposób:

public interface ICustomer 
{ 
    string FirstName { get; } 
    string SecondName { get; } 
} 

internal interface ICustomerWithSetMethods : ICustomer 
{ 
    void SetFirstName(string name); 
    void SetLastName(string name); 
} 

public class Customer : ICustomerWithSetMethods 

Następnie z poza to będzie wyglądać Customer realizuje tylko ICustomer, ale z wewnątrz Twój kod będzie widać, że realizuje ICustomerWithSetMethods.

Niestety to nie gra ładnie, czy API musi deklarować żadnych metod publicznych, gdzie by naprawdę jak po prostu zadeklarować typ zwracanej ICustomer, ale musisz wiedzieć, że faktycznie to zawsze ICustomerWithSetMethods.

Zakładając, że nadal chcą, aby umożliwić wielu implementacji, można potencjalnie iść do klasy abstrakcyjnej Zamiast:

public abstract class CustomerBase 
{ 
    public abstract string FirstName { get; } 
    public abstract string LastName { get; } 

    internal abstract void SetFirstName(string name); 
    internal abstract void SetLastName(string name); 
} 

teraz mamy niewielki osobliwość, że nikt poza zespołem może przedłużyć swój CustomerBase, ponieważ nie są metodami abstrakcyjnymi, które musiałyby zastąpić, a które nie mogą nawet zobaczyć - ale oznacza to, że możesz używać CustomerBase wszędzie w swoim API.

To podejście przyjęliśmy w Noda Time dla systemów kalendarzy w końcu - I blogged about it, kiedy po raz pierwszy wymyśliłem plan. I ogólnie preferuję interfejsy do klas abstrakcyjnych, ale korzyści tutaj były znaczące.

+0

Tęskniłeś za typami i zakresem FirstName i SecondName –

+0

@JonB: Tak zrobiłem. Naprawiono, dzięki. –

+0

@JonSkeet Zgadzam się z twoim podejściem, ale generalnie implementuję zarówno interfejs, jak i abstrakcyjne klasy bazowe, gdy konserwacja z biegiem czasu będzie problemem. Jest to trochę dodatkowe pisanie, ale narzędzia takie jak Resharper ułatwiają, a to oznacza, że ​​możesz mieć więcej niż jedną abstrakcyjną implementację, gdy masz złożoną rodzinę obiektów, być może taką, która zostanie rozszerzona poza twoją własną bibliotekę kodów. – SAJ14SAJ

Powiązane problemy