Przez lata używania C # / .NET do wielu wewnętrznych projektów, jedna biblioteka rozwinęła się organicznie w jeden wielki zbiór rzeczy. Nazywa się to „Util” i jestem pewien, że wielu z was widziało jedną z tych bestii w swojej karierze.
Wiele części tej biblioteki jest bardzo samodzielnych i można je podzielić na osobne projekty (które chcielibyśmy otworzyć). Jest jednak jeden poważny problem, który należy rozwiązać, zanim zostaną one wydane jako osobne biblioteki. Zasadniczo istnieje wiele przypadków tego, co mogę nazwać „opcjonalnymi zależnościami” między tymi bibliotekami.
Aby to lepiej wyjaśnić, rozważ niektóre moduły, które są dobrymi kandydatami do zostania samodzielnymi bibliotekami. CommandLineParser
służy do analizowania wierszy poleceń. XmlClassify
służy do szeregowania klas do formatu XML. PostBuildCheck
wykonuje sprawdzanie skompilowanego zestawu i zgłasza błąd kompilacji, jeśli się nie powiedzie. ConsoleColoredString
to biblioteka kolorowych literałów łańcuchowych. Lingo
służy do tłumaczenia interfejsów użytkownika.
Każda z tych bibliotek może być używana całkowicie autonomicznie, ale jeśli są one używane razem, istnieją przydatne dodatkowe funkcje. Na przykład, zarówno CommandLineParser
i XmlClassify
narazić funkcjonalność sprawdzania post-build, który wymaga PostBuildCheck
. Podobnie, CommandLineParser
pozwala na dostarczenie dokumentacji opcji przy użyciu kolorowych literałów łańcuchowych, co jest wymagane ConsoleColoredString
, i obsługuje dokumentację do przetłumaczenia przez Lingo
.
Dlatego kluczowym rozróżnieniem jest to, że są to funkcje opcjonalne . Można użyć parsera wiersza poleceń z prostymi, bezbarwnymi łańcuchami, bez tłumaczenia dokumentacji lub wykonywania jakichkolwiek kontroli po kompilacji. Lub można sprawić, by dokument był przetłumaczalny, ale nadal bezbarwny. Lub zarówno kolorowe, jak i do przetłumaczenia. Itp.
Przeglądając tę bibliotekę „Util”, widzę, że prawie wszystkie potencjalnie rozdzielne biblioteki mają takie opcjonalne funkcje, które wiążą je z innymi bibliotekami. Gdybym naprawdę potrzebował tych bibliotek jako zależności, to ten plik rzeczy nie jest wcale nieplątany: nadal potrzebujesz wszystkich bibliotek, jeśli chcesz użyć tylko jednej.
Czy istnieją ustalone podejścia do zarządzania takimi opcjonalnymi zależnościami w .NET?
źródło
Odpowiedzi:
Refaktoryzuj powoli.
Spodziewaj się, że wykonanie tego zajmie trochę czasu i może wystąpić w kilku iteracjach, zanim będzie można całkowicie usunąć zespół Utils .
Ogólne podejście:
Najpierw poświęć trochę czasu i zastanów się, jak chcesz, aby te zestawy narzędzi wyglądały po zakończeniu. Nie przejmuj się zbytnio istniejącym kodem, pomyśl o celu końcowym. Na przykład możesz chcieć mieć:
Utwórz puste projekty dla każdego z tych projektów i utwórz odpowiednie odwołania do projektu (odniesienia do interfejsu Core, UI.WinForms odniesienia do interfejsu użytkownika itp.)
Przenieś dowolny nisko wiszący owoc (klasy lub metody, które nie mają problemów z zależnościami) z zestawu Utils do nowych zespołów docelowych.
Zdobądź kopię NDepend i Martina Fowlera Refactoring, aby rozpocząć analizę zestawu Utils i rozpocząć pracę na trudniejszych. Dwie techniki, które będą pomocne:
Obsługa opcjonalnych interfejsów
Albo zespół odwołuje się do innego zestawu, albo nie. Jedynym innym sposobem użycia funkcji w niepowiązanym zestawie jest interfejs załadowany przez odbicie od wspólnej klasy. Wadą tego jest to, że zestaw podstawowy będzie musiał zawierać interfejsy dla wszystkich współużytkowanych funkcji, ale wadą jest to, że możesz wdrożyć narzędzia w razie potrzeby bez „wad” plików DLL w zależności od każdego scenariusza wdrażania. Oto jak poradziłbym sobie z tą sprawą, używając kolorowego sznurka jako przykładu:
Najpierw zdefiniuj wspólne interfejsy w zestawie podstawowym:
Na przykład
IStringColorer
interfejs wyglądałby następująco:Następnie zaimplementuj interfejs w zespole z funkcją. Na przykład
StringColorer
klasa wyglądałaby następująco:Utwórz
PluginFinder
(lub może w tym przypadku lepszą nazwę interfejsu), która może znaleźć interfejsy z plików DLL w bieżącym folderze. Oto uproszczony przykład. Zgodnie z radą Per @ EdWoodcock (i zgadzam się), gdy Twoje projekty będą rosły, sugerowałbym użycie jednego z dostępnych frameworków Dependency Injection ( przychodzi mi na myśl Common Serivce Locator z Unity i Spring.NET ) dla bardziej niezawodnej implementacji z bardziej zaawansowanym „znajdź mnie tej funkcji ”, znanej również jako wzorzec lokalizatora usług . Możesz go zmodyfikować zgodnie z własnymi potrzebami.Na koniec użyj tych interfejsów w innych zestawach, wywołując metodę FindInterface. Oto przykład
CommandLineParser
:}
NAJWAŻNIEJSZE: Testuj, testuj, testuj między każdą zmianą.
źródło
Możesz skorzystać z interfejsów zadeklarowanych w dodatkowej bibliotece.
Spróbuj rozwiązać kontrakt (klasa przez interfejs) przy użyciu wstrzykiwania zależności (MEF, Unity itp.). Jeśli nie zostanie znaleziony, ustaw go tak, aby zwracał instancję zerową.
Następnie sprawdź, czy instancja ma wartość NULL, w którym to przypadku nie wykonujesz dodatkowych funkcji.
Jest to szczególnie łatwe w MEF, ponieważ jest to podręcznik do tego celu.
Umożliwiłoby to skompilowanie bibliotek kosztem podziału ich na n + 1 bibliotek dll.
HTH.
źródło
Myślałem, że opublikuję najbardziej opłacalną opcję, jaką do tej pory wymyśliliśmy, aby zobaczyć, jakie są myśli.
Zasadniczo oddzielilibyśmy każdy komponent do biblioteki z zerowymi referencjami; cały kod, który wymaga odwołania, zostanie umieszczony w
#if/#endif
bloku o odpowiedniej nazwie. Na przykład kod wCommandLineParser
tych uchwytachConsoleColoredString
s zostałby umieszczony w#if HAS_CONSOLE_COLORED_STRING
.Każde rozwiązanie, które chce zawierać tylko to, co
CommandLineParser
można łatwo zrobić, ponieważ nie ma dalszych zależności. Jeśli jednak rozwiązanie obejmuje równieżConsoleColoredString
projekt, programista ma teraz opcję:CommandLineParser
doConsoleColoredString
HAS_CONSOLE_COLORED_STRING
definicję doCommandLineParser
pliku projektu.Dzięki temu dostępna będzie odpowiednia funkcjonalność.
Jest z tym kilka problemów:
Raczej nie ładny, ale wciąż jest to najbliższy wymysł.
Kolejnym pomysłem, który rozważaliśmy, było użycie konfiguracji projektu zamiast wymagania od użytkownika edycji pliku projektu biblioteki. Jest to jednak absolutnie niewykonalne w VS2010, ponieważ niepotrzebnie dodaje wszystkie konfiguracje projektu do rozwiązania .
źródło
Mam zamiar polecić książkę Brownfield Application Development w .Net . Dwa bezpośrednio powiązane rozdziały to 8 i 9. Rozdział 8 mówi o przekazywaniu aplikacji, podczas gdy rozdział 9 mówi o oswajaniu zależności, odwróceniu kontroli i wpływie, jaki ma to na testowanie.
źródło
Pełne ujawnienie, jestem facetem z Javy. Rozumiem więc, że prawdopodobnie nie szukasz technologii, o których tu wspomnę. Ale problemy są takie same, więc być może wskaże ci właściwy kierunek.
W Javie istnieje wiele systemów kompilacji, które wspierają ideę scentralizowanego repozytorium artefaktów, w którym mieszczą się zbudowane „artefakty” - według mojej wiedzy jest to nieco analogiczne do GAC w .NET (proszę zignorować moją ignorancję, jeśli jest to napięta anaologia) ale więcej, ponieważ jest on używany do tworzenia niezależnych powtarzalnych kompilacji w dowolnym momencie.
W każdym razie inną obsługiwaną funkcją (na przykład w Maven) jest koncepcja OPCJONALNEJ zależności, a następnie zależna od konkretnych wersji lub zakresów i potencjalnie wykluczająca zależności przechodnie. Brzmi dla mnie jak to, czego szukasz, ale mogę się mylić. Spójrz na tę stronę wprowadzającą dotyczącą zarządzania zależnościami od Maven ze znajomym, który zna Javę, i sprawdź, czy problemy wydają się znajome. Pozwoli ci to zbudować aplikację i zbudować ją z lub bez dostępności tych zależności.
Istnieją również konstrukcje, jeśli potrzebujesz naprawdę dynamicznej, wtykowej architektury; jedną z technologii, która próbuje rozwiązać tę formę rozwiązywania zależności w czasie wykonywania, jest OSGI. To jest silnik systemu wtyczek Eclipse . Zobaczysz, że może obsługiwać opcjonalne zależności oraz minimalny / maksymalny zakres wersji. Ten poziom modułowości środowiska wykonawczego nakłada na ciebie wiele ograniczeń i sposobu rozwoju. Większość ludzi może sobie poradzić ze stopniem modułowości zapewnianym przez Maven.
Innym możliwym pomysłem, który można by rozważyć, może być rzędów wielkości prostszych do wdrożenia dla ciebie, jest użycie architektury w stylu rur i filtrów. To w dużej mierze sprawiło, że UNIX stał się tak dobrze prosperującym ekosystemem, który przetrwał i ewoluował przez pół wieku. Zapoznaj się z tym artykułem na temat rur i filtrów w .NET, aby uzyskać kilka pomysłów na temat implementacji tego rodzaju wzorca w swoim środowisku.
źródło
Być może książka „Wielkoskalowe projektowanie oprogramowania C ++” Johna Lakosa jest przydatna (oczywiście C # i C ++ lub nie to samo, ale można wyodrębnić przydatne techniki z książki).
Zasadniczo, ponownie uwzględnij czynnik i przenieś funkcjonalność korzystającą z dwóch lub więcej bibliotek do osobnego komponentu zależnego od tych bibliotek. W razie potrzeby użyj technik takich jak typy nieprzezroczyste itp.
źródło