Czy każda klasa, którą piszę, powinna przestrzegać interfejsu?

10

Piszę grę w Typescript i postanowiłem, że spróbuję zastosować się do idei „ programowania opartego na interfejsie ”, w którym piszesz kod oparty na interfejsie zamiast implementacji obiektu.

Napisałem sporo interfejsów i klas, które je implementują, a następnie cofnąłem się i zdałem sobie sprawę, że klasy były na tyle proste, że prawdopodobnie nigdy nie będę musiał zmieniać implementacji, ponieważ tak naprawdę jest tylko jeden sposób, aby zrobić to, co klasa robi (poruszając się Phaser.Spritew ograniczony sposób, aby zachowywać się jak czołg).

Potem pamiętam, jak przeczytałem kilka lat temu o idei YAGNI , która polega zasadniczo na tym, że nie powinieneś nadmiernie konstruować swojego kodu, aby zawierał rzeczy, których nigdy nie możesz użyć.

Czy zgodnie z najlepszymi praktykami każda klasa powinna implementować interfejs, czy też powinna ograniczyć się do klas, które mogą zostać w przyszłości zamienione?

Carcigenicate
źródło
Pamiętaj, że kiedy używasz rzeczywistej klasy jako parametru, kodujesz również do interfejsu, do interfejsu tej klasy. Nikt nie zmusza cię do korzystania z tej konkretnej klasy, równie dobrze możesz ją odziedziczyć i zapewnić zupełnie nową funkcjonalność, ale to nie wydaje się właściwe. Istnieją interfejsy, które ułatwiają ludziom obejście pomysłu. Ponieważ jest to smutne, nie, tak naprawdę nie potrzebujesz interfejsów do wszystkiego.
Andy

Odpowiedzi:

6

Powodem posiadania interfejsów jest to, że upraszcza polimorfizm. Oznacza to, że możesz wysyłać instancje na podstawie umowy, zamiast znać ich faktyczną implementację. Na przykład, możesz wysłać „Reader” do metody, aby tak zwana metoda mogła użyć metody „read ()”. Deklarując interfejs „Czytnik”, możesz dostosować dowolny obiekt do niniejszej umowy, wdrażając określone przez niego metody. W ten sposób każdy dzwoniący może założyć, że istnieją pewne metody, nawet jeśli obiekty leżące u podstaw umowy mogą być zupełnie inne.

Oddziela polimorfizm od klasy bazowej i umożliwia także między granicami łańcucha klasowego, narzucając kontrakt.

Teraz ... Przydaje się to tylko wtedy, gdy potrzebujesz wielu łańcuchów dziedziczenia, aby działały w ten sam sposób lub jeśli masz wiele klas bazowych o wspólnych zachowaniach, które będziesz chciał przekazać innym użytkownikom.

Jeśli masz tylko JEDEN łańcuch dziedziczenia (klasa podstawowa-> podklasa) lub tylko podstawa, lub nie musisz faktycznie PRZEKAŻAĆ powiązanych obiektów komuś innemu lub używać ich ogólnie, wówczas nie dodawałbyś implementacji interfejsu, ponieważ takie pojęcie jest wtedy nieprzydatny.

Richard Tyregrim
źródło
Dodałbym również, że interfejsy powinny modelować abstrakcje, więc nie twórz ich, po prostu podnosząc wszystkich publicznych członków klasy. Zastanów się, co reprezentuje ta klasa, i umieść w interfejsie tylko te rzeczy, które powinien posiadać każdy obiekt tego typu. Można również zakończyć tworzenie wielu interfejsów (np Movesi Shootsna swoim przykładzie zbiornika) dla jednej klasy.
TMN
7

Jeśli nie jesteś pewien, czy rzeczywiście potrzebujesz interfejsów, prawdopodobnie nie. To jest sedno zasady YAGNI. Kiedy musisz być w stanie zamienić implementację, musisz wprowadzić interfejs.

JacquesB
źródło
6

Tworzenie interfejsu dla każdej klasy jest nieco przesadne. Z czysto obiektowego punktu widzenia każda klasa ma już interfejs . Interfejs jest niczym więcej niż publicznymi metodami i członkami danych klasy.

Zazwyczaj „interfejs” oznacza „klasę Java zdefiniowaną za pomocą interfacesłowa kluczowego” lub „klasę C ++ z tylko publicznymi, czysto wirtualnymi funkcjami”. Są to użyteczne abstrakcje, ponieważ oddzielają interfejs klasy od jej implementacji.

Mając to na uwadze, w razie potrzeby używaj interfejsów.

  • Czy będzie wiele implementacji interfejsu? Jeśli tak, sytuacja ta może być kandydatem do wyodrębnienia powszechnych metod publicznych w interfejsie.

  • Czy przekażę implementacje potencjalnego interfejsu innym modułom, które nie powinny znać wewnętrznego funkcjonowania mojego modułu? Interfejs może być tu przydatny, ponieważ zmniejsza rozmiar punktu styku między modułami.

Zwróć uwagę na powyższe słowa łasicy: „może” być kandydatem, „może być” przydatny. Nie ma twardej i szybkiej reguły, która mówi „tak” lub „nie” w danej sytuacji.

Biorąc to pod uwagę, posiadanie interfejsu do wszystkiego jest prawdopodobnie przesadą. Jeśli zobaczysz ten wzór, to za daleko:

interface I {
  int getA()
  int getB()
  ...
  int getY()
  int getZ()
}

class C : I {
  int getA() { ... }
  int getB() { ... }
  ...
  int getY() { ... }
  int getZ() { ... }
}

źródło
Kolejnym powodem interfejsów jest to, że system testowy sprawia, że ​​jest to konieczne, co może zależeć od twojego języka i środowiska testowego. Mamy nadzieję, że oczywiście nie musisz tego robić.
Darien
@Darien: Powodem, dla którego system testowy może wymagać interfejsów, jest fakt, że w testach faktycznie używana jest inna (wyśmiewana) implementacja, co prowadzi do powrotu do pierwszego punktu, kiedy interfejs jest odpowiedni.
Bart van Ingen Schenau
Kilka świetnych rzeczy tutaj. Zastanawiając się nad tym: interfejs to nic więcej niż publiczne metody i członkowie danych klasy . Chociaż dotyczy to implementacji interfejsu , z pewnością nie dotyczy to interfejsu, który nigdy nie został zaimplementowany, choć przyznany - byłby to zapach kodu.
Robbie Dee
@RobbieDee tak jest np. W przypadku Javy interface, tak się składa, że ​​wszystko w Javie interfacejest także interfejsem publicznym (koncepcja OO).
Wszyscy! Zapisz drugie i trzecie zdanie odpowiedzi. Laminuj to. Umieść go w swoim portfelu. Odnoś często.
radarbob
5

Dla nowszych programistów może być bardzo kuszące, aby myśleć o interfejsach jako o niedogodności, którą dodajesz tylko dlatego, że według ciebie jest to „najlepsza praktyka”. Jeśli robisz to na ślepo, pachnie to programowaniem kultu ładunku .

Istnieje wiele bardzo dobrych powodów, dla których warto rozważyć interfejsy dla większych zmian, a nie zestawiać razem zestaw klas.

Szydzące frameworki

Jeśli chcesz przetestować aplikację wielowarstwową bez konieczności tworzenia obiektów dla różnych warstw, niewątpliwie zechcesz użyć struktur próbnych do wyśmiewania warstw. Interfejsy są tutaj szeroko stosowane.

Zastrzyk zależności

Chociaż nieco popadli w niełaskę z powodu dodanego wzdęcia, pomysł jest zdrowy - możliwość po prostu zamiany konkrecji na podstawie interfejsu. Zasadę można również znaleźć w wielu wzorach projektowych, takich jak wzór fabryczny.


Te podejścia są podsumowane w D z zasad SOLID . Nacisk na to jest - używaj abstrakcji, a nie konkrecji.

Podobnie jak same wzorce projektowe, kiedy z nich korzystać, należy dokonać oceny. Chodzi o to, aby zrozumieć, w jaki sposób interfejsy są przydatne, a nie tylko przyklejać je do swoich zajęć, ponieważ czujesz, że musisz. Dobra dyskusja na temat różnych zalet DIP i YAGNI tutaj .

Robbie Dee
źródło
Zauważ, że wstrzykiwanie zależności i kontenery IoC to dwie zupełnie różne rzeczy (jedna jest zasadą projektowania, a druga jest konkretnym sposobem praktycznego zastosowania tej zasady za pomocą różnych ram). Możesz używać DI bez użycia kontenera IoC.
sara
@kai „Możesz używać DI bez użycia kontenera IoC” . Myślę, że bardziej dojrzałe sklepy programistyczne mogą (i robią). Po prostu nie ma potrzeby zanieczyszczania kodu, co jest pozornie prostą koncepcją. Joel ma podobny pogląd.
Robbie Dee
YAGNI jest bardzo trudny. Niezależnie od tego, jaka jest filozofia, w większości przypadków YAGNI służy jako wymówka do robienia zakrętów. Unikaj łączenia należy traktować poważniej. Na wielu spotkaniach scrumowych frustrujące jest to, że deweloper nazywa siebie zablokowanym, ponieważ ktoś inny nie zakończył pracy. Dlaczego zablokowanie innego programisty pozwala komukolwiek zaoszczędzić czas? Moją sugestią jest użycie interfejsu, aby uniknąć sprzężenia. Oczywiście nie trzeba używać interfejsu dla klas Model.
Ripal Barot