Programowanie interfejsów zorientowanych na dane

9

Część naszej bazy kodu napisana jest w następującym stylu:

// IScheduledTask.cs
public interface IScheduledTask
{
    string TaskName { get; set; }
    int TaskPriority { get; set; }
    List<IScheduledTask> Subtasks { get; set; }
    // ... several more properties in this vein
}

// ScheduledTaskImpl.cs
public class ScheduledTaskImpl : IScheduledTask
{
    public string TaskName { get; set; }
    public int TaskPriority { get; set; }
    public List<IScheduledTask> Subtasks { get; set; }
    // ... several more properties in this vein, 
    // perhaps a constructor or two for convenience.
}

Oznacza to, że istnieje duża liczba interfejsów określających tylko zestaw właściwości bez zachowania, z których każda ma jedną implementację, która implementuje je za pomocą właściwości automatycznych. Kod jest napisany przez kogoś dość starszego (o wiele bardziej niż ja) i poza tym używaniem interfejsów rozsądny kod proceduralny. Zastanawiałem się, czy ktokolwiek inny zetknął się / użył tego stylu i czy ma on jakąkolwiek przewagę nad zwykłym używaniem konkretnych DTO bez interfejsów.

walpen
źródło
1
Jednym z powodów, dla których to zrobiłem, jest kompatybilność z COM, ale to chyba nie jest twój powód.
Mike
@Mike Ciekawe, nigdy nie korzystałem z COM i nie jest tutaj używany. Zapytam autora tej sekcji kodu, czy planował użyć COM, czy coś w tym rodzaju.
walpen
Domyślam się, że spodziewa się, że przynajmniej jedna z tych właściwości stanie się nieautomatyczna we względnie niedalekiej przyszłości, a pisanie tego bojlera na początku jest nawykiem, który przyzwyczaił mu się oszczędzać trochę czasu na zabawę, chociaż jestem nie jestem facetem z C #, więc to wszystko, co mogę powiedzieć.
Ixrec
6
IMHO to nadmierna obsesja i niewłaściwe stosowanie kodu do interfejsów, a nie implementacja . Kluczowym elementem jest jedyne wdrożenie . Istotą interfejsów jest polimorfizm, ale klasa tylko właściwości jest polimorficzna w pewnym sensie po prostu przez różne wartości. Nawet przy zachowaniu - metodach - nie ma sensu, aby przy jednej realizacji. Ten nonsens jest w całej naszej bazie kodu i co najwyżej utrudnia utrzymanie. Tracę zdecydowanie za dużo czasu, pytając dlaczego, co i gdzie. Dlaczego to zrobili, jakiego projektu nie widzę i gdzie są inne wdrożenia?
radarbob
2
Większość poprzedzających komentarzy dotyczy pojedynczego wdrożenia „zapachu kodu” przedwczesnej abstrakcji; istnieje również tutaj „zapach kodu” anemicznej domeny, patrz: martinfowler.com/bliki/AnemicDomainModel.html
Erik Eidt

Odpowiedzi:

5

Oto moje dwa centy:

  • Nie jesteśmy pewni, że DTO nigdy nie zachowa się przynajmniej trochę. Tak więc dla mnie istnieją obiekty, które mogą, ale nie muszą, zachowywać się w przyszłości.
  • Jedną z możliwych przyczyn tak wielu klas wolnej implementacji jest brak projektu , na przykład w przypadku jego braku, aby zobaczyć, że ScheduledTask, ScheduledJob, ScheduledEvent, ScheduledInspection, itp, powinny być segregowane tylko jeden Schedulableinterfejs podjęciem szeregowaniu zadań implementor.
  • Tworzenie interfejsów w pierwszej kolejności jest bardzo dobrą praktyką, daje wgląd w to, czego potrzebujesz. Wiele interfejsów zaczyna się od braku implementacji. Następnie ktoś pisze jedną implementację i ona ją ma. Stan posiadania jedynej implementacji jest tymczasowy, jeśli unikniesz tego, o czym wspomniałem w drugim punkcie. Skąd wiesz, że jakiś interfejs nigdy nie będzie miał sekundy trzeciej implementacji?
  • Nazwa, którą wybieramy dla dobrze przemyślanego interfejsu, ma tendencję do nie zmieniania się w przyszłości, więc nie będzie to miało wpływu na zależności od tego interfejsu. Z drugiej strony, nazwa konkretnej klasy jest skłonna do zmiany nazwy, gdy zmienia się coś ważnego w sposobie jej implementacji, na przykład konkretna klasa o nazwie TaxSheetmoże zmienić się, SessionAwareTaxSheetponieważ dokonano znacznego przeglądu, ale ITaxSheetprawdopodobnie nazwa interfejsu nie będzie tak łatwa.

Konkluzja:

  • Dobre praktyki projektowe dotyczą również DTO.
  • DTO można dodać trochę zachowania na drodze.
  • Każdy interfejs zaczyna się od jednej implementacji.
  • Z drugiej strony, jeśli masz zbyt wiele kombinacji jednego interfejsu i jednej klasy, może brakować projektu, na który należy zwrócić uwagę. Twoje zainteresowanie może być uzasadnione .
Tulains Córdova
źródło
4
Poparłem twoją odpowiedź, ale twój drugi punkt sugeruje, że wszystkie klasy powinny automatycznie mieć odpowiedni interfejs, z którym nigdy się nie zgadzałem. Nieoczekiwane pisanie interfejsów, które mogą mieć drugą implementację, powoduje jedynie rozprzestrzenianie się interfejsu i YAGNI.
Robert Harvey
1

Szczególnym problemem, jaki widziałem w przypadku DTO korzystających z interfejsów, jest to, że pozwala na to:

public interface ISomeDTO { string SomeProperty { get; set; }}

public class SomeDTO : ISomeDTO
{
    public string SomeProperty { get; set; }
    string ISomeDTO.SomeProperty { get { /* different behavior */ } set { SomeProperty = value; } }
}

Widziałem ten wzorzec zastosowany jako szybki, brudny hack w celu wdrożenia pewnych krytycznych zachowań. Prowadzi to do kodu, który może mieć bardzo mylące zachowanie:

SomeDTO a = GetSomeDTO();
ISomeDTO ia = a;
Assert.IsTrue(a.SomeProperty == ia.SomeProperty); // assert fails!

Jest to trudne do utrzymania i mylące przy próbach rozplątywania lub refaktoryzacji. IMO, dodanie zachowania do DTO narusza zasadę pojedynczej odpowiedzialności. Celem DTO jest reprezentowanie danych w formacie, który można utrwalić i zapełnić w ramach utrwalania.

DTO nie są modelami domenowymi. Nie powinniśmy się martwić, jeśli DTO są anemiczne. Cytując dyskusję Martina Fowlera na temat modelu anemicznego :

Warto również podkreślić, że umieszczanie zachowania w obiektach domeny nie powinno być sprzeczne z solidnym podejściem polegającym na stosowaniu warstw do oddzielenia logiki domeny od takich rzeczy, jak trwałość i odpowiedzialność za prezentację.

Erik
źródło