Mam aplikację internetową. Nie wierzę, że technologia jest ważna. Struktura jest aplikacją na poziomie N, pokazaną na obrazku po lewej stronie. Istnieją 3 warstwy.
Interfejs użytkownika (wzorzec MVC), warstwa logiki biznesowej (BLL) i warstwa dostępu do danych (DAL)
Mam problem z tym, że moja BLL jest ogromna, ponieważ ma logikę i ścieżki w wywołaniu zdarzeń aplikacji.
Typowy przepływ przez aplikację może być:
Zdarzenie uruchomione w interfejsie użytkownika, przejdź do metody w BLL, wykonaj logikę (być może w wielu częściach BLL), ostatecznie do DAL, z powrotem do BLL (gdzie prawdopodobnie więcej logiki), a następnie zwróć pewną wartość do interfejsu użytkownika.
BLL w tym przykładzie jest bardzo zajęty i zastanawiam się, jak to rozdzielić. Mam też logikę i obiekty, które mi się nie podobają.
Wersja po prawej to mój wysiłek.
Logika jest nadal jak płynie wniosek między UI i DAL, ale są prawdopodobnie nie ma właściwości ... Tylko metody (większość klas w tej warstwie mogłoby ewentualnie być statyczny, ponieważ nie przechowuje żadnych stanu). Warstwa Poco to miejsce, w którym istnieją klasy, które mają właściwości (takie jak klasa Person, w której będzie imię, wiek, wzrost itp.). Nie miałyby one nic wspólnego z przepływem aplikacji, tylko przechowują stan.
Przepływ może być:
Nawet wyzwolony z interfejsu użytkownika i przekazuje niektóre dane do kontrolera warstwy interfejsu użytkownika (MVC). To tłumaczy nieprzetworzone dane i konwertuje je na model poco. Model poco jest następnie przekazywany do warstwy logicznej (która była BLL) i ostatecznie do warstwy zapytania, potencjalnie manipulowanej po drodze. Warstwa zapytania Command przekształca POCO w obiekt bazy danych (które są prawie takie same, ale jeden jest przeznaczony do trwałości, a drugi do interfejsu użytkownika). Element zostanie zapisany, a obiekt bazy danych zostanie zwrócony do warstwy zapytania. Następnie jest konwertowany na POCO, gdzie wraca do warstwy logicznej, potencjalnie przetwarzany dalej, a następnie z powrotem do interfejsu użytkownika
Wspólna logika i interfejsy to miejsce, w którym możemy mieć trwałe dane, takie jak MaxNumberOf_X i TotalAllowed_X i wszystkie interfejsy.
Zarówno wspólna logika / interfejsy, jak i DAL są „podstawą” architektury. Nic nie wiedzą o świecie zewnętrznym.
Wszystko wie o poco innych niż wspólna logika / interfejsy i DAL.
Przepływ jest nadal bardzo podobny do pierwszego przykładu, ale sprawia, że każda warstwa jest bardziej odpowiedzialna za 1 rzecz (czy to stan, przepływ, czy cokolwiek innego) ... ale czy łamie OOP przy takim podejściu?
Przykładem demonstracji Logiki i Poco może być:
public class LogicClass
{
private ICommandQueryObject cmdQuery;
public PocoA Method1(PocoB pocoB)
{
return cmdQuery.Save(pocoB);
}
/*This has no state objects, only ways to communicate with other
layers such as the cmdQuery. Everything else is just function
calls to allow flow via the program */
public PocoA Method2(PocoB pocoB)
{
pocoB.UpdateState("world");
return Method1(pocoB);
}
}
public struct PocoX
{
public string DataA {get;set;}
public int DataB {get;set;}
public int DataC {get;set;}
/*This simply returns something that is part of this class.
Everything is self-contained to this class. It doesn't call
trying to directly communicate with databases etc*/
public int GetValue()
{
return DataB * DataC;
}
/*This simply sets something that is part of this class.
Everything is self-contained to this class.
It doesn't call trying to directly communicate with databases etc*/
public void UpdateState(string input)
{
DataA += input;
}
}
źródło
Odpowiedzi:
Tak, najprawdopodobniej łamiesz podstawowe koncepcje OOP. Jednak nie czuj się źle, ludzie robią to cały czas, nie oznacza to, że twoja architektura jest „zła”. Powiedziałbym, że jest to prawdopodobnie mniej konserwowalne niż odpowiedni projekt OO, ale jest to raczej subiektywne i nie jest to twoje pytanie. ( Oto mój artykuł krytykujący ogólnie architekturę n-tier).
Rozumowanie : najbardziej podstawową koncepcją OOP jest to, że dane i logika tworzą jedną jednostkę (obiekt). Chociaż jest to bardzo uproszczone i mechaniczne stwierdzenie, mimo to tak naprawdę nie jest przestrzegane w twoim projekcie (jeśli dobrze cię rozumiem). Dość wyraźnie oddzielasz większość danych od większości logiki. Posiadanie metod bezstanowych (statycznych) na przykład nazywa się „procedurami” i ogólnie są one przeciwne do OOP.
Oczywiście zawsze są wyjątki, ale ten projekt z reguły narusza te rzeczy.
Ponownie chciałbym podkreślić, że „narusza OOP”! = „Źle”, więc niekoniecznie jest to ocena wartości. Wszystko zależy od ograniczeń architektury, przypadków użycia w zakresie konserwacji, wymagań itp.
źródło
Jedną z podstawowych zasad programowania funkcjonalnego są czyste funkcje.
Jedną z podstawowych zasad programowania obiektowego jest łączenie funkcji z danymi, na których działają.
Obie te podstawowe zasady znikają, gdy aplikacja musi komunikować się ze światem zewnętrznym. Rzeczywiście, możesz być wierny tym ideałom tylko w specjalnie przygotowanej przestrzeni w swoim systemie. Nie każda linia kodu musi spełniać te ideały. Ale jeśli żaden wiersz kodu nie spełnia tych ideałów, nie można tak naprawdę twierdzić, że używasz OOP lub FP.
Tak więc możesz mieć tylko „obiekty” danych, które rzucasz, ponieważ potrzebujesz, aby przekroczyły granicę, której po prostu nie można zmienić, aby przenieść zainteresowany kod. Po prostu wiedz, że to nie jest OOP. To jest rzeczywistość. OOP polega na tym, że gdy znajdziesz się w tej granicy, zgromadzisz całą logikę, która działa na te dane w jednym miejscu.
Nie musisz też tego robić. OOP to nie wszystko dla wszystkich ludzi. Jest jak jest. Po prostu nie twierdz, że coś dzieje się po OOP, gdy tak się nie stanie lub masz zamiar dezorientować ludzi próbujących utrzymać Twój kod.
Wygląda na to, że twoje POCO mają logikę biznesową, więc nie martwiłbym się zbytnio anemią. Martwi mnie to, że wszystkie wydają się bardzo zmienne. Pamiętaj, że metody pobierające i ustawiające nie zapewniają prawdziwej enkapsulacji. Jeśli twój POCO zmierza do tej granicy, to dobrze. Po prostu zrozum, że nie daje to pełnych korzyści z prawdziwego enkapsulowanego obiektu OOP. Niektórzy nazywają to Obiektem Transferu Danych lub DTO.
Sztuką, którą z powodzeniem zastosowałem, jest tworzenie obiektów OOP, które jedzą DTO. Używam DTO jako obiektu parametru . Mój konstruktor odczytuje z niego stan (odczytuje jako kopię obronną ) i odrzuca go na bok. Teraz mam w pełni zamkniętą i niezmienną wersję DTO. Wszystkie metody związane z tymi danymi można przenieść tutaj, pod warunkiem, że znajdują się po tej stronie granicy.
Nie dostarczam getterów ani seterów. Idę powiedzieć, nie pytaj . Wywołujecie moje metody i robią to, co trzeba. Prawdopodobnie nawet nie mówią ci, co zrobili. Po prostu to robią.
Teraz w końcu coś, gdzieś natrafi na inną granicę i to wszystko znów się rozpada. W porządku. Obróć kolejne DTO i przerzuć je przez ścianę.
Na tym właśnie polega architektura portów i adapterów. Czytałem o tym z funkcjonalnego punktu widzenia . Może cię też to zainteresuje.
źródło
Jeśli poprawnie odczytam twoje wyjaśnienie, twoje obiekty wyglądają trochę tak: (trudne bez kontekstu)
W tym, że twoje klasy Poco zawierają tylko dane, a twoje klasy logiki zawierają metody, które działają na tych danych; tak, złamałeś zasady „Classic OOP”
Ponownie trudno jest stwierdzić na podstawie twojego ogólnego opisu, ale zaryzykowałbym to, że to, co napisałeś, może być sklasyfikowane jako Anemiczny Model Domeny.
Nie sądzę, że jest to szczególnie złe podejście, ani, jeśli weźmiesz pod uwagę Poco jako struktury, nie jest to w żaden sposób nie do złamania OOP w bardziej konkretnym sensie. Dzięki temu Twoje obiekty są teraz LogicClasses. Rzeczywiście, jeśli sprawisz, że Twoje Pocos będą niezmienne, projekt można uznać za całkiem funkcjonalny.
Kiedy jednak odwołujesz się do Shared Logic, Pocos, które są prawie, ale nie takie same i statyki, zaczynam się martwić o szczegóły twojego projektu.
źródło
Jeden potencjalny problem, który widziałem w twoim projekcie (i jest bardzo powszechny) - niektóre z absolutnie najgorszych kodów „OO”, jakie kiedykolwiek spotkałem, były spowodowane przez architekturę, która oddzielała obiekty „Data” od obiektów „Code”. To koszmarne rzeczy! Problem polega na tym, że wszędzie w kodzie biznesowym, gdy chcesz uzyskać dostęp do swoich obiektów danych, masz tendencję do po prostu kodowania bezpośrednio tam (nie musisz, możesz zbudować klasę narzędziową lub inną funkcję do obsługi tego, ale to właśnie to Z biegiem czasu widziałem to wielokrotnie).
Kod dostępu / aktualizacji zazwyczaj nie jest gromadzony, więc wszędzie znajdziesz duplikaty.
Z drugiej strony te obiekty danych są przydatne, na przykład jako trwałość bazy danych. Wypróbowałem trzy rozwiązania:
Kopiowanie i wysyłanie wartości do „prawdziwych” obiektów i wyrzucanie obiektu danych jest żmudne (ale może być poprawnym rozwiązaniem, jeśli chcesz pójść tą drogą).
Dodanie metod przenoszenia danych do obiektów danych może działać, ale może sprawić, że duży, nieporządny obiekt danych robi więcej niż jedną rzecz. Może to również utrudnić enkapsulację, ponieważ wiele mechanizmów trwałości chce publicznych akcesorów ... Nie podobało mi się to, kiedy to zrobiłem, ale jest to prawidłowe rozwiązanie
Najlepszym rozwiązaniem dla mnie jest koncepcja klasy „Wrapper”, która zawiera klasę „Data” i zawiera wszystkie funkcje przekierowywania danych - wtedy nie ujawniam w ogóle klasy danych (nawet seterów i getterów chyba że są absolutnie potrzebne). Eliminuje to pokusę bezpośredniego manipulowania obiektem i zmusza do dodania współdzielonej funkcjonalności do opakowania.
Inną zaletą jest to, że możesz mieć pewność, że twoja klasa danych jest zawsze w poprawnym stanie. Oto szybki przykład kodu psuedocode:
Pamiętaj, że nie masz kontroli wieku w całym kodzie w różnych obszarach, a także nie masz ochoty jej używać, ponieważ nie możesz nawet dowiedzieć się, jakie są urodziny (chyba że potrzebujesz jej na coś innego, w w którym przypadku możesz go dodać).
Zwykle nie rozszerzam obiektu danych, ponieważ tracisz tę enkapsulację i gwarancję bezpieczeństwa - w tym momencie możesz po prostu dodać metody do klasy danych.
W ten sposób w Twojej logice biznesowej nie ma wielu śmieci / iteratorów dostępu do danych, staje się o wiele bardziej czytelny i mniej zbędny. Polecam również nawyk zawsze owijania kolekcji z tego samego powodu - utrzymywanie zapętlania / wyszukiwania konstrukcji poza logiką biznesową i upewnianie się, że zawsze są w dobrym stanie.
źródło
Nigdy nie zmieniaj kodu, ponieważ myślisz lub ktoś mówi ci, że to nie to lub nie to. Zmień kod, jeśli stwarza to problemy i wymyśliłeś sposób na uniknięcie tych problemów bez tworzenia innych.
Więc poza tym, że nie lubisz rzeczy, chcesz zainwestować dużo czasu, aby dokonać zmian. Zapisz problemy, które masz teraz. Napisz, jak Twój nowy projekt rozwiązałby problemy. Oblicz wartość ulepszenia i koszt wprowadzenia zmian. Następnie - i to jest najważniejsze - upewnij się, że masz czas na dokończenie tych zmian, w przeciwnym razie skończysz w połowie w tym stanie, w połowie w tym stanie, a to najgorsza możliwa sytuacja. (Kiedyś pracowałem nad projektem z 13 różnymi typami ciągów i trzema możliwymi do zidentyfikowania połowicznymi wysiłkami, aby ustandaryzować jeden typ)
źródło
Kategoria „OOP” jest znacznie większa i bardziej abstrakcyjna niż to, co opisujesz. Nie przejmuje się tym wszystkim. Dba o wyraźną odpowiedzialność, spójność, połączenie. Na poziomie, o który pytasz, nie ma sensu pytać o „praktykę OOPS”.
To powiedziawszy na przykład:
Wydaje mi się, że istnieje nieporozumienie na temat tego, co oznacza MVC. Nazywasz swój interfejs użytkownika „MVC”, niezależnie od logiki biznesowej i kontroli „zaplecza”. Ale dla mnie MVC obejmuje całą aplikację internetową:
Istnieją tutaj niezwykle ważne podstawowe założenia:
Co ważne: interfejs użytkownika jest częścią MVC. Nie na odwrót (jak na schemacie). Jeśli to zaakceptujesz, wtedy tłuste modele są naprawdę całkiem dobre - pod warunkiem, że rzeczywiście nie zawierają rzeczy, których nie powinny.
Zauważ, że „tłuste modele” oznaczają, że cała logika biznesowa należy do kategorii Model (pakiet, moduł, niezależnie od nazwy w wybranym języku). Poszczególne klasy powinny oczywiście mieć strukturę OOP w dobry sposób, zgodnie z wszelkimi wytycznymi dotyczącymi kodowania, które sam sobie podajesz (tj. Niektóre maksymalne wiersze kodu dla klasy lub metody itp.).
Należy również pamiętać, że sposób implementacji warstwy danych ma bardzo ważne konsekwencje; zwłaszcza, czy warstwa modelu jest w stanie funkcjonować bez warstwy danych (np. do testowania jednostek lub do tanich DB w pamięci na laptopie programisty zamiast drogich DB Oracle lub cokolwiek masz). Ale tak naprawdę jest to szczegół implementacji na poziomie architektury, na którą teraz patrzymy. Oczywiście tutaj nadal chcesz mieć separację, tzn. Nie chciałbym widzieć kodu, który ma czystą logikę domeny bezpośrednio przeplataną z dostępem do danych, intensywnie łącząc to ze sobą. Temat na inne pytanie.
Wracając do twojego pytania: Wydaje mi się, że między twoją nową architekturą a schematem MVC, który opisałem, istnieje duże pokrywanie się, więc nie jesteś w zupełnie złym kierunku, ale wydaje się, że albo wymyślasz coś na nowo, lub używając go, ponieważ sugeruje to obecne środowisko programistyczne / biblioteki. Trudno mi powiedzieć. Nie mogę więc udzielić ci dokładnej odpowiedzi, czy to, co zamierzasz, jest szczególnie dobre, czy złe. Możesz dowiedzieć się, sprawdzając, czy każda „rzecz” ma za to dokładnie jedną klasę; czy wszystko jest bardzo spójne i mało sprzężone. To daje dobrą wskazówkę i moim zdaniem wystarcza na dobry projekt OOP (lub dobry test porównawczy tego samego, jeśli chcesz).
źródło