Jakiej strategii używasz do nazewnictwa pakietów w projektach Java i dlaczego? [Zamknięte]

88

Myślałem o tym jakiś czas temu i niedawno pojawił się ponownie, ponieważ mój sklep tworzy swoją pierwszą prawdziwą aplikację internetową w języku Java.

Jako wprowadzenie widzę dwie główne strategie nazewnictwa pakietów. (Żeby było jasne, nie odnoszę się do całej części tego „domena.firma.project”, mówię o konwencji pakietu poniżej.) W każdym razie, widzę konwencje nazewnictwa pakietów:

  1. Funkcjonalne: nazywanie pakietów zgodnie z ich funkcją architektoniczną, a nie ich tożsamością zgodnie z domeną biznesową. Innym terminem na to może być nazywanie według „warstwy”. Tak więc miałbyś pakiet * .ui, pakiet * .domain i * .orm. Twoje pakiety to poziome plasterki, a nie pionowe.

    Jest to znacznie częstsze niż nazewnictwo logiczne. Właściwie nie wierzę, żebym kiedykolwiek widział lub słyszał o projekcie, który to robi. To oczywiście sprawia, że ​​jestem nieufny (trochę tak, jakbyś pomyślał, że znalazłeś rozwiązanie problemu NP), ponieważ nie jestem zbyt mądry i zakładam, że każdy musi mieć świetne powody, aby robić to tak, jak robią. Z drugiej strony nie sprzeciwiam się temu, by ludzie po prostu tęsknili za słoniem w pokoju i nigdy nie słyszałem prawdziwego argumentu za takim nazewnictwem paczek. Po prostu wydaje się, że jest to de facto standard.

  2. Logiczne: nazywanie pakietów zgodnie z tożsamością domeny biznesowej i umieszczanie w tym pakiecie każdej klasy, która ma do czynienia z tym pionowym wycinkiem funkcjonalności.

    Nigdy o tym nie widziałem ani nie słyszałem, jak wspomniałem wcześniej, ale ma to dla mnie mnóstwo sensu.

    1. Zwykle podchodzę do systemów w pionie, a nie w poziomie. Chcę wejść i rozwijać system obsługi zamówień, a nie warstwę dostępu do danych. Oczywiście istnieje duża szansa, że ​​podczas tworzenia tego systemu dotknę warstwy dostępu do danych, ale chodzi o to, że nie myślę o tym w ten sposób. Oznacza to oczywiście, że kiedy otrzymuję zlecenie zmiany lub chcę zaimplementować jakąś nową funkcję, byłoby miło nie musieć wędrować w grupie pakietów, aby znaleźć wszystkie powiązane klasy. Zamiast tego po prostu zaglądam do pakietu X, ponieważ to, co robię, ma związek z X.

    2. Z punktu widzenia programisty uważam, że główną zaletą jest dokumentowanie przez pakiety domeny biznesowej, a nie architektury. Wydaje mi się, że domena jest prawie zawsze tą częścią systemu, która jest trudniejsza do zrozumienia, gdzie architektura systemu, zwłaszcza w tym momencie, staje się prawie przyziemna w jego implementacji. Fakt, że mogę dojść do systemu z tego typu konwencją nazewnictwa i od razu z nazewnictwa paczek wiem, że zajmuje się on zamówieniami, klientami, przedsiębiorstwami, produktami itp. Wydaje się cholernie przydatny.

    3. Wygląda na to, że pozwoliłoby to znacznie lepiej wykorzystać modyfikatory dostępu Javy. Pozwala to na znacznie bardziej przejrzyste definiowanie interfejsów w podsystemach, a nie w warstwach systemu. Więc jeśli masz podsystem zamówień, który chcesz, aby był przezroczysty trwały, teoretycznie możesz po prostu nigdy nie pozwolić nikomu innemu wiedzieć, że jest trwały, ponieważ nie musisz tworzyć publicznych interfejsów do jego klas trwałości w warstwie dao i zamiast tego pakować klasę dao w tylko z zajęciami, którymi się zajmuje. Oczywiście, jeśli chcesz ujawnić tę funkcjonalność, możesz zapewnić jej interfejs lub upublicznić. Po prostu wygląda na to, że wiele z tego tracisz, dzieląc pionowy fragment funkcji systemu na wiele pakietów.

    4. Przypuszczam, że jedną wadą, którą widzę, jest to, że sprawia, że ​​zrywanie warstw jest trochę trudniejsze. Zamiast po prostu usunąć lub zmienić nazwę pakietu, a następnie upuścić nowy w miejscu z alternatywną technologią, musisz wejść i zmienić wszystkie klasy we wszystkich pakietach. Jednak nie widzę, żeby to była wielka sprawa. Może to wynikać z braku doświadczenia, ale muszę sobie wyobrazić, że ilość razy, kiedy wymieniasz technologie, blednie w porównaniu z ilością razy, kiedy wchodzisz i edytujesz pionowe wycinki funkcji w swoim systemie.

Więc myślę, że wtedy padłoby pytanie, jak nazwać swoje paczki i dlaczego? Proszę zrozum, że niekoniecznie myślę, że natknąłem się na złotą gęś czy coś tutaj. Jestem całkiem nowy w tym wszystkim, mając głównie doświadczenie akademickie. Jednak nie mogę dostrzec luk w moim rozumowaniu, więc mam nadzieję, że wszyscy to potrafią, abym mógł przejść dalej.

Tim Visher
źródło
To, co do tej pory słyszałem, wydaje się wskazywać, że jest to rodzaj wyroku. Jednak większość odpowiedzi nie koncentruje się na względach praktycznych. Więcej tego szukam. Jednak dzięki za dotychczasową pomoc!
Tim Visher,
Nie sądzę, żeby to był wyrok. Najpierw należy podzielić według warstwy, a następnie według funkcjonalności. Aktualizuję odpowiedź.
eljenso
@eljenso: powiedz to chłopakom z Eclipse :) W eclipse pierwszy podział to "cechy", czyli jednostki rozmieszczenia, a także funkcjonalność. Wewnątrz „funkcji” znajdują się „wtyczki”, które są zwykle podzielone na warstwy - ale wtyczki w ramach tej samej „funkcji” muszą być zawsze wdrażane razem. Jeśli masz tylko jedną jednostkę wdrożeniową, to najpierw dzielenie według warstw, a dopiero potem według funkcjonalności może mieć sens. Przy wielu jednostkach wdrożeniowych nie chcesz wdrażać tylko warstwy prezentacji - potrzebujesz dodatkowej funkcjonalności.
Peter Štibraný
Mam konkretne pytanie dotyczące warstwy biznesowej aplikacji, jaka powinna być konwencja nazewnictwa?
Bionix1441

Odpowiedzi:

32

W przypadku projektowania opakowań najpierw dzielę według warstw, a następnie według innych funkcji.

Istnieją dodatkowe zasady:

  1. warstwy są układane od najbardziej ogólnych (na dole) do najbardziej szczegółowych (na górze)
  2. każda warstwa ma interfejs publiczny (abstrakcja)
  3. warstwa może zależeć tylko od publicznego interfejsu innej warstwy (hermetyzacja)
  4. warstwa może zależeć tylko od bardziej ogólnych warstw (zależności od góry do dołu)
  5. warstwa korzystnie zależy od warstwy znajdującej się bezpośrednio pod nią

Na przykład w przypadku aplikacji internetowej możesz mieć następujące warstwy w warstwie aplikacji (od góry do dołu):

  • warstwa prezentacji: generuje interfejs użytkownika, który będzie wyświetlany w warstwie klienta
  • warstwa aplikacji: zawiera logikę specyficzną dla aplikacji, stanową
  • warstwa usług: grupuje funkcjonalność według domeny, bezstanowa
  • warstwa integracji: zapewnia dostęp do warstwy zaplecza (db, jms, e-mail, ...)

Oto kilka dodatkowych reguł dotyczących wynikowego układu pakietu:

  • katalog główny każdej nazwy pakietu to <prefix.company>.<appname>.<layer>
  • interfejs warstwy jest dodatkowo podzielony przez funkcjonalność: <root>.<logic>
  • prywatna implementacja warstwy jest poprzedzona przedrostkiem private: <root>.private

Oto przykładowy układ.

Warstwa prezentacji jest podzielona według technologii wyświetlania i opcjonalnie (grup) aplikacji.

com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...

Warstwa aplikacji jest podzielona na przypadki użycia.

com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...

Warstwa usług jest podzielona na domeny biznesowe, na które wpływa logika domeny w warstwie zaplecza.

com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...

Warstwa integracji jest podzielona na „technologie” i obiekty dostępu.

com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...

Zaletą takiego oddzielania pakietów jest to, że łatwiej jest zarządzać złożonością oraz zwiększa możliwości testowania i ponownego wykorzystania. Chociaż wydaje się, że jest to duże obciążenie, z mojego doświadczenia wynika, że ​​jest to bardzo naturalne i wszyscy pracujący nad tą strukturą (lub podobną) odbierają ją w ciągu kilku dni.

Dlaczego uważam, że podejście wertykalne nie jest tak dobre?

W modelu warstwowym kilka różnych modułów wysokiego poziomu może używać tego samego modułu niższego poziomu. Na przykład: możesz zbudować wiele widoków dla tej samej aplikacji, wiele aplikacji może korzystać z tej samej usługi, wiele usług może korzystać z tej samej bramy. Sztuczka polega na tym, że podczas przechodzenia przez warstwy zmienia się poziom funkcjonalności. Moduły w bardziej szczegółowych warstwach nie odwzorowują 1-1 na modułach z bardziej ogólnej warstwy, ponieważ poziomy funkcjonalności, które wyrażają, nie odwzorowują 1-1.

Kiedy używasz podejścia wertykalnego do projektowania opakowań, tj. Najpierw dzielisz według funkcjonalności, następnie wsuwasz wszystkie elementy składowe o różnych poziomach funkcjonalności do tej samej „kurtki funkcjonalnej”. Możesz zaprojektować swoje ogólne moduły dla bardziej szczegółowych. Ale to narusza ważną zasadę, że bardziej ogólna warstwa nie powinna wiedzieć o bardziej szczegółowych warstwach. Na przykład warstwa usług nie powinna być modelowana na podstawie koncepcji z warstwy aplikacji.

eljenso
źródło
„Zaletą takiego rozdzielania pakietów jest to, że łatwiej jest zarządzać złożonością oraz zwiększa możliwości testowania i ponownego wykorzystania”. Naprawdę nie wchodzisz w powody, dla których tak jest.
mangoDrunk
1
Nie wchodzisz w powody, dla których tak nie jest . Ale w każdym razie ... zasady organizowania są dość jasne i proste, więc zmniejsza złożoność. Jest bardziej testowalny i wielokrotnego użytku ze względu na samą organizację. Każdy może to wypróbować, a potem możemy się zgodzić lub nie. Pokrótce wyjaśniam niektóre wady podejścia wertykalnego, pośrednio podając powody, dla których uważam, że podejście horyzontalne jest łatwiejsze.
eljenso
8
W tym artykule całkiem ładnie wyjaśniono, dlaczego lepiej jest używać pakiet po funkcji zamiast pakietu po warstwie .
Tom
@eljenso „Nie wchodzisz w powody, dla których tak nie jest”, próbując przenieść ciężar dowodu? Myślę, że artykuł opublikowany przez Toma bardzo ładnie wyjaśnia, dlaczego pakiet po funkcji jest lepszy i mam trudności z określeniem „Nie sądzę, że to kwestia oceny. Najpierw należy podzielić według warstwy, a następnie według funkcjonalności. iść "poważnie.
pka
18

Uważam, że trzymam się zasad projektowania opakowań wujka Boba . Krótko mówiąc, klasy, które mają być ponownie używane razem i razem zmieniane (z tego samego powodu, np. Zmiana zależności lub zmiana struktury), powinny być umieszczone w tym samym pakiecie. IMO, podział funkcjonalny miałby większe szanse na osiągnięcie tych celów niż podział branżowy / biznesowy w większości aplikacji.

Na przykład poziomy wycinek obiektów domeny może być ponownie wykorzystany przez różne rodzaje front-endów, a nawet aplikacje, a poziomy fragment frontonu sieci Web prawdopodobnie zmieni się razem, gdy konieczna będzie zmiana podstawowej struktury sieciowej. Z drugiej strony łatwo sobie wyobrazić efekt falowania tych zmian w wielu pakietach, jeśli klasy z różnych obszarów funkcjonalnych są zgrupowane w tych pakietach.

Oczywiście nie wszystkie rodzaje oprogramowania są takie same, a podział pionowy może mieć sens (jeśli chodzi o osiągnięcie celów ponownego wykorzystania i możliwości zmiany) w niektórych projektach.

Buu Nguyen
źródło
Dzięki, to jest bardzo jasne. Jak powiedziałem w pytaniu, wydaje mi się, że liczba przypadków wymiany technologii jest znacznie mniejsza niż w przypadku pionowych fragmentów funkcjonalności. Czy to nie było twoje doświadczenie?
Tim Visher
Nie chodzi tylko o technologie. Punkt 1 twojego oryginalnego posta ma sens tylko wtedy, gdy pionowe wycinki są niezależnymi aplikacjami / usługami komunikującymi się ze sobą przez jakiś interfejs (SOA, jeśli wolisz). więcej poniżej ...
Buu Nguyen
Teraz, gdy przechodzisz do szczegółów każdej z tych drobnoziarnistych aplikacji / usług, które mają własne GUI / firmę / dane, nie mogę sobie wyobrazić, że zmienia się w pionowym wycinku, czy chodzi o technologię, zależność, regułę / przepływ pracy, logowanie , zabezpieczenia, styl interfejsu użytkownika, można całkowicie odizolować od innych wycinków.
Buu Nguyen
Byłbym zainteresowany Twoją opinią na temat mojej odpowiedzi
i3ensays
@BuuNguyen Link do zasad projektowania opakowań jest uszkodzony.
johnnieb
5

Zwykle występują oba poziomy podziału. Od góry znajdują się jednostki rozmieszczające. Nazywa się je „logicznie” (w twoim rozumieniu, pomyśl o funkcjach Eclipse). Wewnątrz jednostki wdrożeniowej masz funkcjonalny podział pakietów (pomyśl o wtyczkach Eclipse).

Na przykład, funkcja jest com.feature, i składa się z com.feature.client, com.feature.corei com.feature.uiwtyczek. Wewnątrz wtyczek mam bardzo mały podział na inne pakiety, chociaż to też nie jest niezwykłe.

Aktualizacja: Przy okazji, Juergen Hoeller mówi o organizacji kodu w InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen jest jednym z architektów Springa i dużo o tym wie.

Peter Štibraný
źródło
Nie całkiem nadążam. Zwykle możesz zobaczyć com.apache.wicket.x, gdzie x jest funkcjonalne lub logiczne. Zwykle nie widzę com.x. Chyba mówisz, że wybrałbyś strukturę firma.firma.project.feature.layer? Czy masz powody?
Tim Visher
Powodem jest to, że „com.company.project.feature” jest jednostką wdrażania. Na tym poziomie niektóre funkcje są opcjonalne i można je pominąć (tj. Nie są wdrażane). Jednak funkcja wewnętrzna nie jest opcjonalna i zazwyczaj chcesz mieć je wszystkie. Tutaj bardziej sensowne jest dzielenie się warstwami.
Peter Štibraný
4

Większość projektów java, nad którymi pracowałem, dzieli pakiety java najpierw funkcjonalnie, a potem logicznie.

Zwykle części są wystarczająco duże, aby zostały podzielone na oddzielne artefakty kompilacji, w których można umieścić podstawową funkcjonalność w jednym słoiku, interfejs API w innym, elementy interfejsu WWW w pliku wojennym itp.

Ben Hardy
źródło
Jak to mogłoby wyglądać? domain.company.project.function.logicalSlice?
Tim Visher,
prawie! egdcpweb.profiles, dcpweb.registration, dcpapis, dcppersistence itp. ogólnie działa całkiem nieźle, przebieg może się różnić. zależy również od tego, czy używasz modelowania domen - jeśli masz wiele domen, możesz najpierw podzielić według domen.
Ben Hardy,
Osobiście wolę coś przeciwnego, logicznego niż funkcjonalnego. apple.rpc, apples.model, a następnie banana.model, banana.store
mP.
Możliwość grupowania wszystkich elementów związanych z jabłkami ma większą wartość niż grupowanie wszystkich elementów internetowych.
mP.
3

Pakiety mają być kompilowane i dystrybuowane jako całość. Rozważając, jakie klasy należą do pakietu, jednym z kluczowych kryteriów są jego zależności. Od jakich innych pakietów (w tym bibliotek innych firm) zależy ta klasa. Dobrze zorganizowany system połączy w pakiet klasy o podobnych zależnościach. Ogranicza to wpływ zmiany w jednej bibliotece, ponieważ będzie od niej zależeć tylko kilka dobrze zdefiniowanych pakietów.

Wygląda na to, że Twój logiczny, pionowy system może mieć tendencję do „rozmazania” zależności w większości pakietów. Oznacza to, że jeśli każda funkcja jest spakowana jako wycinek pionowy, każdy pakiet będzie zależał od każdej używanej biblioteki innej firmy. Każda zmiana w bibliotece prawdopodobnie wpłynie na cały system.

erickson
źródło
Ach. Więc jedna rzecz, która robi poziome plasterki, to ochrona przed faktycznymi aktualizacjami w bibliotekach, których używasz. Myślę, że masz rację, że pionowe plasterki zacierają każdą zależność, jaką twój system ma w każdym pakiecie. Dlaczego to taka wielka sprawa?
Tim Visher,
Jak na „funkcję”, to nie jest taka wielka sprawa. Są bardziej niestabilne i tak czy inaczej mają więcej zależności. Z drugiej strony, ten kod „klienta” ma zwykle niewielką możliwość ponownego użycia. W przypadku czegoś, co ma być biblioteką wielokrotnego użytku, izolowanie klientów od każdej drobnej zmiany to świetna inwestycja.
erickson,
3

Osobiście wolę logiczne grupowanie klas, które zawierają podpakiet dla każdego funkcjonalnego uczestnictwa.

Cele pakowania

W pakietach chodzi w końcu o grupowanie rzeczy - ideą jest to, że powiązane ze sobą klasy żyją blisko siebie. Jeśli mieszkają w tym samym pakiecie, mogą skorzystać z pakietu prywatnego, aby ograniczyć widoczność. Problem polega na tym, że wrzucanie całego widoku i wytrwałości do jednego pakietu może prowadzić do pomieszania wielu klas w jeden pakiet. Następną rozsądną rzeczą do zrobienia jest utworzenie odpowiednio widoku, trwałości, pakietów użytkowych i klas refaktoryzacji. Niestety chroniony i prywatny zakres pakietu nie wspiera koncepcji obecnego pakietu i pakietu podrzędnego, ponieważ pomogłoby to w egzekwowaniu takich zasad widoczności.

Widzę teraz wartość w separacji poprzez funkcjonalność, ponieważ jaka jest wartość grupowania wszystkich rzeczy związanych z widokiem. Rzeczy w tej strategii nazewnictwa zostają rozłączone z niektórymi klasami w widoku, podczas gdy inne trwają i tak dalej.

Przykład mojej logicznej struktury opakowania

Dla ilustracji nazwijmy dwa moduły - niewłaściwie używajmy modułu nazw jako koncepcji grupującej klasy w określonej gałęzi drzewa pakietów.

apple.model apple.store banana.model banana.store

Zalety

Klient korzystający z Banana.store.BananaStore ma dostęp tylko do funkcji, które chcemy udostępnić. Wersja hibernacji jest szczegółem implementacji, którego nie muszą być świadomi ani nie powinni widzieć tych klas, ponieważ dodają bałagan do operacji przechowywania.

Inne zalety logiczne v funkcjonalne

Im dalej w górę w kierunku korzenia, tym szerszy staje się zakres i rzeczy należące do jednego pakietu zaczynają wykazywać coraz więcej zależności od elementów należących do ich modułów. Gdyby zbadać na przykład moduł „bananowy”, większość zależności ograniczałaby się do tego modułu. W rzeczywistości do większości pomocników w kategorii „banana” nie byłoby w ogóle odwołań poza zakresem tego pakietu.

Dlaczego funkcjonalność?

Jaką wartość osiąga się zbierając rzeczy w oparciu o funkcjonalność. W takim przypadku większość klas jest od siebie niezależnych i nie ma potrzeby korzystania z prywatnych metod lub klas pakietu. Refaktoryzacja ich do własnych podpakietów niewiele zyskuje, ale pomaga zmniejszyć bałagan.

Developer zmiany w systemie

Kiedy programiści mają wprowadzić zmiany, które są nieco bardziej niż trywialne, wydaje się głupie, że potencjalnie mają zmiany, które obejmują pliki ze wszystkich obszarów drzewa pakietów. Przy logicznym podejściu strukturalnym ich zmiany są bardziej lokalne w tej samej części drzewa pakietów, co wydaje się właściwe.

poseł.
źródło
„Gdy deweloperzy [...] pliki ze wszystkich obszarów drzewa pakietów”. Masz rację, mówiąc, że wydaje się to głupie. Ponieważ dokładnie na tym polega właściwe ułożenie warstw: zmiany nie wpływają na całą strukturę aplikacji.
eljenso
Dzięki za radę. Nie jestem pewien, czy dobrze cię rozumiem. Wydaje mi się, że próbujesz argumentować za pakietami logicznymi, nie jestem do końca jasny dlaczego. Czy mógłbyś spróbować uczynić swoją odpowiedź trochę jaśniejszą, prawdopodobnie przeredagowując? Dzięki!
Tim Visher,
3

Swoje miejsce mają zarówno funkcjonalne (architektoniczne), jak i logiczne (cechowe) podejście do pakowania. Wiele przykładowych aplikacji (tych, które można znaleźć w podręcznikach itp.) Opiera się na funkcjonalnym podejściu polegającym na umieszczaniu prezentacji, usług biznesowych, mapowania danych i innych warstw architektonicznych w oddzielnych pakietach. W przykładowych aplikacjach każdy pakiet ma często tylko kilka lub tylko jedną klasę.

To początkowe podejście jest w porządku, ponieważ wymyślony przykład często służy do: 1) koncepcyjnego odwzorowania architektury prezentowanej struktury, 2) odbywa się to w jednym logicznym celu (np. Dodawanie / usuwanie / aktualizowanie / usuwanie zwierząt domowych z kliniki) . Problem w tym, że wielu czytelników traktuje to jako standard bez granic.

Ponieważ aplikacja „biznesowa” rozszerza się i obejmuje coraz więcej funkcji, stosowanie podejścia funkcjonalnego staje się ciężarem. Chociaż wiem, gdzie szukać typów opartych na warstwie architektury (np. Kontrolery sieciowe w pakiecie "web" lub "ui", itp.), Opracowanie pojedynczej funkcji logicznej zaczyna wymagać przeskakiwania tam i z powrotem między wieloma pakietami. Jest to co najmniej uciążliwe, ale jest gorsze niż to.

Ponieważ logicznie powiązane typy nie są pakowane razem, interfejs API jest zbyt rozpowszechniony; interakcja między logicznie powiązanymi typami musi być „publiczna”, aby typy mogły importować i współdziałać ze sobą (możliwość minimalizacji do domyślnej / widoczności pakietu zostaje utracona).

Jeśli tworzę bibliotekę frameworkową, moje pakiety będą zgodne z funkcjonalnym / architektonicznym podejściem do pakowania. Moi konsumenci API mogą nawet docenić fakt, że ich instrukcje importowania zawierają intuicyjny pakiet nazwany na podstawie architektury.

I odwrotnie, podczas tworzenia aplikacji biznesowej będę pakować według funkcji. Nie mam problemu z umieszczeniem Widget, WidgetService i WidgetController w tym samym pakieciecom.myorg.widget. ”, A następnie skorzystaniem z domyślnej widoczności (i mniejszej liczby instrukcji importu oraz zależności między pakietami).

Istnieją jednak przypadki krzyżowe. Jeśli moja usługa WidgetService jest używana przez wiele domen logicznych (funkcji), mogę utworzyć pakiet „ com.myorg.common.service. ”. Istnieje również duża szansa, że ​​utworzę klasy z zamiarem ponownego wykorzystania w różnych funkcjach i ostatecznie otrzymam takie pakiety, jak „ com.myorg.common.ui.helpers. ” I „ com.myorg.common.util. ”. Mogę nawet skończyć przenosząc wszystkie późniejsze „wspólne” klasy do osobnego projektu i dołączając je do mojej aplikacji biznesowej jako zależność myorg-commons.jar.

i3ensays
źródło
2

To zależy od stopnia szczegółowości Twoich procesów logicznych?

Jeśli są one autonomiczne, często masz dla nich nowy projekt w kontroli źródła, a nie nowy pakiet.

Projekt, nad którym obecnie pracuję, zmierza w kierunku logicznego podziału, jest pakiet dla aspektu jython, pakiet dla silnika reguł, pakiety dla foo, bar, binglewozzle, itp. Szukam parserów specyficznych dla XML / writers dla każdego modułu w tym pakiecie, zamiast mieć pakiet XML (co zrobiłem wcześniej), chociaż nadal będzie istniał podstawowy pakiet XML, do którego idzie współdzielona logika. Jednym z powodów tego jest jednak to, że może być rozszerzalny (wtyczki), a zatem każda wtyczka będzie musiała również zdefiniować swój kod XML (lub bazy danych itp.), Więc centralizacja może spowodować później problemy.

Ostatecznie wydaje się, że jest to najbardziej sensowne dla danego projektu. Myślę jednak, że łatwo jest spakować zgodnie z liniami typowego diagramu warstwowego projektu. Otrzymasz połączenie logicznego i funkcjonalnego opakowania.

Potrzebne są otagowane przestrzenie nazw. Parser XML dla niektórych funkcji Jython może być oznaczony zarówno jako Jython, jak i XML, zamiast konieczności wybierania jednego lub drugiego.

A może wiję się.

JeeBee
źródło
Nie do końca rozumiem twój punkt widzenia. Myślę, że mówisz, że powinieneś po prostu zrobić to, co jest najbardziej sensowne dla twojego projektu i dla ciebie jest to logiczne, z dorzuceniem odrobiny funkcjonalności. Czy są ku temu konkretne praktyczne powody?
Tim Visher
Jak powiedziałem, zamierzam mieć wtyczki zwiększające wbudowaną funkcjonalność. Wtyczka będzie musiała być w stanie analizować i zapisywać swój kod XML (i ostatecznie do bazy danych), a także zapewniać funkcjonalność. Dlatego mam com.xx.feature.xml lub com.xx.xml.feature? Ten pierwszy wydaje się schludniejszy.
JeeBee
2

Staram się projektować struktury pakietów w taki sposób, że gdybym miał narysować wykres zależności, byłby łatwy do naśladowania i używania spójnego wzorca, z jak najmniejszą liczbą odwołań cyklicznych.

Dla mnie jest to znacznie łatwiejsze do utrzymania i wizualizacji w systemie nazewnictwa pionowego niż poziomego. jeśli component1.display ma odniesienie do component2.dataaccess, powoduje to wyświetlenie większej liczby sygnałów ostrzegawczych, niż gdyby display.component1 miał odniesienie do dataaccess. komponent2.

Oczywiście komponenty wspólne dla obu są umieszczane we własnym pakiecie.

Levand
źródło
Jak wtedy rozumiem, byłbyś zwolennikiem nazewnictwa pionowego. Myślę, że do tego właśnie służy pakiet * .utils, gdy potrzebujesz klasy obejmującej wiele wycinków.
Tim Visher
2

Całkowicie się zgadzam i proponuję logiczną („według funkcji”) organizację! Pakiet powinien być możliwie jak najbardziej zgodny z koncepcją „modułu”. Organizacja funkcjonalna może rozłożyć moduł na projekt, co skutkuje mniejszą hermetyzacją i podatnością na zmiany w szczegółach implementacji.

Weźmy na przykład wtyczkę Eclipse: umieszczenie wszystkich widoków lub akcji w jednym pakiecie byłoby bałaganem. Zamiast tego każdy składnik funkcji powinien trafiać do pakietu funkcji lub, jeśli jest ich wiele, do podpakietów (obsługi funkcji A., preferencji funkcji itp.)

Oczywiście problem tkwi w hierarchicznym systemie pakietów (który posiada m.in. Java), co sprawia, że ​​obsługa ortogonalnych problemów jest niemożliwa lub co najmniej bardzo trudna - choć występują one wszędzie!

thSoft
źródło
1

Osobiście wybrałbym nazewnictwo funkcjonalne. Krótki powód: unika powielania kodu lub koszmaru zależności.

Pozwól mi trochę rozwinąć. Co się dzieje, gdy używasz zewnętrznego pliku jar z własnym drzewem pakietów? Efektywnie importujesz (skompilowany) kod do swojego projektu, a wraz z nim (oddzielone funkcjonalnie) drzewo pakietów. Czy miałoby sens jednoczesne użycie obu konwencji nazewnictwa? Nie, chyba że to było przed tobą ukryte. I tak jest, jeśli twój projekt jest wystarczająco mały i składa się z jednego komponentu. Ale jeśli masz kilka jednostek logicznych, prawdopodobnie nie chcesz ponownie implementować, powiedzmy, modułu ładowania pliku danych. Chcesz dzielić go między jednostkami logicznymi, nie mieć sztucznych zależności między jednostkami niepowiązanymi logicznie i nie musisz wybierać, do której jednostki chcesz wstawić to konkretne narzędzie współdzielone.

Myślę, że właśnie dlatego nazewnictwo funkcjonalne jest najczęściej używane w projektach, które osiągają lub mają osiągnąć określony rozmiar, a nazewnictwo logiczne jest używane w konwencjach nazewnictwa klas w celu śledzenia określonej roli, jeśli którakolwiek z klas w pakiet.

Postaram się dokładniej odpowiedzieć na każdy z Twoich punktów dotyczących nazewnictwa logicznego.

  1. Jeśli musisz łowić ryby w starych klasach, aby zmodyfikować funkcjonalności, gdy zmieniasz plany, jest to oznaka złej abstrakcji: powinieneś budować klasy, które zapewniają dobrze zdefiniowaną funkcjonalność, definiowalną jednym krótkim zdaniem. Tylko kilka klas najwyższego poziomu powinno zebrać wszystkie te elementy, aby odzwierciedlić analizę biznesową. W ten sposób będziesz mógł ponownie wykorzystać więcej kodu, mieć łatwiejszą konserwację, bardziej przejrzystą dokumentację i mniej problemów z zależnościami.

  2. Zależy to głównie od sposobu, w jaki realizujesz swój projekt. Zdecydowanie widok logiczny i funkcjonalny jest ortogonalny. Więc jeśli używasz jednej konwencji nazewnictwa, musisz zastosować drugą do nazw klas, aby zachować pewien porządek, lub rozwidlić na jakiejś głębokości od jednej konwencji nazewnictwa do drugiej.

  3. Modyfikatory dostępu to dobry sposób, aby umożliwić innym klasom, które rozumieją Twoje przetwarzanie, dostęp do wewnętrznych elementów Twojej klasy. Relacja logiczna nie oznacza zrozumienia ograniczeń algorytmicznych lub współbieżności. Funkcjonalne może, chociaż nie. Jestem bardzo zmęczony modyfikatorami dostępu innymi niż publiczne i prywatne, ponieważ często ukrywają one brak odpowiedniej architektury i abstrakcji klas.

  4. W dużych, komercyjnych projektach zmiany technologii zdarzają się częściej, niż by się wydawało. Na przykład musiałem już 3 razy zmienić parser XML, 2 razy technologię buforowania i 2 razy oprogramowanie do geolokalizacji. Dobrze, że ukryłem wszystkie drobne szczegóły w dedykowanym opakowaniu ...

Varkhan
źródło
1
Wybacz mi, jeśli się mylę, ale wydaje mi się, że bardziej mówisz o tworzeniu frameworka, który ma być używany przez wiele osób. Myślę, że zasady zmieniają się między tego typu programowaniem a tworzeniem systemów dla użytkowników końcowych. Czy się mylę?
Tim Visher
Myślę, że w przypadku typowych klas używanych przez wiele pionowych plasterków sensowne jest posiadanie czegoś w rodzaju utilspakietu. Wtedy wszystkie klasy, które tego potrzebują, mogą po prostu zależeć od tego pakietu.
Tim Visher,
Po raz kolejny wielkość ma znaczenie: kiedy projekt staje się dostatecznie duży, musi być wspierany przez kilka osób i nastąpi jakaś specjalizacja. Aby ułatwić interakcję i zapobiec błędom, szybko konieczne jest podzielenie projektu na części. A pakiet narzędzi wkrótce stanie się gigantyczny!
Varkhan,
1

Interesującym eksperymentem jest całkowite nieużywanie pakietów (z wyjątkiem pakietu głównego).

Powstaje wówczas pytanie, kiedy i dlaczego warto wprowadzać pakiety. Można przypuszczać, że odpowiedź będzie inna niż ta, na którą odpowiedziałbyś na początku projektu.

Zakładam, że w ogóle pojawia się twoje pytanie, ponieważ pakiety są jak kategorie i czasami trudno jest zdecydować się na jedną lub drugą. Czasami tagi byłyby bardziej przydatne, aby przekazać, że klasa jest użyteczna w wielu kontekstach.

user53682
źródło
0

Z czysto praktycznego punktu widzenia konstrukcje widoczności języka Java umożliwiają klasom w tym samym pakiecie dostęp do metod i właściwości za pomocą protectedi defaultwidoczności, a także publictych. Używanie niepublicznych metod z zupełnie innej warstwy kodu z pewnością oznaczałoby duży zapach kodu. Dlatego staram się umieszczać klasy z tej samej warstwy w tym samym pakiecie.

Rzadko używam tych chronionych lub domyślnych metod gdzie indziej - z wyjątkiem być może w testach jednostkowych dla klasy - ale kiedy to robię, zawsze jest to z klasy w tej samej warstwie

Bill Michell
źródło
Czy to nie jest coś z natury systemów wielowarstwowych, aby zawsze być zależnym od następnej warstwy w dół? Na przykład interfejs użytkownika zależy od warstwy usług, która zależy od warstwy domeny itp. Pakowanie pionowych plasterków razem wydaje się chronić przed nadmiernymi zależnościami między pakietami, prawda?
Tim Visher
Prawie nigdy nie używam metod chronionych i domyślnych. 99% jest publicznych lub prywatnych. Kilka wyjątków: (1) domyślna widoczność metod używanych tylko przez testy jednostkowe, (2) chroniona metoda abstrakcyjna, która jest używana tylko z abstrakcyjnej klasy bazowej.
Esko Luontola
1
Tim Visher, zależności między pakietami nie stanowią problemu, o ile zależności zawsze wskazują ten sam kierunek i nie ma cykli na wykresie zależności.
Esko Luontola
0

To zależy. W mojej pracy czasami dzielimy pakiety według funkcji (dostęp do danych, analityka) lub według klas aktywów (kredyt, akcje, stopy procentowe). Po prostu wybierz strukturę, która jest najwygodniejsza dla Twojego zespołu.

quant_dev
źródło
Czy jest jakiś powód, żeby iść w obie strony?
Tim Visher
Dzielenie według klas aktywów (bardziej ogólnie: według domeny biznesowej) ułatwia nowym osobom odnalezienie się w kodzie. Dzielenie według funkcji jest dobre do hermetyzacji. Dla mnie dostęp do „pakietu” w Javie jest podobny do „przyjaciela” w C ++.
quant_dev
@quant_dev Nie zgadzam się z twierdzeniem „.. by funkcja jest dobra do hermetyzacji”. Myślę, że masz na myśli zupełne przeciwieństwo. Nie wybieraj też tylko „wygody”. Dla każdego jest przypadek (zobacz moją odpowiedź).
i3ensays
-3

Z mojego doświadczenia wynika, że ​​ponowne użycie stwarza więcej problemów niż ich rozwiązywanie. W przypadku najnowszych i tanich procesorów i pamięci wolałbym raczej kopiować kod niż ściśle integrować go w celu ponownego wykorzystania.

Raghu Kasturi
źródło
5
Ponowne wykorzystanie kodu nie dotyczy ceny sprzętu, ale kosztów jego utrzymania.
user2793390
1
Zgadzam się, ale naprawianie czegoś w prostym module nie powinno mieć wpływu na cały kod. O jakich kosztach utrzymania mówisz?
Raghu Kasturi,
1
Poprawka błędu w module powinna wpłynąć na całą aplikację. Dlaczego chcesz wielokrotnie naprawiać ten sam błąd?
user2793390