Mam klasę, w której każda metoda zaczyna się tak samo:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
Czy istnieje dobry sposób na wymaganie (i miejmy nadzieję, że nie pisanie za każdym razem) fooIsEnabled
części dla każdej metody publicznej w klasie?
java
design-patterns
Krystyna
źródło
źródło
Odpowiedzi:
Nie wiem o eleganckim, ale oto działająca implementacja wykorzystująca wbudowaną Javę,
java.lang.reflect.Proxy
która wymusza, że wszystkie wywołania metodFoo
rozpoczynały się od sprawdzeniaenabled
stanu.main
metoda:Foo
berło:FooFactory
klasa:Jak zauważyli inni, wydaje się, że to przesada, jeśli masz tylko kilka metod, o które musisz się martwić.
To powiedziawszy, z pewnością są korzyści:
Foo
implementacje metod nie muszą się martwić oenabled
problem przekrojowy sprawdzania. Zamiast tego kod metody musi tylko martwić się o jej podstawowy cel, nic więcej.Foo
klasy i omyłkowo „zapomnieć” o dodaniuenabled
czeku. Plikenabled
sprawdzania jest automatycznie dziedziczone przez każdą nowo dodaną metodę.enabled
kontrolę, bardzo łatwo jest to zrobić bezpiecznie i w jednym miejscu.Spring
, chociaż z pewnością mogą one być również dobrymi opcjami.Aby być uczciwym, niektóre z wad to:
FooImpl
klasy jest brzydkie.Foo
, musisz dokonać zmiany w 2 miejscach: klasie implementacji i interfejsie. Nic wielkiego, ale to jeszcze trochę więcej pracy.EDYTOWAĆ:
Komentarz Fabiana Streitela sprawił, że pomyślałem o 2 kłopotach z moim powyższym rozwiązaniem, które, przyznaję, nie jestem zadowolony z siebie:
Aby rozwiązać punkt 1 i przynajmniej złagodzić problem z punktem 2, utworzyłbym adnotację
BypassCheck
(lub coś podobnego), której mógłbym użyć do oznaczenia metod wFoo
interfejsie, dla których nie chcę wykonywać polecenia „ włączona kontrola ”. W ten sposób nie potrzebuję w ogóle magicznych ciągów i programiście znacznie łatwiej będzie poprawnie dodać nową metodę w tym szczególnym przypadku.Korzystając z rozwiązania adnotacji, kod wyglądałby tak:
main
metoda:BypassCheck
adnotacja:Foo
berło:FooFactory
klasa:źródło
Jest wiele dobrych sugestii ... co możesz zrobić, aby rozwiązać swój problem, to przemyśleć Wzorzec Stanu i zaimplementować go.
Spójrz na ten fragment kodu ... być może doprowadzi Cię do pomysłu. W tym scenariuszu wygląda na to, że chcesz zmodyfikować całą implementację metod na podstawie wewnętrznego stanu obiektu. Proszę pamiętać, że suma metod w obiekcie jest określana jako zachowanie.
Mam nadzieję że ci się spodoba!
PD: Jest implementacją Wzorca Stanu (znany również jako Strategia w zależności od kontekstu .. ale zasady są takie same).
źródło
bar()
inFooEnabledBehavior
ibar()
inFooDisabledBehavior
mogą mieć wiele tego samego kodu, być może nawet o jedną linię różną między nimi. Mógłbyś bardzo łatwo, zwłaszcza jeśli ten kod byłby utrzymywany przez młodszych programistów (takich jak ja), skończyć z gigantycznym bałaganem, który jest nie do utrzymania i nie do przetestowania. Może się to zdarzyć w przypadku każdego kodu, ale wydaje się, że tak łatwo jest szybko zepsuć. +1 jednak, bo dobra sugestia.Tak, ale to trochę pracy, więc to zależy od tego, jak ważne jest to dla Ciebie.
Możesz zdefiniować klasę jako interfejs, napisać implementację delegata, a następnie użyć
java.lang.reflect.Proxy
do zaimplementowania interfejsu z metodami, które wykonują część udostępnioną, a następnie warunkowo wywołać delegata.Twój
MyInvocationHandler
może wyglądać mniej więcej tak (pominięto obsługę błędów i tworzenie szkieletów klas, zakładając, żefooIsEnabled
jest zdefiniowane w dostępnym miejscu):To nie jest niesamowicie ładne. Ale w przeciwieństwie do różnych komentatorów, zrobiłbym to, ponieważ myślę, że powtarzanie jest ważniejszym ryzykiem niż tego rodzaju gęstość, i będziesz w stanie stworzyć "odczucie" swojej prawdziwej klasy, z tym nieco nieodgadnionym opakowaniem dodanym bardzo lokalnie w zaledwie kilku wierszach kodu.
Zobacz dokumentację Java aby uzyskać szczegółowe informacje na temat dynamicznych klas proxy.
źródło
To pytanie jest ściśle związane z programowaniem aspektowym . AspectJ jest rozszerzeniem AOP języka Java i możesz rzucić okiem, aby uzyskać trochę ispiracji.
O ile wiem, nie ma bezpośredniego wsparcia dla AOP w Javie. Jest kilka wzorców GOF, które się z nim wiążą, jak na przykład Template Method i Strategy, ale tak naprawdę nie zaoszczędzą one linii kodu.
W Javie i większości innych języków można zdefiniować powtarzającą się logikę, której potrzebujesz w funkcjach i zastosować tak zwane zdyscyplinowane podejście do kodowania, w którym wywołujesz je we właściwym czasie.
Jednak to nie pasowałoby do twojego przypadku, ponieważ chciałbyś, aby kod wyodrębniony z faktury mógł powrócić z
checkBalance
. W językach, które obsługują makra (jak C / C ++), można zdefiniowaćcheckSomePrecondition
icheckSomePostcondition
jako makra, a zostaną one po prostu zastąpione przez preprocesor, zanim jeszcze zostanie wywołany kompilator:Java nie ma tego po wyjęciu z pudełka. Może to kogoś urazić, ale w przeszłości korzystałem z automatycznego generowania kodu i silników szablonów, aby zautomatyzować powtarzalne zadania związane z kodowaniem. Jeśli przetwarzasz pliki Java przed skompilowaniem ich za pomocą odpowiedniego preprocesora, na przykład Jinja2, możesz zrobić coś podobnego do tego, co jest możliwe w C.
Możliwe podejście w czystej Javie
Jeśli szukasz czystego rozwiązania w języku Java, to, co możesz znaleźć, prawdopodobnie nie będzie zwięzłe. Ale nadal może uwzględniać wspólne części programu i unikać powielania kodu i błędów. Mógłbyś zrobić coś takiego (jest to jakiś wzorzec inspirowany strategią ). Zwróć uwagę, że w C # i Javie 8 oraz w innych językach, w których funkcje są nieco łatwiejsze w obsłudze, takie podejście może faktycznie wyglądać ładnie.
Coś w rodzaju prawdziwego scenariusza
Tworzysz klasę do wysyłania ramek danych do robota przemysłowego. Wykonanie polecenia wymaga czasu. Po wykonaniu polecenia wysyła z powrotem ramkę sterującą. Robot może ulec uszkodzeniu, jeśli otrzyma nowe polecenie, podczas gdy poprzednie jest nadal wykonywane. Twój program używa
DataLink
klasy do wysyłania i odbierania ramek do i od robota. Musisz zabezpieczyć dostęp doDataLink
instancji.Połączenia gwintowe interfejs użytkownika
RobotController.left
,right
,up
lubdown
gdy użytkownik kliknie przycisków, ale również wywołujeBaseController.tick
w regularnych odstępach czasu, w celu przekazywania poleceń ponownie włączyć do prywatnejDataLink
instancji.źródło
protect
który jest łatwiejszy do ponownego wykorzystania i dokumentowania. Jeśli powiesz przyszłemu opiekunowi, że krytyczny kod powinien być chronionyprotect
, to już mówisz mu, co ma robić. Jeśli zasady ochrony ulegną zmianie, nowy kod jest nadal chroniony. To jest dokładnie uzasadnienie definiowania funkcji, ale OP musiał „zwrócićreturn
”, czego funkcje nie mogą zrobić.Rozważyłbym refaktoryzację. Ten wzór mocno łamie SUCHY wzór (nie powtarzaj tego). Wierzę, że to łamie tę klasową odpowiedzialność. Ale to zależy od twojej kontroli nad kodem. Twoje pytanie jest bardzo otwarte - gdzie dzwonisz do
Foo
instancji?Przypuszczam, że masz taki kod
może powinieneś to nazwać mniej więcej tak:
I utrzymuj to w czystości. Na przykład rejestrowanie:
a
logger
nie zadaje sobie pytania , czy debugowanie jest włączone. Po prostu rejestruje.Zamiast tego dzwoniąc do klasy, musisz to sprawdzić:
Jeśli to jest biblioteka i nie możesz kontrolować wywoływania tej klasy, wyrzuć,
IllegalStateException
co wyjaśnia dlaczego, jeśli to wywołanie jest nielegalne i powoduje problemy.źródło
IMHO najbardziej eleganckim i najskuteczniejszym rozwiązaniem tego problemu jest posiadanie więcej niż jednej implementacji Foo, wraz z fabryczną metodą jej tworzenia:
Lub odmiana:
Jak wskazuje sstan, jeszcze lepiej byłoby użyć interfejsu:
Dostosuj to do reszty swoich okoliczności (czy za każdym razem tworzysz nowe Foo, czy ponownie używasz tej samej instancji itp.)
źródło
Foo
klasy rozszerzonej i zapomnieć o dodaniu wersji tej metody, która nie działa, pomijając w ten sposób pożądane zachowanie.isFooEnabled
podczas tworzenia instancjiFoo
. Jest za wcześnie. W oryginalnym kodzie odbywa się to po uruchomieniu metody. WartośćisFooEnabled
może się w międzyczasie zmienić.fooIsEnabled
może być stały. Ale nic nam nie mówi, że ma być stały. Rozważam więc przypadek ogólny.Mam inne podejście: mam
}
i wtedy możesz to zrobić
Być może można nawet zastąpić
isFooEnabled
oFoo
zmiennej które albo przytrzymujeFooImpl
być używany lubNullFoo.DEFAULT
. Wtedy połączenie jest znowu prostsze:BTW, nazywa się to „wzorcem zerowym”.
źródło
(isFooEnabled ? foo : NullFoo.DEFAULT).bar();
wydaje się nieco niezdarne. Miej trzecią implementację, która jest delegowana do jednej z istniejących implementacji. Zamiast zmieniać wartość pola,isFooEnabled
można zmienić cel delegacji. Zmniejsza to liczbę oddziałów w kodzieFoo
w kodzie wywoławczym! Skąd mogliśmy wiedzieć, czyisFooEnabled
? To jest wewnętrzne pole w klasieFoo
.W podobnym funkcjonalnym podejściu do odpowiedzi @ Colina, z funkcjami lambda Java 8 , możliwe jest zawinięcie warunkowego przełączania funkcji włączania / wyłączania kodu do metody guard (
executeIfEnabled
), która akceptuje lambdę akcji, do której kod może być warunkowo wykonany przeszedł.Chociaż w twoim przypadku to podejście nie zapisuje żadnych wierszy kodu, poprzez suszenie tego, masz teraz możliwość scentralizowania innych problemów związanych z przełączaniem funkcji, a także problemów z AOP lub debugowaniem, takich jak logowanie, diagnostyka, profilowanie i in.
Jedną korzyścią z używania tutaj lambd jest to, że można użyć zamknięć, aby uniknąć konieczności przeciążania
executeIfEnabled
metody.Na przykład:
Z testem:
źródło
Jak wskazano w innych odpowiedziach, wzorzec projektowania strategii jest odpowiednim wzorcem projektowym, aby uprościć ten kod. Zilustrowałem to tutaj za pomocą wywołania metody poprzez odbicie, ale istnieje wiele mechanizmów, których można użyć, aby uzyskać ten sam efekt.
źródło
Proxy
.foo.fooIsEnabled ...
? A priori, jest to wewnętrzne pole obiektu, którego nie możemy i nie chcemy widzieć na zewnątrz.Gdyby tylko java była trochę lepsza w działaniu. Uważa się, że najlepszym rozwiązaniem OOO jest utworzenie klasy, która opakowuje pojedynczą funkcję, więc jest wywoływana tylko wtedy, gdy włączone jest foo.
a następnie wdrożenie
bar
,baz
ibat
jak anonimowych klas, które rozciągają sięFunctionWrapper
.Inny pomysł
Użyj rozwiązania glglgl dopuszczającego wartość null, ale make
FooImpl
iNullFoo
klasy wewnętrzne (z prywatnymi konstruktorami) poniższej klasy:w ten sposób nie musisz martwić się o pamiętanie o użyciu
(isFooEnabled ? foo : NullFoo.DEFAULT)
.źródło
Foo foo = new Foo()
aby zadzwonićbar
, napiszfoo.bar.call()
Wygląda na to, że klasa nic nie robi, gdy Foo nie jest włączone, więc dlaczego nie wyrazić tego na wyższym poziomie, na którym tworzysz lub pobierasz instancję Foo?
Działa to tylko wtedy, gdy isFooEnabled jest stałą. W ogólnym przypadku możesz utworzyć własną adnotację.
źródło
fooIsEnabled
wywołanie metody. Robisz to przed utworzeniem instancjiFoo
. Jest za wcześnie. Wartość może się w międzyczasie zmienić.isFooEnabled
jest polem instancjiFoo
obiektów.Nie znam składni języka Java. Założenie, że w Javie istnieje polimorfizm, własność statyczna, abstrakcyjna klasa i metoda:
źródło
new bar()
ma znaczyć?Name
z dużej litery.bar()
. Jeśli chcesz to zmienić, jesteś skazany na zagładę.Zasadniczo masz flagę, że jeśli jest ustawiona, wywołanie funkcji powinno zostać pominięte. Więc myślę, że moje rozwiązanie byłoby głupie, ale oto ono.
Oto implementacja prostego serwera proxy, na wypadek gdybyś chciał wykonać jakiś kod przed wykonaniem jakiejkolwiek funkcji.
źródło
isEnabled()
. A priori, bycie aktywnym oznacza wewnętrzne gotowanieFoo
, a nie eksponowanie.Istnieje inne rozwiązanie, wykorzystujące delegata (wskaźnik do funkcji). Możesz mieć unikalną metodę, która najpierw przeprowadza walidację, a następnie wywołuje odpowiednią metodę zgodnie z wywoływaną funkcją (parametrem). Kod C #:
źródło