Czy używanie interfejsów dla typów danych jest anty-wzorcem?

9

Załóżmy, że mam różne podmioty w moim modelu (przy użyciu EF), powiedzmy Użytkownik, Produkt, Faktura i Zamówienie.

Piszę kontrolkę użytkownika, która może wydrukować podsumowania obiektów encji w mojej aplikacji, gdzie encje należą do wcześniej ustalonego zestawu, w tym przypadku mówię, że streszczenia użytkownika i produktu można podsumować.

Wszystkie streszczenia będą miały tylko identyfikator i opis, dlatego tworzę prosty interfejs:

 public interface ISummarizableEntity {     
       public string ID { get; }    
       public string Description { get; } 
 }

Następnie dla przedmiotowych encji tworzę klasę częściową, która implementuje ten interfejs:

public partial class User : ISummarizableEntity
{
    public string ID
    {
        get{ return UserID.ToString(); }
    }

    public string Description 
    {
        get{ return String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age); }
    }
}

public partial class Product: ISummarizableEntity
{
    public string ID
    {
        get{ return ProductID.ToString(); }
    }

    public string Description 
    {
        get{ return String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department); }
    }
}

W ten sposób moja kontrola użytkownika / częściowy widok może po prostu powiązać się z dowolną kolekcją ISummarizableEntity i nie musi być wcale zainteresowany źródłem. Powiedziano mi, że interfejsy nie powinny być używane jako typy danych, ale nie otrzymałem więcej informacji. Z tego co widzę, chociaż interfejsy zwykle opisują zachowanie, samo użycie właściwości nie jest samo w sobie anty-wzorem, ponieważ właściwości są po prostu cukrem syntaktycznym dla pobierających / ustawiających.

Mógłbym stworzyć konkretny typ danych i mapę z encji do tego, ale nie widzę korzyści. Mógłbym sprawić, aby obiekty encji dziedziczyły po klasie abstrakcyjnej, a następnie zdefiniować właściwości, ale następnie blokuję encje, aby nie mogły być dalej używane, ponieważ nie możemy mieć wielokrotnego dziedziczenia. Jestem również otwarty na to, że dowolny obiekt jest ISummarizableEntity, gdybym chciał (oczywiście zmieniłbym nazwę interfejsu)

Rozwiązanie, którego używam w umyśle, jest łatwe do utrzymania, rozszerzalne, testowalne i dość solidne. Widzisz tutaj anty-wzór?

Grzech
źródło
Czy jest jakiś powód, dla którego wolisz to od posiadania czegoś takiego EntitySummary, Usera Productkażdy ma taką metodę public EntitySummary GetSummary()?
Ben Aaronson,
@Ben, sugerujesz prawidłową opcję. Ale nadal wymagałoby to zdefiniowania interfejsu, który informuje osobę dzwoniącą, że może oczekiwać, że obiekt będzie miał metodę GetSummary (). Jest to zasadniczo ten sam projekt z dodatkowym poziomem modułowości we wdrażaniu. Może nawet dobry pomysł, jeśli podsumowanie musi żyć samodzielnie (choć krótko) niezależnie od źródła.
Kent A.
@KentAnderson Zgadzam się. To może, ale nie musi być dobrym pomysłem, w zależności od ogólnego interfejsu tych klas i sposobu wykorzystania podsumowań.
Ben Aaronson

Odpowiedzi:

17

Interfejsy nie opisują zachowania. Czasami wręcz przeciwnie.

Interfejsy opisują umowy, takie jak „jeśli mam zaoferować ten obiekt dowolnej metodzie, która akceptuje ISummarizableEntity, ten obiekt musi być bytem, ​​który jest w stanie się streścić” - w twoim przypadku jest to zdefiniowane jako możliwość zwrotu identyfikator ciągu i ciąg Opis.

To idealne wykorzystanie interfejsów. Tutaj nie ma anty-wzoru.

pdr
źródło
2
„Interfejsy nie opisują zachowania”. W jaki sposób „samo podsumowanie” nie jest zachowaniem?
Doval
2
@ThomasStringer, dziedziczenie, z purystycznego punktu widzenia OO, implikuje wspólne pochodzenie (np. Kwadrat i okrągkształtami ). W przykładzie OP użytkownik i produkt nie mają wspólnego rozsądnego przodka. Dziedziczenie w tym przypadku byłoby wyraźnym anty-wzorem.
Kent A.
2
@Doval: Myślę, że nazwa interfejsu może opisywać oczekiwane zachowanie. Ale nie musi; interfejs mógłby również nazywać się IHasIdAndDescription, a odpowiedź byłaby taka sama. Sam interfejs nie opisuje zachowania, opisuje oczekiwania.
pdr
2
@pdr Jeśli wyślesz 20 V przez gniazdo słuchawkowe, zdarzają się złe rzeczy. Kształt to za mało; bardzo realne i bardzo ważne jest oczekiwanie, jaki rodzaj sygnału będzie przesyłany przez tę wtyczkę. Właśnie dlatego udawanie, że interfejsy nie mają dołączonych specyfikacji zachowania, jest błędne. Co możesz zrobić z Listlistą, która nie zachowuje się jak lista?
Doval
3
Wtyczka elektryczna z odpowiednim interfejsem może pasować do gniazdka, ale to nie znaczy, że będzie przewodzić prąd (pożądane zachowanie).
JeffO
5

Wybrałeś lepszą ścieżkę dla tego projektu, ponieważ definiujesz określony typ zachowania, które będzie wymagane od wielu różnych typów obiektów. Dziedziczenie w tym przypadku oznaczałoby wspólny związek między klasami, który tak naprawdę nie istnieje. W takim przypadku kompozycyjność jest ważniejsza niż dziedziczenie.

Kent A.
źródło
3
Interfejsy nie mają nic wspólnego z dziedziczeniem.
DougM
1
@DougM, może nie powiedziałem tego dobrze, ale jestem pewien, że się zgadzamy.
Kent A.
1

Należy unikać interfejsów posiadających tylko właściwości, ponieważ:

  • zaciemnia to zamiar: potrzebujesz tylko kontenera danych
  • zachęca do dziedziczenia: prawdopodobieństwo, że ktoś rozwiąże obawy w przyszłości
  • zapobiega serializacji

Mieszacie dwie obawy:

  • podsumowanie jako dane
  • streszczenie jako umowa

Podsumowanie składa się z dwóch ciągów: identyfikatora i opisu. To są zwykłe dane:

public class Summary {
    private readonly string id;
    private readonly string description;
    public Summary(string id, string description) {
        this.id = id;
        this.description = description;
    }
    public string Id { get { return id; } }
    public string Description { get { return description; } }
}

Po zdefiniowaniu podsumowania chcesz zdefiniować umowę:

public interface ISummarizableEntity {
    public Summary GenerateSummary();
}

Zauważ, że używanie inteligencji w getterach jest anty-wzorcem i należy tego unikać: zamiast tego powinno się znajdować w funkcjach. Oto jak wyglądają wdrożenia:

public partial class User : ISummarizableEntity {
    public Summary GenerateSummary() {
        var id = UserID.ToString();
        var description = String.Format("{0} {1} is from {2} and is {3} years old", FirstName, LastName, Country, Age);
        return new Summary(id,description);
    }
}

public partial class Product : ISummarizableEntity {
    public Summary GenerateSummary() {
        var id = ProductID.ToString();
        var description = String.Format("{0} weighs {1}{2} and belongs in the {3} department", ProductName, WeightValue, WeightUnit, Department);
        return new Summary(id,description);
    }
}
Vanna
źródło
„Należy unikać interfejsów posiadających tylko właściwości” Nie zgadzam się. Podaj uzasadnienie, dlaczego tak uważasz.
Euforyczny
Masz rację, dodałem kilka szczegółów
vanna