Czy interfejsy powinny znajdować się w osobnym opakowaniu? [Zamknięte]

80

Jestem nowy w zespole pracującym nad dość dużym projektem z wieloma komponentami i zależnościami. Dla każdego komponentu istnieje interfacespakiet, w którym umieszczone są dostępne interfejsy dla tego komponentu. Czy to dobra praktyka?

Moją zwykłą praktyką zawsze było to, że interfejsy i implementacje odbywały się w tym samym pakiecie.

kctang
źródło
4
Dlaczego to jest oznaczone jako [język-niezależny od języka]?
finnw
Inna sytuacja, w której może być to preferowane, jeśli moduł ma być komunikowany przez jakiś RPC zamiast bezpośredniego wywołania - wtedy interfejs jest przydatny do generowania proxy dla klienta?
redzedi

Odpowiedzi:

101

Umieszczenie zarówno interfejsu, jak i implementacji jest powszechne i nie wydaje się być problemem.

Weźmy na przykład Java API - większość klas ma oba interfejsy i ich implementacje zawarte w tym samym pakiecie.

Weźmy na przykład java.utilpakiet:

Zawiera interfejsy, takie jak Set, Map, List, a także uwzględniając implementacje takich jak HashSet, HashMapi ArrayList.

Ponadto Javadoc są zaprojektowane tak, aby dobrze działały w takich warunkach, ponieważ rozdziela dokumentację na widoki Interfejsy i Klasy podczas wyświetlania zawartości pakietu.

Posiadanie pakietów tylko dla interfejsów może być w rzeczywistości trochę przesadzone, chyba że istnieje ogromna liczba interfejsów. Jednak rozdzielanie interfejsów na osobne pakiety tylko po to, by to robić, brzmi jak zła praktyka.

Jeśli konieczne jest odróżnienie nazwy interfejsu od implementacji, można by zastosować konwencję nazewnictwa, aby ułatwić identyfikację interfejsów:

  • Poprzedź nazwę interfejsu rozszerzeniem I. To podejście jest stosowane w przypadku interfejsów w środowisku .NET. Dość łatwo byłoby stwierdzić, że IListjest to interfejs dla listy.

  • Użyj ableprzyrostka - . Takie podejście jest często postrzegane w Java API, takich jak Comparable, Iterablei Serializableaby wymienić tylko kilka.

coobird
źródło
43
+1 chociaż nie polecałbym konwencji nazewnictwa I *, chyba że jesteś w .NETverse, a nawet wtedy tylko dla spójności
CurtainDog,
7
Użyłem tej konwencji w kodzie java i uznałem ją za przydatną ... YMMV
hhafez
5
Wiem, że to trochę stare, ale teraz zasugerowałem, aby wyraźnie nazwać interfejs i nazwać implementację czymś strager. np. Interface = Store Implementation = StoreImpl Głównie w celu podkreślenia, że ​​interfejs powinien być używany podczas implementacji.
cudowny mróz
1
Myślę, że takie podejście, nawet jeśli wygląda dobrze, prowadzi do problemów architektonicznych. Na przykład nie ma sposobu, aby zdefiniować biblioteki, które używają tylko interfejsów, a następnie zaimplementować je we wspólnej bibliotece, czyli warstwie infrastruktury.
Luca Masera
1
Zgadzam się z komentarzem o niestosowaniu przedrostka „I” dla interfejsów w taki sam sposób, w jaki nie polecałbym używania sufiksu „impl” do implementacji, jest to fajny artykuł poświęcony tym przypadkom: octoperf.com/blog/2016 / 10/27 / impl-classes-are-evil
raspacorp
19

W przypadku każdego języka połączenie ich w jednym pakiecie jest w porządku. Ważne jest to, co jest wystawione na świat zewnętrzny i jak wygląda z zewnątrz. Nikt nie będzie wiedział ani nie dbał o to, czy implementacja znajduje się w tym samym pakiecie, czy nie.

Spójrzmy na ten konkretny przypadek.

Jeśli masz wszystkie publiczne rzeczy w jednym pakiecie, a prywatne rzeczy w innym pakiecie, które nie są publicznie ujawniane, klient biblioteki widzi jeden pakiet. Jeśli przenosisz rzeczy prywatne do paczki z rzeczami publicznie ujawnionymi, ale nie ujawniasz ich z paczki, klient widzi dokładnie to samo.

Tak więc ma to zapach reguły bez dobrego powodu: to podejmowanie decyzji na podstawie tego, co jest publicznie widoczne, a decyzja ta nie ma żadnego wpływu na to, co jest publicznie widoczne.

To powiedziawszy, jeśli w jakimkolwiek konkretnym przypadku wydaje się dobrym pomysłem podzielenie interfejsu i implementacji na oddzielne pakiety, zrób to od razu. Przychodzi mi do głowy to, że pakiet jest ogromny lub masz alternatywną implementację, którą możesz chcieć połączyć zamiast standardowej.

cjs
źródło
Biorąc pod uwagę znacznik „Java” w pytaniu - jakie jest znaczenie „ pakietu udostępnianego publicznie ” w Javie? A może Twoja odpowiedź nie dotyczy języka Java? Wydawało mi się, że tylko członkowie pakietu (tacy jak klasy, interfejsy i ich członkowie), a nie same pakiety, mają specyfikatory kontroli dostępu, takie jak `` publiczny '', `` prywatny '' itp. (Jest to omówione dalej @ question stackoverflow.com/questions/4388715/ ... )
Bacar
Właśnie wyjaśniłem sformułowanie; przez „publicznie ujawniony pakiet” naprawdę miałem na myśli „pakiet z publicznie ujawnionymi rzeczami”.
cjs
Z pewnością wszystkie pakiety powinny mieć publiczne rzeczy; jeśli nie, jak możesz korzystać z pakietu?
Bacar
W tym konkretnym przypadku były pakiety, które nie były publiczne, ale były używane przez pakiety, które upubliczniały rzeczy. Tak więc tego pierwszego nie można używać bezpośrednio, ale tylko pośrednio poprzez drugie.
cjs
1
Ale to samo pytanie. W jaki sposób pakiety, które upubliczniają rzeczy, mogą używać pakietów, które niczego nie upubliczniają? Nie znam żadnego sposobu na wymuszenie w Javie „pierwszego nie można używać bezpośrednio, ale tylko pośrednio poprzez drugie”. Jeśli twój pakiet implementacyjny ma tylko prywatne elementy członkowskie, jest tak samo niedostępny dla twojego pakietu interfejsu, jak dla klienta. i podobnie dla innych zakresów dostępu. W Javie nie ma odpowiednika friend(C ++) lub internal(C #).
Bacar
10

W wielu frameworkach, takich jak OSGi, prawie musisz. Myślę, że to promuje luźniejsze połączenie, na opakowaniu zamiast na poziomie słoika.

javamonkey79
źródło
7

Jednym z argumentów przemawiających za umieszczaniem interfejsów w różnych pakietach jest to, że łatwiej jest tworzyć słoiki „api”, które można rozprowadzać wśród konsumentów produktu lub usługi. Jest to całkowicie możliwe dzięki połączeniu interfejsów i implementacji, ale prostsze jest tworzenie skryptów, jeśli znajdują się one w różnych pakietach.

Cameron Pope
źródło
7
Możesz mieć projekt API jako oddzielny (jako oddzielny do dystrybucji), a to nadal nie zatrzymuje interfejsów i implementacji w tym samym pakiecie. Trochę tak, jak masz testy jednostkowe w tym samym pakiecie co przetestowane klasy, ale nadal w osobnym artefakcie kompilacji. Jary i pakiety są w Javie prawie ortogonalne.
George
7

Książka Practical Software Engineering: A Case-Study Approach opowiada się za umieszczaniem interfejsów w oddzielnych projektach / pakietach.

Architektura PCMEF +, o której mówi książka, ma następujące zasady:

  1. Zasada zależności w dół (DDP)
  2. Zasada powiadamiania w górę (UNP)
  3. Zasada komunikacji z sąsiadami (KPK)
  4. Zasada jawnego stowarzyszenia (EAP)
  5. Zasada eliminacji cyklu (CEP)
  6. Zasada nazewnictwa klas (CNP)
  7. Zasada pakietu znajomości (APP)

Opis zasady # 3 i # 7 wyjaśnia, dlaczego jest to dobry pomysł:

Zasada komunikacji z sąsiadem wymaga, aby pakiet mógł komunikować się bezpośrednio tylko z pakietem sąsiednim. Zasada ta gwarantuje, że system nie rozpadnie się na nieściśliwą sieć wzajemnie połączonych obiektów. Aby wyegzekwować tę zasadę, przekazywanie wiadomości między obiektami nie sąsiadującymi wykorzystuje delegację (sekcja 9.1.5.1). W bardziej złożonych scenariuszach pakiet znajomości (sekcja 9.1.8.2) może być używany do grupowania interfejsów w celu ułatwienia współpracy obejmującej odległe pakiety.

Zasada pakietu znajomości jest konsekwencją zasady komunikacji z sąsiadami. Pakiet znajomości składa się z interfejsów, które obiekt przekazuje zamiast konkretnych obiektów w argumentach wywołań metod. Interfejsy można zaimplementować w dowolnym pakiecie PCMEF. To skutecznie umożliwia komunikację między nie sąsiadującymi pakietami, jednocześnie centralizując zarządzanie zależnościami w pojedynczym pakiecie znajomych. Potrzeba pakietu znajomości została wyjaśniona w sekcji 9.1.8.2 i zostanie ponownie omówiona w kontekście PCMEF.

Zobacz ten link: http://comp.mq.edu.au/books/pse/about_book/Ch9.pdf

Kenci
źródło
5

Tak, to bardzo dobra praktyka, ponieważ umożliwia opublikowanie interfejsu bez publikowania konkretnej implementacji. To powiedziawszy, jeśli nie musisz publikować zewnętrznych interfejsów, nie ma problemu z umieszczeniem definicji interfejsów w tym samym pakiecie co implementacja.

Paul Sonier
źródło
2
Może, jeśli definiujesz interfejsy dla JCP lub coś, co ma sens, ale jeśli tworzysz wiele konkretnych implementacji tych interfejsów dla swojego projektu, wydaje się, że niepotrzebne jest pakowanie interfejsów osobno.
Brian Reiter
2
W każdym razie to nie jest duży ciężar ...
Paul Sonier
4
-1 Tak, jest. To sprawia, że ​​niepotrzebnie trudno jest znaleźć implementacje i oddziela rzeczy, które należą do siebie.
Michael Borgwardt
5
A niepublikowanie implementacji to zupełnie inna sprawa, którą można zrobić, ustawiając je jako prywatne pakiety - i NIE można tego zrobić umieszczając je w osobnym pakiecie. Nie ma czegoś takiego jak pakiet niepubliczny.
Michael Borgwardt
1
Co więcej, brzmi to tak, jakby niepotrzebnie upubliczniać wszystkie interfejsy.
Adeel Ansari
4

Robimy to tam, gdzie pracuję (tj. Umieszczamy interfejs w jednym pakiecie, a implementację w innym), a główną zaletą, jaką z tego uzyskujemy, jest możliwość łatwego przełączania się między implementacjami.

hhafez
źródło
7
W jaki sposób pomaga ci to zmienić implementacje?
Janusz
zobacz odpowiedź Camerona Pope'a, właśnie dlatego robimy to tutaj
hhafez
3

Nie mam dużego doświadczenia w Javie, ale lubię to robić jako dobrą praktykę w C # / .NET, ponieważ pozwala to na przyszłą rozbudowę, gdzie zespoły z konkretnymi klasami, które implementują interfejsy, mogą nie być dystrybuowane aż do klienta, ponieważ są one obsługiwane przez pośrednika fabryki lub przez sieć w scenariuszu usługi sieci Web.

Jesse C. Slicer
źródło
2

Zwykle umieszczam interfejsy z implementacją, ale widzę, dlaczego możesz chcieć je oddzielić. Powiedzmy na przykład, że ktoś chciałby ponownie zaimplementować klasy na podstawie twoich interfejsów, potrzebowałby jar / lib / etc z twoją implementacją, a nie tylko interfejsami. Mając je oddzielne, możesz po prostu powiedzieć „Oto moja implementacja tego interfejsu” i skończyć z tym. Jak powiedziałem, nie to, co robię, ale w pewnym sensie rozumiem, dlaczego niektórzy mogą chcieć.

Matt_JD
źródło
2

Robię to na projekcie i działa to dobrze z następujących powodów:

  • Oddzielnie spakowane interfejsy i implementacje są dla innego przypadku użycia niż dla różnych typów Map lub Set. Nie ma powodu, aby mieć pakiet tylko dla drzew (java.util.tree.Map, java.util.tree.Set). To tylko standardowa infrastruktura danych, więc połącz ją z innymi strukturami danych. Jeśli jednak masz do czynienia z grą logiczną, która ma naprawdę prosty interfejs do debugowania i ładny interfejs produkcyjny, jako część jej interfejsu możesz mieć com.your.app.skin.debug i com.your.app .skin.pretty. Nie umieszczałbym ich w tym samym pakiecie, ponieważ robią różne rzeczy i wiem, że skorzystałbym z jakiejś SmurfNamingConvention (DebugAttackSurface, DebugDefenceSurface, PrettyAttackSurface itp.), Aby utworzyć nieformalną przestrzeń nazw dla tych dwóch, gdyby były w tym samym pakiecie.

  • Moim obejściem problemu znajdowania powiązanych interfejsów i implementacji, które są w oddzielnych pakietach, jest przyjęcie konfiguracji nazewnictwa dla moich pakietów. Np. Mogę mieć wszystkie moje interfejsy w com.your.app.skin.framework i wiedzieć, że inne pakiety na tym samym poziomie drzewa pakietów są implementacjami. Wadą jest to, że jest to niekonwencjonalna konwencja. Szczerze, zobaczę jak dobry jest ten konwent za 6 miesięcy :)

  • Nie używam tej techniki religijnie. Istnieją interfejsy, które mają sens tylko w konkretnej implementacji. Nie umieszczam ich w pakiecie ramowym. Jest kilka pakietów, w których nie wygląda na to, żebym utworzył 40 różnych klas implementacji, więc nie przejmuję się.

  • Moja aplikacja korzysta z guice i ma bardzo wiele interfejsów.

Pytania dotyczące projektu programu zwykle wiążą się z zaletami i wadami i nie ma jednej odpowiedzi, która byłaby odpowiednia dla wszystkich. Na przykład, dlaczego miałbyś kiedykolwiek używać tej techniki w małym 200-liniowym programie? Ma to sens dla mnie, biorąc pod uwagę moje inne wybory architektoniczne, dla mojego konkretnego problemu. :)

Żółtaczka mureny
źródło
1

Wolę je w tym samym opakowaniu. Tworzenie pakietu specjalnie dla interfejsów nie ma sensu. Jest to szczególnie zbędne dla zespołów, które po prostu uwielbiają swoje przedrostki i sufiksy interfejsu (np. „I” i „Impl” w przypadku języka Java).

Jeśli istnieje potrzeba opublikowania zestawu interfejsów jako publicznego interfejsu API, bardziej sensowne jest przechowywanie ich w całkowicie oddzielnym projekcie i tworzenie zależności między projektami. Ale wszystko sprowadza się do preferencji i wygody sytuacji, jak przypuszczam.

aberrant80
źródło
0

Umieść je w pakietach, które odzwierciedlają Twoje projekty. Łączenie interfejsów i implementacji razem, jeśli są częścią tego samego projektu, jest dobre i powszechne, ale jeśli piszesz API, prawdopodobnie ktoś inny wybierze nazwę pakietu odpowiednią dla swojego projektu.

Ogólnie rzecz biorąc, jeśli dotyczy tego samego projektu, nie widzę żadnej korzyści z utrzymywania interfejsów w oddzielnym pakiecie od ich implów. Jeśli robi się zagracony, mogą wystąpić inne problemy z nazwami pakietów, niezależnie od układu interfejsu.

Rog
źródło
0

Myślę, że ważne jest, aby zauważyć, że dla frameworka OSGi ładniej jest, że są one w różnych pakietach, dzięki czemu można łatwo wyeksportować cały pakiet, ukrywając pakiety implementacyjne.

jkabugu
źródło
-1

Istnieje wiele dobrych powodów, dla których należy umieścić interfejsy w oddzielnym pakiecie od implementacji. Na przykład możesz chcieć użyć kontenera, który obsługuje wstrzykiwanie zależności, do połączenia niezbędnych implementacji w czasie wykonywania, w którym to przypadku tylko interfejs musi być obecny w czasie kompilacji, a implementację można dostarczyć w czasie wykonywania. Alternatywnie, możesz chcieć udostępnić wiele implementacji (na przykład używając implementacji próbnych do testowania) lub wiele wersji konkretnej implementacji w czasie wykonywania (na przykład do testów A / B itp.). W tego rodzaju przypadkach wygodniej jest osobno spakować interfejs i implementację.

Tyson
źródło
Nie musisz umieszczać implementacji w innym pakiecie, aby wykonać jedną z wymienionych rzeczy
Crowie,
To sprawiedliwe, ale nadal uważam, że różnica wymagań „obecność w czasie kompilacji w czasie wykonywania” jest przekonującym przypadkiem w przypadku oddzielnego pakowania. Zaktualizowałem tekst, aby lepiej to odzwierciedlić.
Tyson,