Jestem całkiem nowy w zasadach projektowania SOLID . Rozumiem ich przyczynę i korzyści, ale jednak nie stosuję ich do mniejszego projektu, który chciałbym zreformować jako praktyczne ćwiczenie z wykorzystaniem zasad SOLID. Wiem, że nie ma potrzeby zmiany aplikacji, która działa idealnie, ale i tak chcę ją zrefaktoryzować, aby zyskać doświadczenie projektowe dla przyszłych projektów.
Aplikacja ma następujące zadanie (właściwie o wiele więcej, ale bądźmy prostsze): musi odczytać plik XML zawierający definicje tabeli bazy danych / kolumny / widoku itp. I utworzyć plik SQL, którego można użyć do utworzenia schemat bazy danych ORACLE.
(Uwaga: powstrzymaj się od dyskusji, dlaczego go potrzebuję lub dlaczego nie używam XSLT i tak dalej, istnieją powody, ale są one nie na temat).
Na początek postanowiłem spojrzeć tylko na tabele i ograniczenia. Jeśli zignorujesz kolumny, możesz podać je w następujący sposób:
Ograniczenie jest częścią tabeli (a ściślej częścią instrukcji CREATE TABLE), a ograniczenie może również odnosić się do innej tabeli.
Najpierw wyjaśnię, jak teraz wygląda aplikacja (nie stosując SOLID):
W tej chwili aplikacja ma klasę „Tabela”, która zawiera listę wskaźników do ograniczeń należących do tabeli oraz listę wskaźników do ograniczeń odnoszących się do tej tabeli. Za każdym razem, gdy zostanie ustanowione połączenie, zostanie również ustanowione połączenie wsteczne. Tabela ma metodę createStatement (), która z kolei wywołuje funkcję createStatement () każdego ograniczenia. Wspomniana metoda sama użyje połączeń z tabelą właściciela i tabelą odniesienia, aby pobrać ich nazwy.
Oczywiście nie dotyczy to w ogóle SOLID. Na przykład istnieją zależności cykliczne, które rozprężają kod pod względem wymaganych metod „dodawania” / „usuwania” i niektórych destrukterów dużych obiektów.
Jest więc kilka pytań:
- Czy powinienem rozwiązać zależności cykliczne za pomocą wstrzykiwania zależności? Jeśli tak, to przypuszczam, że Ograniczenie powinno otrzymać tabelę właściciela (i opcjonalnie odnośnik) w swoim konstruktorze. Ale jak w takim razie mógłbym przeglądać listę ograniczeń dla pojedynczej tabeli?
- Jeśli zarówno klasa Table przechowuje stan samego siebie (np. Nazwę tabeli, komentarz do tabeli itp.), Jak i linki do Ograniczeń, to czy te jedno lub dwa „obowiązki” mają na myśli zasadę pojedynczej odpowiedzialności?
- W przypadku, gdy 2. ma rację, czy powinienem po prostu utworzyć nową klasę w logicznej warstwie biznesowej, która zarządza łączami? Jeśli tak, 1. oczywiście nie byłoby już istotne.
- Czy metody „createStatement” powinny być częścią klas Table / Constraint, czy też mam je przenieść? Jeśli tak to gdzie? Jedna klasa menedżera na każdą klasę przechowywania danych (tj. Tabela, ograniczenie, ...)? Czy raczej utworzyć klasę menedżera dla linku (podobną do 3.)?
Ilekroć próbuję odpowiedzieć na jedno z tych pytań, znajduję się gdzieś w kółko.
Problem oczywiście staje się o wiele bardziej złożony, jeśli uwzględnisz kolumny, indeksy i tak dalej, ale jeśli pomożecie mi z prostą kwestią związaną z tabelą / ograniczeniami, może resztę samodzielnie opracuję.
źródło
Odpowiedzi:
Możesz zacząć od innego punktu widzenia, aby zastosować tutaj „zasadę pojedynczej odpowiedzialności”. To, co nam pokazałeś, to (mniej więcej) tylko model danych Twojej aplikacji. SRP oznacza tutaj: upewnij się, że twój model danych jest odpowiedzialny tylko za przechowywanie danych - nie mniej, nie więcej.
Więc jeśli masz zamiar czytać plik XML, stworzyć model z niego dane i pisać SQL, co powinno nie wystarczy wdrożyć coś do swojej
Table
klasy, która jest specyficzna XML lub SQL. Chcesz, aby przepływ danych wyglądał tak:Zatem jedynym miejscem, w którym należy umieścić kod specyficzny dla XML, jest klasa o nazwie na przykład
Read_XML
. Jedynym miejscem dla kodu specyficznego dla SQL powinna być klasaWrite_SQL
. Oczywiście, być może zamierzasz podzielić te 2 zadania na więcej pod-zadań (i podzielić swoje klasy na wiele klas menedżerów), ale twój „model danych” nie powinien ponosić żadnej odpowiedzialności z tej warstwy. Więc nie dodawaj acreateStatement
do żadnej z klas modelu danych, ponieważ daje to twojemu modelowi odpowiedzialność za SQL.Nie widzę żadnego problemu, kiedy opisujesz, że Tabela jest odpowiedzialna za przechowywanie wszystkich jej części (nazwa, kolumny, komentarze, ograniczenia ...), taka jest idea modelu danych. Ale opisałeś, że „Tabela” jest również odpowiedzialna za zarządzanie pamięcią niektórych jej części. Jest to problem specyficzny dla C ++, z którym nie spotkałbyś się tak łatwo w językach takich jak Java lub C #. Sposobem C ++ na pozbycie się tej odpowiedzialności jest użycie inteligentnych wskaźników, przekazanie własności na inną warstwę (na przykład bibliotekę boost lub na własną „inteligentną” warstwę wskaźnika). Ale uwaga, twoje cykliczne zależności mogą „drażnić” niektóre inteligentne implementacje wskaźników.
Coś więcej o SOLID: oto fajny artykuł
http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game
wyjaśniając SOLID na małym przykładzie. Spróbujmy zastosować to w twoim przypadku:
trzeba nie tylko klasy
Read_XML
iWrite_SQL
, ale także trzecią klasę, która zarządza oddziaływanie tych 2 klas. Nazwijmy toConversionManager
.Stosując zasadę DI może oznaczać tutaj: ConversionManager nie powinny tworzyć instancje
Read_XML
iWrite_SQL
przez siebie. Zamiast tego obiekty te można wstrzykiwać przez konstruktor. Konstruktor powinien mieć taki podpisConversionManager(IDataModelReader reader, IDataModelWriter writer)
gdzie
IDataModelReader
jest interfejsem, z któregoRead_XML
dziedziczy, iIDataModelWriter
to samo dlaWrite_SQL
. To sprawia, że sąConversionManager
otwarte na rozszerzenia (bardzo łatwo udostępniasz różnym czytelnikom lub pisarzom) bez konieczności ich zmiany - więc mamy przykład zasady otwartej / zamkniętej. Pomyśl o tym, co będziesz musiał zmienić, jeśli chcesz wesprzeć innego dostawcę bazy danych - w zasadzie nie musisz niczego zmieniać w swoim modelu danych, po prostu podaj innego SQL-Writera.źródło
W takim przypadku powinieneś zastosować S SOLID.
Tabela zawiera wszystkie zdefiniowane ograniczenia. Ograniczenie zawiera wszystkie tabele, do których się odwołuje. Prosty i prosty model.
W tym tkwi zdolność do wykonywania odwrotnych wyszukiwań, tj. Do ustalenia, z jakimi ograniczeniami odnosi się do niektórych tabel.
Więc tak naprawdę chcesz usługi indeksowania. To jest zupełnie inne zadanie i dlatego powinno być realizowane przez inny przedmiot.
Aby rozbić go na bardzo uproszczoną wersję:
Jeśli chodzi o implementację indeksu, istnieją 3 sposoby:
getContraintsReferencing
metoda może naprawdę tylko pełzać całaDatabase
dlaTable
instancji i indeksowanie ichConstraint
s, aby uzyskać wynik. W zależności od tego, jak to jest kosztowne i jak często go potrzebujesz, może to być opcja.Table
iConstraint
instancji, gdy ulegną zmianie. Nieco prostszym rozwiązaniem byłobyIndex
zbudowanie „indeksu migawek” całościDatabase
do pracy, który następnie należy odrzucić. Jest to oczywiście możliwe tylko wtedy, gdy aplikacja robi duże rozróżnienie między „czasem modelowania” a „czasem zapytania”. Jeśli raczej możliwe jest wykonanie tych dwóch jednocześnie, nie jest to wykonalne.źródło
Lekarstwem na okrągłe zależności jest ślubowanie, że nigdy, nigdy ich nie stworzysz. Uważam, że testowanie kodu jako pierwszego jest silnym czynnikiem odstraszającym.
W każdym razie zależności okrągłe można zawsze zerwać, wprowadzając abstrakcyjną klasę podstawową. Jest to typowe dla reprezentacji graficznych. Tutaj tabele są węzłami, a ograniczenia klucza obcego są krawędziami. Utwórz więc abstrakcyjną klasę tabeli i abstrakcyjną klasę ograniczeń, a może abstrakcyjną klasę kolumny. Wtedy wszystkie implementacje mogą zależeć od klas abstrakcyjnych. To może nie być najlepsza możliwa reprezentacja, ale jest to poprawa w stosunku do klas wzajemnie powiązanych.
Ale, jak podejrzewasz, najlepsze rozwiązanie tego problemu może nie wymagać śledzenia związków między obiektami. Jeśli chcesz tylko przetłumaczyć XML na SQL, nie potrzebujesz reprezentacji wykresu ograniczeń w pamięci. Wykres ograniczeń byłby miły, gdybyś chciał uruchomić algorytmy grafowe, ale nie wspomniałeś o tym, więc założę, że nie jest to wymagane. Potrzebujesz tylko listy tabel i listy ograniczeń oraz gościa dla każdego dialektu SQL, który chcesz obsługiwać. Wygeneruj tabele, a następnie wygeneruj ograniczenia zewnętrzne względem tabel. Dopóki wymagania się nie zmienią, nie będę miał problemu z podłączeniem generatora SQL do DOM XML. Zaoszczędź jutro na jutro.
źródło