Domyślnie vs Impl podczas implementacji interfejsów w Javie

34

Po przeczytaniu Czy nazwy pakietów powinny być w liczbie pojedynczej czy mnogiej? przyszło mi do głowy, że nigdy nie widziałem właściwej debaty na temat jednego z moich domowych pomysłów: nazewnictwa implementacji interfejsów.

Załóżmy, że masz interfejs, Orderktóry ma być implementowany na różne sposoby, ale implementacja jest początkowa tylko przy pierwszym tworzeniu projektu. Czy iść do DefaultOrderlub OrderImplczy jakiś inny wariant, aby uniknąć fałszywych dychotomii? A co robisz, gdy pojawi się więcej wdrożeń?

I najważniejsze ... dlaczego?

Gary Rowe
źródło

Odpowiedzi:

59

Nazwy mają okazję przekazać znaczenie. Dlaczego miałbyś rzucić tę szansę z Impl?

Przede wszystkim, jeśli będziesz mieć tylko jedną implementację, zrezygnuj z interfejsu. Stwarza to problem z nazewnictwem i nic nie dodaje. Co gorsza, może to powodować problemy z niespójnymi podpisami metod w interfejsach API, jeśli ty i wszyscy inni programiści nie będziecie ostrożni, aby zawsze używać tylko interfejsu.

Biorąc to pod uwagę, możemy założyć, że każdy interfejs ma lub może mieć dwie lub więcej implementacji.

  • Jeśli masz teraz tylko jeden i nie wiesz, w jaki sposób ten drugi może być inny, domyślnie jest dobrym początkiem.

  • Jeśli masz teraz dwa, nazwij każdy zgodnie z jego przeznaczeniem.

    Przykład: Niedawno mieliśmy konkretny kontekst klasy (w odniesieniu do bazy danych). Zdano sobie sprawę, że musimy być w stanie reprezentować kontekst, który był offline, więc nazwa Kontekst została użyta dla nowego interfejsu (w celu zachowania kompatybilności ze starymi interfejsami API) i utworzono nową implementację OfflineContext . Ale zgadnij, na co zmieniono nazwę oryginału? Zgadza się, ContextImpl (yikes).

    W takim przypadku DefaultContext prawdopodobnie będzie w porządku, a ludzie to zrozumieją, ale nie jest tak opisowy, jak mógłby być. W końcu, jeśli nie jest offline , co to jest? Więc poszliśmy z: OnlineContext .


Przypadek specjalny: użycie przedrostka „I” na interfejsach

Jedna z pozostałych odpowiedzi sugeruje użycie przedrostka I w interfejsach. Najlepiej nie musisz tego robić.

Jeśli jednak potrzebujesz zarówno interfejsu do niestandardowych implementacji, ale masz również podstawową konkretną implementację, która będzie często używana, a podstawowa nazwa jest zbyt prosta, aby zrezygnować z samego interfejsu, możesz rozważyć dodanie „I” do interfejsu (choć jest całkowicie w porządku, jeśli nadal nie jest odpowiedni dla ciebie i twojego zespołu).

Przykład: Wiele obiektów może być „EventDispatcher”. Ze względu na interfejsy API musi to być zgodne z interfejsem. Ale chcesz również podać podstawowy moduł wysyłania zdarzeń do delegowania. DefaultEventDispatcher byłoby w porządku, ale to trochę długo, a jeśli będą często widząc jego nazwę, to może wolą używać nazwy baza EventDispatcher dla konkretnej klasy i wdrożenia IEventDispatcher dla niestandardowych implementacji:

/* Option 1, traditional verbose naming: */
interface EventDispatcher { /* interface for all event dispatchers */ }
class DefaultEventDispatcher implements EventDispatcher {
  /* default event dispatcher */
}

/* Option 2, "I" abbreviation because "EventDispatcher" will be a common default: */
interface IEventDispatcher { /* interface for all event dispatchers */ }
class EventDispatcher implements IEventDispatcher {
  /* default event dispatcher. */
}
Nicole
źródło
3
+1 za solidną odpowiedź. Podobnie jak uzasadnienie nieużywania Impl.
Gary Rowe
2
+1 absolutnie się zgadza. Nazwanie interfejsu z dala od koncepcji domeny, którą reprezentuje, zupełnie nie ma sensu.
rupjones
9
„jeśli kiedykolwiek będziesz mieć tylko jedno wdrożenie”, skąd wiesz, że będziesz miał tylko jedno wdrożenie? „każdy interfejs ma lub może mieć dwie lub więcej implementacji” ... przed posiadaniem dwóch implementacji, najpierw nie ma żadnej, ty masz jedną, potem masz dwie, to znaczy może być moment, kiedy masz tylko jedną implementację, po prostu przed wdrożeniem drugiego.
Tulains Córdova
2
@NickC Nie jestem pedantinem na temat semantyki (jestem poetą i nawet o tym nie wiedziałem). Angielski nie jest moim językiem ojczystym, więc nie mogę być pedantyczny. Mówiłem o wadliwej logice. Do odsprzęgania używasz interfejsów. To nie wymaga pewnej liczby wdrożeń.
Tulains Córdova
4
if you will only ever have one implementation, do away with the interface- chyba że chcesz przetestować komponent, w którym to przypadku możesz chcieć zachować ten interfejs w celu utworzenia MockOrder, OrderStub lub podobnego.
JBRWilkinson
15

Nazewnictwo decyduję na podstawie przypadku użycia interfejsu.

Jeśli używany jest interfejs oddzielania , wybieram Implimplementacje.

Jeśli celem interfejsu jest abstrakcja behawioralna , wówczas implementacje są nazywane zgodnie z tym, co konkretnie robią. Często dołączam do tego nazwę interfejsu. Więc jeśli interfejs zostanie wywołanyValidator , używam FooValidator.

Uważam, że Defaultto bardzo zły wybór. Najpierw zanieczyszcza funkcje uzupełniania kodu, ponieważ nazwy zawsze zaczynają się od niego. Inną kwestią jest to, że domyślna wartość może ulec zmianie z czasem. To, co pierwsze może być domyślnym, może po pewnym czasie stać się przestarzałą funkcją. Dlatego albo zawsze zaczynasz zmieniać nazwę zajęć, gdy tylko zmienią się wartości domyślne, albo żyjesz z wprowadzającymi w błąd nazwami.

SpaceTrucker
źródło
8

Zgadzam się z odpowiedzią Nicole (szczególnie, że interfejs prawdopodobnie nie jest konieczne w większości przypadków), ale dla dobra dyskusji będę wyrzucać dodatkową alternatywę innych niż OrderImplI DefaultOrder: ukryć realizację za statycznej metody fabryki podobnego Orders.create(). Na przykład:

public final class Orders {
  public static Order create() {
    return new Order() {
      // Implementation goes here.
    };
  }
}

Przy takim podejściu implementacją może być anonimowa klasa wewnętrzna lub klasa prywatna DefaultImpl nazwie lub w nazwie, lub może być nazwana czymś zupełnie innym. Niezależnie od tego, który wybierzesz, dzwoniący nie musi się tym przejmować, więc zyskujesz większą elastyczność teraz i później, kiedy / jeśli zdecydujesz się go zmienić.

Doskonałymi przykładami tego wzorca w praktyce są klasy java.util.Collectionsi java.util.concurrent.Executorsnarzędzia, których metody zwracają ukryte implementacje. Jak wspomina Effective Java (w punkcie 1), ten wzorzec może pomóc w zmniejszeniu „wagi koncepcyjnej” interfejsu API.

Andrew McNamee
źródło
+1 za interesujący dodatkowy przypadek: anonimowe wdrożenia.
Gary Rowe,
1
Również szeroko stosowany w Guava .
Nicole
3

Zawsze wybieram po OrderImplprostu dlatego, że pokazuje się alfabetycznie zaraz po Orderinterfejsie.

Ben Hoffstein
źródło
2
Jak poradzić sobie z kolejnymi dalszymi wdrożeniami, jak je obsługiwać i tak dalej?
Gary Rowe
1
Zapytałeś o wstępną implementację. Po rozpoczęciu implementacji zamówienia krajowego, zamówienia zagranicznego itp. Są one wymieniane z większą starannością i bez względu na ich pozycję w alfabecie.
Ben Hoffstein,
Całkiem słuszne - zredagowałem oryginalne pytanie, aby odzwierciedlić ten nowy wymóg.
Gary Rowe
Nie jest to dobry pomysł Jeśli będzie więcej niż jedno wdrożenie.
Sadegh,
0

Możesz nazwać interfejs z prefiksem I (IWhthing), a następnie dokonać implementacji Cokolwiek.

Oficjalne konwencje kodu Java nie mówią o tego rodzaju nazewnictwie interfejsów, jednak ten rodzaj nazewnictwa ułatwia rozpoznawanie i nawigację.

Matthieu
źródło
Dlaczego miałbyś prefiksować interfejs literą I? Czy ma to ułatwić rozpoznanie w nawigacji?
Gary Rowe,
@Gary: prefiksowanie typów interfejsów za pomocą I jest ugruntowaną konwencją w języku Delphi. W klasach Delphi typy są prefiksowane przez T. Tak więc mielibyśmy interfejs IOrder i domyślną implementację TOrder z TSomethingOrder i TBullMarketOrder jako specyficznymi implementacjami.
Marjan Venema
3
Widziałem, że ta konwencja jest również często używana w programowaniu .NET. Być może dlatego, że korzysta z niego dokumentacja i przykłady Microsoftu.
Ben Hoffstein
5
Wygląda na to, że @Renesis wymyślił przydatny przypadek użycia go w Javie. Osobiście polegałbym na IDE, aby powiedzieć mi różnicę, w przeciwnym razie mielibyśmy E dla Enums, C dla klas i wszystkie w dużej mierze zbędne.
Gary Rowe
0

Myślę, że chociaż Domyślny może mieć sens w niektórych przypadkach, bardziej pomocne byłoby opisanie implementacji. Więc jeśli interfejs jest UserProfileDAOwtedy twoje implementacje mogą być UserProfileSQLDAOlub UserProfileLDAPDAOczy coś takiego.

nerwowy
źródło
0

jeśli to możliwe, nazwij go po tym, co / jak to robi.

zwykle najgorsze podejście to nazywanie go po tym, jak ma być używany.

Jeśli ma być używany jako klasa bazowa do implementacji, możesz użyć BaseX lub AbstractX (jeśli jest abstrakcyjny (ale spróbuj zgnieść to, co robi), ponieważ jeśli nie zrobiłby nic, nie stworzyłbyś go interfejs) Jeśli zapewnia najprostszą możliwą funkcjonalność i oczekuje się, że będzie używana bezpośrednio (nie rozszerzając jej), gdy taka funkcjonalność wystarczy, możesz nazwać ją SimpleX lub BasicX.

Jeśli jest używany, chyba że zapewniona jest inna implementacja, nazwij go DefaultX

użytkownik470365
źródło
-2

Większość tych odpowiedzi opisuje, co się dzieje, ale nie dlaczego.

Co stało się z zasadami OO? Co Impl mówi mi o klasie i jak z niej korzystać? Powinieneś martwić się zrozumieniem zwyczajów, a nie ich budowy.

Jeśli utworzę interfejs Person, czym jest PersonImpl? Bez znaczenia. Przynajmniej DefaultPerson mówi mi, że ktokolwiek go kodował, nie powinien był tworzyć interfejsu.

Interfejsy typują polimorfizm i powinny być używane jako takie.

użytkownik108620
źródło
1
Akceptowana odpowiedź @NickC zawiera szczegółowe informacje na temat tego, co jest robione i dlaczego.
Gary Rowe,