Jak zaimplementować tylko część interfejsu

14

Podczas programowania w OOP czasami biblioteka / interfejs udostępnia interfejs, którego nie można zmienić. Nazwijmy ten interfejs J.

Teraz masz obiekt klasy A, który zużywa obiekty implementujące ten interfejs. Wewnątrz Potrzebna jest tylko niewielka część definicji interfejsu. Niektóre klasy obiektów są tworzone przeze mnie podczas projektu (nazwijmy jedną z nich typem D), więc na wdrożenie wszystkiego w interfejsie J. wiąże się narzut

Chcę zaimplementować podzbiór funkcjonalności w interfejsie J, ale moje dotychczasowe rozwiązania mnie nie satysfakcjonują:

  • implementowanie każdego aspektu J, a następnie rzucanie „notImplementedExceptions” dezinformuje użytkownika moich obiektów: wydaje się, że moje obiekty typu D są zgodne z interfejsem J, ale nie - i inni odbiorcy moich obiektów (które akceptują obiekty implementujące interfejs J) nie mogę polegać na integralności moich obiektów.
  • Implementowanie nowo zdefiniowanego interfejsu zabrania mi używania obiektów, które implementują tylko interfejs J, chociaż interfejs J jest w pełni kompatybilny z moim własnym interfejsem.
  • Pozwolenie moim niestandardowym obiektom na implementację interfejsu J spowodowałoby znaczne obciążenie, ponieważ nie potrzebują one całej tej funkcjonalności.

Kiedy mogłem zmienić interfejs J, tworzyłem „superinterfejs” K, który posiadałby ten podzbiór funkcjonalności interfejsu J, i czyniłem interfejs J dziedziczonym z interfejsu K. Ale nie mogę zmienić interfejsu J.

Jakie jest obiektowe rozwiązanie tego problemu? Czy najlepsze rozwiązanie nadal implementuje interfejs „just” J? Czy są sposoby OOP na „nadklasę” interfejsu bez jego zmiany?

vstrien
źródło
1
Zobacz, jak to robią * klasy adapterów w Swing.

Odpowiedzi:

9

jeśli nie kontrolujesz interfejsu J, utkniesz.

możesz, dla jasności, wdrożyć własny interfejs subJ i interfejs J oraz użyć subJ we własnym kodzie, aby wyjaśnić, że dodatkowe metody w interfejsie J nie są wymagane, ale nie sądzę, że to naprawdę wiele zyskuje

jeśli to możliwe, całkowicie zaimplementuj cały interfejs J.

jeśli to możliwe, skontaktuj się z właścicielem interfejsu J i poproś go o zmianę w celu lepszego dostosowania do twoich celów

Steven A. Lowe
źródło
2
Jako kompromis: możesz poprosić właściciela interfejsu J o dziedziczenie z mniejszego interfejsu SubJ.
k3b
27

Istotą wdrożenia interfejsu jest zapewnienie ścisłej umowy między dzwoniącym a odbiorcą, która nigdy się nie zmienia, podczas gdy szczegóły implementacji mogą się różnić.

Wdrażając interfejs J, mówisz światu zewnętrznemu, co możesz zrobić.

Jeśli nie potrzebujesz połowy tego, co robi J, to albo interfejs powinien być naprawdę podzielony, ponieważ nie jest tak mały, jak mógłby być, lub musisz wprowadzić NotImplementedExceptionmetody i właściwości, których nie potrzebujesz. Nie jest to jednak najlepsze rozwiązanie, ponieważ dezorientuje ludzi w kwestii tego, co może zrobić Twój kod.

ChrisF
źródło
5

Jeśli twoja klasa konsumencka A nie wymaga całego interfejsu J, sugerowałbym utworzenie nowego interfejsu (nazwij go K), który kompleksowo opisuje wszystko, czego wymaga.

Daje to wyraźny sygnał każdemu, kto korzysta z klasy A, co do jego strony umowy. W ten sposób zwiększa się szanse na ponowne użycie. Jeśli ktoś musi dostarczyć obiekt, który implementuje duży i złożony interfejs, aby zrobić coś stosunkowo prostego, prawdopodobnie skończy pisać coś, co klasa A zrobi sama.

Aby umożliwić klasie A korzystanie z obiektów implementujących sam interfejs J, można zapewnić klasę opakowania, która implementuje interfejs K i przekazuje wszelkie odpowiednie wywołania do elementu interfejsu J.

Paul Butcher
źródło
3

Chociaż zgadzam się z istniejącymi odpowiedziami, które mówią, że naprawdę musisz albo całkowicie zaimplementować J (z wyjątkami dla niezaimplementowanych metod), albo wcale, możliwe obejście jest następujące:

  • Utwórz mniejszy interfejs K, który jest podzbiorem J i implementuje wymagane metody.
  • Utwórz opakowanie dla A, które akceptuje obiekty, które implementują J i K.
  • W przypadku zaliczenia w J po prostu przepchnij go do wystąpienia A.
  • W przypadku przekazania w K utwórz anonimową implementację J, łącząc tylko metody od K do J, które tam są. Przekaż wynik do wystąpienia A.

Należy pamiętać, że jest to dość brzydkie rozwiązanie, ponieważ wymaga opakowania, które akceptuje wiele rzeczy, które są zasadniczo takie same. Ale osiąga następujące wyniki:

  • Brak zmian w J lub A.
  • Brak „ujawnionych” implementacji J, które są niekompletne.

Jeśli Twój język OO nie zezwala na anonimowe tworzenie instancji interfejsu, możesz utworzyć fikcyjną implementację interfejsu i utworzyć tę instancję.

Deckard
źródło
1

Ile zajmie Ci wdrożenie całego interfejsu?

Jeśli zajmie ci to dużo czasu, oznacza to problem z projektem i radziłbym (tak jak zrobił to przede mną Steven A.), aby skontaktował się z właścicielem i sprawdził, czy można go zmienić w przyszłości.

Czy korzystasz z interfejsu we własnym kodzie, niezależnie od biblioteki interfejsu?

Jak zasugerował Steven A., możesz używać własnego interfejsu, tak jak chcesz widzieć go we własnym kodzie. To przynajmniej utrzymuje twój kod wewnętrzny w czystości. Możesz również wysłać to właścicielowi jako interfejs, którego można się spodziewać. Być może zgadza się z tobą, a może może ci wyjaśnić, dlaczego interfejs nie powinien być podzielony.

Czy twoja implementacja kiedykolwiek potrzebowałaby nieużywanych członków interfejsu?

Jeśli możesz się spodziewać, że nigdy nie zostaną wywołane, ponieważ „nie są obsługiwane”, wolę używać NotSupportedException. Daje to wyraźne wskazanie, że nigdy nie będziesz obsługiwał tego interfejsu, zamiast go zaimplementować.

Steven Jeuris
źródło
Dobry pomysł korzystania z NotSupportedExceptionniego jasno pokazuje, że nie obsługuje tej metody.
ChrisF
@ChrisF: Używam tylko NotImplementedExceptiondo kodu produkcyjnego, w którym inaczej napisałbym „DO ZROBIENIA: zaimplementuj to” Niestety nie pojawiają się na liście zadań Visual Studio . Ale w praktyce nie pozostawiam metody niezaimplementowanej dłużej niż dwa dni. Resharper pokazuje również te wyjątki pogrubioną czcionką (przynajmniej w mojej konfiguracji). :)
Steven Jeuris
0

Zaimplementuj to, czego potrzebujesz, i wyrzuć wyjątek NoImplementedException na pozostałe.

To kwestia użytkowania. Jeśli nie korzystasz z interfejsu lub wiesz, że Twój kod nie będzie korzystał z interfejsu, nie inwestuj w to czasu, ponieważ nie musisz inwestować czasu w zbędny kod.

Pracuj nad danym zadaniem i pozostań dobrą drogą dla innych, jeśli chcą skorzystać z interfejsu.

Wiele interfejsów w Javie nie jest w pełni zaimplementowanych.

To podejście Apache do rzeczy: http://commons.apache.org/lang/api-2.4/org/apache/commons/lang/NotImplementedException.html

Wyświetlana nazwa
źródło
Być może argumentujesz, dlaczego uważasz, że jest to właściwe rozwiązanie. To nic nie dodaje do pytania.
Steven Jeuris
nie ma potrzeby wdrażania wszystkiego, ponieważ nie ma prawdziwej potrzeby wyjaśniania wszystkiego. gdyby to był jego przypadek, części interfejsu byłyby zbędne, natychmiast by je podniósł.
Nazwa wyświetlana