Patrzyłem na niektóre odpowiedzi i szukałem w Google, ale nie mogłem znaleźć nic pomocnego (tzn. Nie miałoby to niewygodnych skutków ubocznych).
Moim abstrakcyjnym problemem jest to, że mam obiekt i muszę wykonać na nim długą sekwencję operacji; Myślę o tym jak o rodzaju linii montażowej, jak budowanie samochodu.
Wierzę, że te obiekty nazywałyby się obiektami metod .
Więc w tym przykładzie w pewnym momencie miałbym CarWithoutUpholstery, na którym musiałbym uruchomić installBackSeat, installFrontSeat, installWoodenInserts (operacje nie kolidują ze sobą, a nawet mogą być wykonywane równolegle). Operacje te są wykonywane przez CarWithoutUpholstery.worker () i dają nowy obiekt, który byłby CarWithUpholstery, na którym następnie uruchomiłbym może cleanInsides (), VerifyNoUpholsteryDefects () i tak dalej.
Operacje w jednej fazie są już niezależne, tj. Już zmagam się z ich podzbiorem, który można wykonać w dowolnej kolejności (przednie i tylne siedzenia mogą być instalowane w dowolnej kolejności).
Moja logika używa obecnie Reflection dla uproszczenia implementacji.
To znaczy, gdy mam CarWithoutUpholstery, obiekt sprawdza się pod kątem metod o nazwie performSomething (). W tym momencie wykonuje wszystkie te metody:
myObject.perform001SomeOperation();
myObject.perform002SomeOtherOperation();
...
podczas sprawdzania błędów i innych rzeczy. Chociaż kolejność operacji jest nieistotna, przypisałem porządek leksykograficzny na wypadek, gdyby kiedykolwiek odkryłem, że jakiś porządek jest ważny. Jest to sprzeczne z YAGNI , ale kosztowało bardzo niewiele - prosty sort () - i może zaoszczędzić ogromnej ilości zmian nazw metod (lub wprowadzenia innych metod wykonywania testów, np. Szeregu metod).
Inny przykład
Powiedzmy, że zamiast budować samochód muszę sporządzić komuś raport Tajnej Policji i przekazać go mojemu Złemu Władcy . Moim ostatnim obiektem będzie ReadyReport. Aby go zbudować, zaczynam od zebrania podstawowych informacji (imię, nazwisko, małżonek ...). To moja faza A. W zależności od tego, czy jest małżonek, czy nie, może być konieczne przejście do faz B1 lub B2 i zebranie danych dotyczących seksualności jednej lub dwóch osób. Składa się z kilku różnych zapytań do różnych złych stworów kontrolujących życie nocne, kamery uliczne, paragony sprzedaży w sklepach erotycznych i co tam nie. I tak dalej i tak dalej.
Jeśli ofiara nie ma rodziny, nie wejdę nawet w fazę GetInformationAboutFamily, ale jeśli to zrobię, nie ma znaczenia, czy najpierw skieruję cel na ojca, matkę lub rodzeństwo (jeśli w ogóle). Ale nie mogę tego zrobić, jeśli nie wykonałem FamilyStatusCheck, który dlatego należy do wcześniejszej fazy.
Wszystko działa cudownie ...
- jeśli potrzebuję dodatkowej operacji, muszę tylko dodać metodę prywatną,
- jeśli operacja jest wspólna dla kilku faz, mogę ją odziedziczyć z nadklasy,
- operacje są proste i niezależne. Żadna inna nie wymaga żadnej wartości z jednej operacji (operacje, które są wykonywane w innej fazie),
- obiekty w dalszej linii nie muszą przeprowadzać wielu testów, ponieważ nie mogłyby nawet istnieć, gdyby ich twórcy nie zweryfikowali tych warunków w pierwszej kolejności. To znaczy, umieszczając wstawki w desce rozdzielczej, czyszcząc deskę rozdzielczą i weryfikując deskę rozdzielczą, nie muszę weryfikować, czy deska rozdzielcza faktycznie tam jest .
- pozwala na łatwe testowanie. Mogę z łatwością wyśmiewać częściowy obiekt i uruchomić na nim dowolną metodę, a wszystkie operacje są deterministycznymi czarnymi skrzynkami.
...ale...
Problem pojawił się, gdy dodałem ostatnią operację w jednym z moich obiektów metod, co spowodowało, że ogólny moduł przekroczył obowiązkowy indeks złożoności („mniej niż N metod prywatnych”).
Już wziąłem sprawę na górę i zasugerowałem, że w tym przypadku bogactwo prywatnych metod nie jest oznaką katastrofy. Złożoność jest obecna, ale istnieje, ponieważ operacja jest złożona, a tak naprawdę nie jest tak złożona - jest po prostu długa .
Korzystając z przykładu Złego Władcy, moim problemem jest to, że Władca Zła (znany również jako Ten, Który Nie Będzie Zaprzeczony ), który poprosił o wszystkie informacje dietetyczne, moje Sługi Dietetyczne mówią mi, że muszę zapytać restauracje, aneksy kuchenne, ulicznych sprzedawców, nielicencjonowanych sprzedawców ulicznych, szklarnię właściciele itp. oraz Zły (pod) Overlord - znany jako Ten, Który Również Nie Będzie Zaprzeczony - narzekając, że wykonuję zbyt wiele zapytań w fazie GetDietaryInformation.
Uwaga : zdaję sobie sprawę, że z kilku punktów widzenia nie jest to wcale problemem (ignorowanie możliwych problemów z wydajnością itp.). Wszystko, co się dzieje, polega na tym, że określona metryka jest niezadowolona i jest to uzasadnione.
Co myślę, że mógłbym zrobić
Oprócz pierwszego wszystkie te opcje są wykonalne i, moim zdaniem, możliwe do obrony.
- Potwierdziłem, że mogę być podstępny i zadeklarować połowę moich metod
protected
. Ale wykorzystywałbym słabość procedury testowej i poza usprawiedliwieniem się, gdy mnie przyłapano, nie podoba mi się to. Ponadto jest to miara zatrzymująca. Co się stanie, jeśli liczba wymaganych operacji podwoi się? Mało prawdopodobne, ale co wtedy? - Mogę dowolnie podzielić tę fazę na AnnealedObjectAlpha, AnnealedObjectBravo i AnnealedObjectCharlie, a jedną trzecią operacji wykonuję na każdym etapie. Mam wrażenie, że to faktycznie dodaje złożoności (więcej klas N-1), bez żadnej korzyści poza zaliczeniem testu. Mogę oczywiście stwierdzić, że CarWithFrontSeatsInstalled i CarWithAllSeatsInstalled to logicznie kolejne etapy. Ryzyko, że metoda Bravo będzie później wymagana przez Alphę, jest niewielkie, a nawet mniejsze, jeśli dobrze ją zagram. Ale nadal.
- Mogę łączyć różne operacje, zdalnie podobne, w jednym.
performAllSeatsInstallation()
. Jest to tylko miara zatrzymująca i zwiększa złożoność pojedynczej operacji. Jeśli kiedykolwiek będę musiał wykonać operacje A i B w innej kolejności i zapakowałem je w E = (A + C) i F (B + D), będę musiał rozdzielić E i F i przetasować kod . - Mogę użyć szeregu funkcji lambda i całkowicie ominąć czek, ale uważam to za niezręczne. Jest to jednak jak dotąd najlepsza alternatywa. Pozbyłby się refleksji. Dwa problemy, które mam, polegają na tym, że prawdopodobnie zostałbym poproszony o przepisanie wszystkich obiektów metod, nie tylko hipotetycznych
CarWithEngineInstalled
, i chociaż byłoby to bardzo dobre bezpieczeństwo pracy, to tak naprawdę wcale nie jest atrakcyjne; oraz że sprawdzanie pokrycia kodu ma problemy z lambdami (które można rozwiązać, ale nadal ).
Więc...
- Jak myślisz, która opcja jest dla mnie najlepsza?
- Czy jest lepszy sposób, którego nie rozważałem? ( może lepiej przyjdę czysty i zapytam bezpośrednio, co to jest? )
- Czy ten projekt jest beznadziejnie wadliwy i czy lepiej przyznać się do porażki i rowy - tej architektury? Nie jest to dobre dla mojej kariery, ale czy pisanie źle zaprojektowanego kodu byłoby lepsze w dłuższej perspektywie?
- Czy mój obecny wybór jest w rzeczywistości One True Way i muszę walczyć o zainstalowanie lepszej jakości wskaźników (i / lub oprzyrządowania)? Do tej ostatniej opcji potrzebuję referencji ... Nie mogę po prostu machnąć ręką na @PHB podczas szemrania. To nie są te parametry, których szukasz . Nie ważne jak bardzo chciałbym być w stanie