Zasadniczo muszę wykonywać różne działania, biorąc pod uwagę określony warunek. Istniejący kod jest zapisywany w ten sposób
Interfejs podstawowy
// DoSomething.java
interface DoSomething {
void letDoIt(String info);
}
Wdrożenie pierwszej klasy robotniczej
class DoItThisWay implements DoSomething {
...
}
Wdrożenie drugiej klasy robotniczej
class DoItThatWay implements DoSomething {
...
}
Główna klasa
class Main {
public doingIt(String info) {
DoSomething worker;
if (info == 'this') {
worker = new DoItThisWay();
} else {
worker = new DoItThatWay();
}
worker.letDoIt(info)
}
Ten kod działa dobrze i jest łatwy do zrozumienia.
Teraz, ze względu na nowy wymóg, muszę przekazać nową informację, która ma sens DoItThisWay
.
Moje pytanie brzmi: czy następujący styl kodowania jest dobry, aby poradzić sobie z tym wymogiem.
Użyj nowej zmiennej klasy i metody
// Use new class variable and method
class DoItThisWay implements DoSomething {
private int quality;
DoSomething() {
quality = 0;
}
public void setQuality(int quality) {
this.quality = quality;
};
public void letDoIt(String info) {
if (quality > 50) { // make use of the new information
...
} else {
...
}
} ;
}
Jeśli zrobię to w ten sposób, muszę wprowadzić odpowiednią zmianę do osoby dzwoniącej:
class Main {
public doingIt(String info) {
DoSomething worker;
if (info == 'this') {
int quality = obtainQualityInfo();
DoItThisWay tmp = new DoItThisWay();
tmp.setQuality(quality)
worker = tmp;
} else {
worker = new DoItThatWay();
}
worker.letDoIt(info)
}
Czy to dobry styl kodowania? Czy mogę to po prostu rzucić
class Main {
public doingIt(String info) {
DoSomething worker;
if (info == 'this') {
int quality = obtainQualityInfo();
worker = new DoItThisWay();
((DoItThisWay) worker).setQuality(quality)
} else {
worker = new DoItThatWay();
}
worker.letDoIt(info)
}
java
coding-style
Anthony Kong
źródło
źródło
quality
do konstruktoraDoItThisWay
?DoItThisWay
iDoItThatWay
są wykonywane raz w konstruktorzeMain
.Main
jest klasą długowieczną idoingIt
nazywa się ją wiele razy.setQuality
metoda będzie wywoływana wiele razy podczas życiaDoItThisWay
obiektu?Odpowiedzi:
Zakładam, że to
quality
musi być ustawione obok każdegoletDoIt()
połączenia naDoItThisWay()
.Problem widzę wynikające tutaj jest taka: Ty wprowadzamy sprzęgło czasowej (czyli co happends jeśli zapomnisz zadzwonić
setQuality()
przed wywołaniemletDoIt()
na zasadzieDoItThisWay
?). A implementacje są rozbieżneDoItThisWay
iDoItThatWay
rozbieżne (jedno trzebasetQuality()
zadzwonić, drugie nie).Chociaż może to nie powodować problemów w tej chwili, może w końcu cię prześladować. Warto spojrzeć jeszcze raz
letDoIt()
i zastanowić się, czy informacje jakościowe mogą być częściąinfo
przekazywanej przez ciebie informacji; ale to zależy od szczegółów.źródło
Z mojego punktu widzenia żadna z twoich wersji nie jest. Najpierw trzeba zadzwonić,
setQuality
zanimletDoIt
będzie można go wywołać, to połączenie czasowe . Utkniesz oglądanieDoItThisWay
jako pochodnaDoSomething
, ale to nie jest (przynajmniej nie funkcjonalnie), to raczej coś w rodzajuTo by było
Main
raczej coś w rodzajuZ drugiej strony możesz przekazać parametry bezpośrednio do klas (zakładając, że jest to możliwe) i przekazać decyzję, której z nich użyć do fabryki (sprawiłoby to, że instancje byłyby wymienne i oba można wyprowadzić
DoSomething
). To byMain
wyglądało takWiem, że to napisałeś
Ale możesz buforować części, które są kosztowne do wytworzenia w fabryce i przekazać je również konstruktorowi.
źródło
DoItThisWay
wymaga ustawienia jakości przed wywołaniemletDoIt
, zachowuje się inaczej. Spodziewałbym się, że aDoSomething
zadziała w taki sam sposób (wcześniej nie ustawiając jakości) dla wszystkich instrumentów pochodnych. Czy to ma sens? Oczywiście jest to trochę rozmyte, ponieważ jeślisetQuality
wywołamy metodę fabryczną, klient będzie agnostyczny, czy jakość musi być ustawiona, czy nie.letDoIt()
dotyczy (brak jakichkolwiek dodatkowych informacji) „Mogę poprawnie wykonać (zrobić wszystko, co robię), jeśli mi to zapewniszString info
”. WymaganiesetQuality()
wcześniejszego wezwania wzmacnia warunek wstępny powołanialetDoIt()
, a zatem stanowiłoby naruszenie LSP.letDoIt
” nie wyrzuci żadnych wyjątków, a zapomnienie o wywołaniu „setQuality” spowoduje, żeletDoIt
wyrzuci wyjątek, to zgodzę się, taka implementacja naruszy LSP. Ale te wyglądają dla mnie na bardzo sztuczne założenia.Załóżmy, że
quality
nie można przekazać do konstruktora, a wywołaniesetQuality
jest wymagane.Obecnie fragment kodu podobny do
jest o wiele za mały, aby zainwestować w niego zbyt wiele myśli. IMHO wygląda trochę brzydko, ale nie bardzo trudno zrozumieć.
Problemy pojawiają się, gdy taki fragment kodu rośnie, a po refaktoryzacji powstaje coś takiego
Teraz nie widzisz od razu, czy ten kod jest nadal poprawny, a kompilator ci nie powie. Tak więc wprowadzenie tymczasowej zmiennej właściwego typu i unikanie rzutowania jest nieco bardziej bezpieczne dla typu, bez żadnego dodatkowego wysiłku.
Jednak tak naprawdę dałbym
tmp
lepszą nazwę:Takie nazewnictwo pomaga sprawić, że zły kod wygląda źle.
źródło
DoItThisWay
uzyskuje się bardziej szczegółowe parametry - według sugestii nazwa musi być zmieniana za każdym razem. Lepiej jest używać nazw vor zmiennych, które czynią typ obiektu jasnym, a nie nazwy metod, które są dostępne.workerThisWay
stało się coś w tym rodzajuusingQuality
. W tym małym fragmencie kodu bezpieczniej jest być bardziej szczegółowym. (A jeśli w ich domenie jest lepsze wyrażenie niż „usingQuality”, użyj go.Wspólny interfejs, w którym inicjalizacja różni się w zależności od danych wykonawczych, zwykle nadaje się do wzorca fabrycznego.
Następnie DoThingsFactory może martwić się uzyskaniem i ustawieniem informacji o jakości wewnątrz
getDoer(info)
metody przed zwróceniem konkretnego obiektu DoThingsWithQuality rzutowanego do interfejsu DoSomething.źródło