Clean Code - instrukcja `switch`, wyjaśnienie polimorfizmu

Clean Code - instrukcja `switch`, wyjaśnienie polimorfizmu
Burdzi0
  • Rejestracja:prawie 9 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Futurama
  • Postów:887
1

Czytam Czysty Kod Roberta C. Martina i nie rozumiem jednej części dotyczącej korzystania z polimorfizmu zamiast instrukcji switch.

Mając klasę Payroll.java

Kopiuj
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
	switch (e.type) {
		case COMMISSIONED:
			return calculateComissionedPay(e);
		case HOURLY:
			return calculateHourlyPay(e);
		case SALARIED:
			return calculateSalariedPay(e);
		default:
			throw new InvalidEmployeeType(e.type);

W treści odnajdujemy taki fragment:

Z tą funkcją wiąże się wiele problemów. Po pierwsze jest duża, a po dodaniu kolejnego typu pracownika urośnie jeszcze bardziej. Po drugie jasne jest, że wykonuje jeszcze jedną operację. Po trzecie narusza zasadę pojedynczej odpowiedzialności (SRP), ponieważ istnieje więcej niż jeden powód uzasadniający zmianę. Po czwarte, narusza zasadę otwarty-zamknięty (OCP), ponieważ musi się zmieniać przy każdym dodaniu nowego typu. ...

Autor proponuje takie rozwiązanie:

Kopiuj
public abstract class Employee {
	public abstract boolean isPayday();
	public abstract Money calculatePay();
	public abstract void deliverPay(Money pay);
}
----------------------
public interface EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
----------------------
public class EmployeeFactoryImpl implements EmployeeFactory {
	public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
		switch (r.type) {
		case COMMISSIONED:
			return new CommissionedEmployee(r);
		case HOURLY:
			return new HourlyEmployee(r);
		case SALARIED:
			return new SalariedEmployee(r);
		default:
			throw new InvalidEmployeeType(r.type);
		}
	}
}

Moje pytanie odnosi się właśnie czwartego punktu, czyli zasady otwarty-zamknięty. Jak to, co zaproponował autor ma zapewnić nienaruszanie zasady OCP skoro, przy każdym nowym typie (Next)Employee trzeba otworzyć klasę EmployeeFactoryImpl i edytować metodę makeEmployee?


Bite my shiny metal ass!
Life throws you an error code like that, you don't have the luxury of a ZnVja2luZw== pop-up explanation *Robię projekty studenckie, pisz priv ;) *
twonek
  • Rejestracja:prawie 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
3

Po tej zmianie ta pierwotna funkcja w Payroll.java będzie pewnie wyglądać tak:

Kopiuj
public Money calculatePay(Employee e) {
    return e.calculatePay();
}

i nie będzie się zmieniać przy dodaniu kolejnych typów. A to że fabryka będzie się zmieniać to niejako konieczność, bo gdzieś musi być zawarta informacja o typach. Ale powiesz "no to jaki jest zysk skoro zamiast zmieniać w jednym miejscu będę zmieniać w innym?". Zysk jest taki, że przy drugim podejściu nadal zmiana jest w 1 miejscu nawet gdy masz 10 czy 20 funkcji takich jak Payroll.calculatePay(), a w pierwszym w każdej z tych funkcji trzeba zmienić kod. No i oczywiści SRP.

V-2
  • Rejestracja:około 8 lat
  • Ostatnio:10 miesięcy
  • Postów:671
3

@tworek dobrze napisał.

Od siebie dodam, że ta fabryka też nie musi być oparta na konstrukcji switch-case. To jest najprostsze, ale jej implementację też można zamknąć.

Chociażby tak:

Kopiuj
interface EmployeeProducer {
    Employee makeEmployee(EmployeeRecord record);
}

public final class EmployeeFactory implements EmployeeProducer {
    private final Map<EmployeeRecordType, EmployeeProducer> producers = new HashMap<>();
    
    public void register(EmployeeRecordType employeeType, EmployeeProducer producer) {
        producers.put(employeeType, producer);
    }

    @Override
    public Employee makeEmployee(EmployeeRecord record)  {
        EmployeeProducer producer = producers.get(record.type);
        if (producer != null) {
            return producer.makeEmployee(record);
        }
        throw new InvalidEmployeeType(record.type);
    }
}

I wtedy gdzieś z zewnątrz "wstrzykujemy" do fabryki producenta, niejako rejestrując nowy typ pracownika. Np.:

Kopiuj
factory.register(
        EmployeeRecordType.DRUNKARD,
        new EmployeeProducer() {
            @Override
            public Employee makeEmployee(EmployeeRecord record) {
                return new DrunkardEmployee(record);
            }
        });

Albo, jeśli mamy szczęście korzystać z bardziej współczesnej wersji Javy, z użyciem lambdy:

Kopiuj
factory.register(EmployeeRecordType.DRUNKARD, record -> new DrunkardEmployee(record)); 
// czy nawet, jeśli dobrze pamiętam, record -> DrunkardEmployee::new;

Ot, i wszystko. Nie musimy wtedy w ogóle rozpruwać kodu fabryki - możemy rozwijać nasz kod inkrementacyjnie, wstrzykując do niej nowego producenta obiektów Employee skądkolwiek nam pasuje.


Nie ma najmniejszego powodu, aby w CV pisać "email" przed swoim adresem mailowym, "imię i nazwisko" przed imieniem i nazwiskiem, ani "zdjęcie mojej głowy od przedniej strony" obok ewentualnego zdjęcia. W drugiej firmie której już pracuję mam palących marihuanę programistów [...] piszą kod "leniwie", często nie wysilając się, rozwlekając ten kod, unikając np. programowania funkcyjnego (mówię tutaj o lambdach w javie).
edytowany 3x, ostatnio: V-2
Burdzi0
  • Rejestracja:prawie 9 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Futurama
  • Postów:887
0

Jeszcze tylko jedno pytanie - po co nam interfejs skoro wiemy (autor wie) dokładnie co chcemy napisać? (Pomijając ewentualną lambdę, książka jest chyba za stara na tego typu zabiegi)


Bite my shiny metal ass!
Life throws you an error code like that, you don't have the luxury of a ZnVja2luZw== pop-up explanation *Robię projekty studenckie, pisz priv ;) *
panryz
Spróbuj napisać test do tego bez interfejsu i z interfejsem.
V-2
Jak napisałbyś nawet ten kod produkcyjny bez interfejsu?
jarekr000000
Ten interfejs można w sumie zastąpić przez Function<EmployeeRecord, Employee>
V-2
Pewnie tak, jako androidowiec nie jestem z Javą na bieżąco, my tego nie mamy (pomijając biblioteki typu Guava)
Burdzi0
  • Rejestracja:prawie 9 lat
  • Ostatnio:6 miesięcy
  • Lokalizacja:Futurama
  • Postów:887
0

@V-2: Nie wiem, próbowałem sobie to rozpisać, ale jakoś wzorce fabryki abstrakcyjnej mnie przerastają...
Nawet testu nie umiałbym do tego napisać :/


Bite my shiny metal ass!
Life throws you an error code like that, you don't have the luxury of a ZnVja2luZw== pop-up explanation *Robię projekty studenckie, pisz priv ;) *
V-2
  • Rejestracja:około 8 lat
  • Ostatnio:10 miesięcy
  • Postów:671
0

Może powinieneś więcej eksperymentować z kodem i zajmować się praktyką, bo jadąc na samej teorii (nawet tak dobrej jak "Czysty kod") szybko odczujesz ograniczenia tego podejścia.


Nie ma najmniejszego powodu, aby w CV pisać "email" przed swoim adresem mailowym, "imię i nazwisko" przed imieniem i nazwiskiem, ani "zdjęcie mojej głowy od przedniej strony" obok ewentualnego zdjęcia. W drugiej firmie której już pracuję mam palących marihuanę programistów [...] piszą kod "leniwie", często nie wysilając się, rozwlekając ten kod, unikając np. programowania funkcyjnego (mówię tutaj o lambdach w javie).
Burdzi0
Próbowałem sobie całość kodu rozpisać - wszystkie klasy, itp. niekoniecznie wszystko wychodzi. Ostatnie moje projekty są do pewnego stopnia rozwijane, a potem napotykam problem (architektura/brak testów/brak planowania, nie wiem co może być przyczyną) i po prostu dalej nie pójdzie. Stąd też chwilowa ucieczka w książkę
V-2
Rozpisanie sobie całej mapy projektu to nie jest to, co rozumiem jako praktykę. Wymuszasz na sobie (jak sądzę) ogarnięcie całej architektury jednym skokiem
Burdzi0
A co w tym wypadku rozumiesz jako praktykę? Praca nad innym kodem? Robię to, nie zawsze się udaje.
V-2
Architektura rosnąca oddolnie, czyli mamy rozwiązanie naiwne i drobnymi kroczkami refaktorujemy je w kierunku bardziej złożonych abstrakcji, najlepiej uzbrojeni w breakpoint i często sprawdzając, jak to działa.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)