Wzorce projektowe - rlip/java GitHub Wiki

Wzorce-kreacyjne

Wzorce-czynnościowe

Wzorce-strukturalne

Pozostałe

Wzorce kreacyjne

Wzorzec budowniczy

Robimy np. żeby nie robić konstruktorów z wieloma elementami.

Robi się klasę np. HouseBuilder (ctrl+i na konstruktorze i wpisać builder), najlepiej jako statyczną i wewnątrz klasy którą chcemy zrobić. Tam są wszystkie setery, które zwracają też HouseBuilder, żeby można było je robić jeden za drugim. Na końcu metoda build, która już stworzy oryginalną klasę i ma wszystko w seterze. Konstruktor oryginalnej klasy może być prywatny.

    public class House {
        private String address;
        private Integer floorsNumber;
        private House(String address, Integer floorsNumber) { 
            this.address = address;
            this.floorsNumber = floorsNumber;
        }
        public static class HouseBuilder {
            private String address;
            private Integer floorsNumber = 1; //może być coś domyślnego
            public HouseBuilder setAddress(String address) {
                this.address = address;
                return this;
            }
            public HouseBuilder setFloorsNumber(Integer floorsNumber) {
                this.floorsNumber = floorsNumber;
                return this;
            }
            public House build() {
                return new House(address, floorsNumber);
            }
        }
    }

Statyczna metoda wytwórcza

Pozwala utworzyć obiekt za pomocą innego obiektu np. Boolean.valueOf(), Obiekt z jsona, albo jakieś dom z innego domu. Metody: from (na podstawie), of (z, np. json, xml), valueOf (jest wartością), instanceOf

    FamilyHouse familyHouse = FamilyHouse.from(myHouse);
    
    // i w klasie:

    public static FamilyHouse from(House myHouse) {
        return new FamilyHouse(myHouse.getAddress(), myHouse.getFloorsNumber());
    }
Zalety statycznych metod wytwórczych:
- posiadają nazwę
- nie jest wymagane utworzenie obiektu
- mogą zwracać typ, który jest podtypem zdefiniowanego, zwracanego typu, i ten zwracany typ może zależeć od parametrów
Wady: 
- nie mogą być dziedziczone i nie można takich metod odróżnić od innych metod statycznych
Metody:
  • from — metoda konwersji typu, która przyjmuje jeden parametr i zwraca obiekt, który posiada tę samą wartość co parametr:
Date d = Date.from(instant);
  • of — metoda agregująca, która przyjmuje wiele parametrów i zwraca instancję odpowiedniego typu zawierającą wszystkie parametry:
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf — nieco bardziej rozwlekła alternatywa dla from i of, np.:
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance lub instanceOf — zwraca obiekt opisany przez parametr (jeśli istnieje), ale nie można powiedzieć, że ma tę samą wartość:
StackWalker luke = StackWalker.getInstance(options);
  • create lub newInstance — podobna do instance lub getInstance, poza tym że gwarantuje, iż każda zwracana instancja jest inna od pozostałych:
Object newArray = Array.newInstance(classObject, arrayLen);
  • getType — podobna do getInstance, ale używana, gdy metoda fabryczna znajduje się w osobnej klasie. Type wskazuje na typ obiektu zwracanego przez metodę fabryczną:
FileStore fs = Files.getFileStore(path);
  • newType — podobna do newInstance, ale używana, gdy metoda fabryczna znajduje się w osobnej klasie. Type wskazuje na typ obiektu zwracanego przez metodę fabryczną:
BufferedReader br = Files.newBufferedReader(path);
  • type — zwięzła alternatywa dla getType lub newType:
List<Complaint> litany = Collections.list(legacyLitany);

Singleton

Jeden obiekt na 1 program, intensywnie używany w większości klas np. log, stan autentyfikacji, cache, repozytoriom, coś do śledzenia, coś co się nie zmienia bez względu na miejsce użycia. Trzeba uważać żeby był bezpieczny wątkowo.

public class Logger {
    private static Logger instace;

    private Logger() {
    }

    public static Logger getInstace() {
        return SingletonHolder.INSTANCE;
    }

    public void logToConsole(){

    }

    private static class SingletonHolder {
        private static final Logger INSTANCE = new Logger();
    }
}

Można też jako 1 - elementowy enum:

public enum Elvis {
   INSTANCE;
   public void leaveTheBuilding() { ... }
}

Wzorce czynnościowe

Żeby dzielić duże klasy na mniejsze z 1 odpowiedzialnością i nimi zarządzać.

Obserwator

Jest obiekt obserwujący i obserwowany. Obserwowany powiadamia obserwującego(cych) o zmianach. Np. obsługa interfejsu użytkownika (jakaś akcja na kliknięciu), reakcja na coś (np. zmiany repozytorium), obsługa zmiany w czujnikach. Klasa Observer jest przestarzała, bo nie można jej serializować, nie jest bezpieczna wątkowo, i nie dostarcza zbyt dużo informacji You can use PropertyChangeEvent and PropertyChangeListener from java.beans package. Można też samemu to zrobić. Czyli w jednej klasie trzymamy tablicę gdzie będą obserwatory - klasy implementujacy jakiś interfejs z 1 funkcją, która na nich będzie wywoływana. Są tam też metody do dodawania, usuwania i powiadomienia obserwatorów. I w zasadzie tyle. Tu jest przykład taki ze streemam https://community.oracle.com/docs/DOC-1006738

Strategia

Robimy interfejs(y) np. JobStrategy i jego implementacje np. DoctorJobStrategy. Potem jakiś obiekt będzie miał zmienną(e) typu tego(tych) interfejsu, ustawianą gdzieś z zewnątrz. Następnie obiekt ma metodę, w której jest wykonanie akcji na na tamtej zmiennej. Czyli można dynamicznie takie strategie ustawiać obiektowi i je zmieniać w razie potrzeby.

    public interface JobStrategy {
        void doYourJob();
    }
    public class DoctorJobStrategy implements JobStrategy {
        @Override
        public void doYourJob() {
            System.out.println("Work as doctor");
        }
    }
    public class Employee implements TravelStrategy, JobStrategy {
        public JobStrategy jobStrategy;
    
        @Override
        public void doYourJob() {
            jobStrategy.doYourJob();
        }
    }
    public static void main(String[] args) {
        Employee mike = new Employee();
        mike.jobStrategy = new DoctorJobStrategy();
        mike.doYourJob();
    }

Metoda szablonowa

Jej zadaniem jest zdefiniowanie metody, będącej szkieletem algorytmu. Algorytm ten może być następnie dokładnie definiowany w klasach pochodnych. Niezmienna część algorytmu zostaje opisana w metodzie szablonowej, której klient nie może nadpisać (final). W metodzie szablonowej wywoływane są inne metody, reprezentujące zmienne kroki algorytmu. Mogą one być abstrakcyjne lub definiować domyślne zachowania. Klient, który chce skorzystać z algorytmu, może wykorzystać domyślną implementację bądź może utworzyć klasę pochodną i nadpisać metody opisujące zmienne fragmenty algorytmu.

Plusy: łatwo wprowadza się alternatywne implementacje, redukcja duplikacji kodu. Minusy: może trudna w utrzymaniu i sporo ogranicz kilentów - bo muszą się trzymać zdefiniowanych kroków

public abstract class AbstractExporter {

    private final Report report;
    private final PrintStream out;

    AbstractExporter(Report report, PrintStream out) {
        this.report = report;
        this.out = out;
    }

    public final void export() { //stała metoda dla wszystkich klas pochodnych
        beforeLabels(out);
        handleLabels(out, report.getLabels());
        afterLabels(out);

        beforeRecords(out);
        handleRecords();
        afterRecords(out);
    }

    protected void beforeLabels(PrintStream out) { // metoda jak ta można nadpisać
    }
...

Wzorce strukturalne

Wzorce modelujące związki pomiędzy klasami, żeby były luźno powiązane. Łączenie, integracja różnych klas.

Adapter

Rozszerza funkcjonalność na podstawie innego obiektu. Obiektu starego nie zmieniamy, a nowy adoptowujemy do nowych wymagań. Można np. połączyć nim różne api, ale raczej unikać żeby nie było za dużego zamieszania.

Robi się nową klasę, która albo rozszerza poprzednią albo lepiej - ma jako pole tą poprzednią i na niej wywołuje się stare lub nowe funkcjonalności

OffitialTrippingEmployee ofMike = new OffitialTrippingEmployee(mike); //rozszerzeamy fukcjonalność mika nie zmieniająć jego klasy
ofMike.goToClient();
-------------------
    public void goToClient() {
        employee.travelToWork(employee);
        System.out.println("Travel to Klient"); // i tu coś nowego
    }

Dekorator

Dekorujemy obiekt dodając mu coś nowego. Robi się to tak, że tworzy się taki łańcuch opakowując klasę inną klasą, a to można dalej opakowywać.

new FreqBonus(new DeadLinieBonus(new SpecialBonus(mike))).getSalary()

Każda klasa opakowywyjąca dziedziczy po klasie abstrakcyjnej. W której jest zmienna, która będzie zawierać to co do tej pory się nazabierało i konstruktor wpisujący to w tą zmienną.

public abstract class Bonus implements Payable{
    private Payable payable;

    Bonus(Payable payable){
        this.payable = payable;
    }
    public int getSalary() {
        return payable.getSalary() + getPaidBonus(payable.getSalary());
    }
    abstract protected int getPaidBonus(int salary);
}
-----------------------------------------
public class SpecialBonus extends Bonus {
    public SpecialBonus(Payable payable) {
        super(payable);
    }

    @Override
    protected int getPaidBonus(int salary) {
        return 1000;
    }
}

Fasada

Upraszcza metody które wystawiane dalej, żeby były prostsze, idiotoodporne i żeby nikt nie integrował w wewnętrzny system. Można robić kilka poziomów. Dobrze utrzymywać ten sam poziom w fasadach.

public class ApiFacade {
    private EmployeeCreator employeeCreator = new EmployeeCreator(); //to taka fabryka
    private EmployeeDatabase eDataBase = new EmployeeDatabase(); 
    
    public Employee createDoctor(int i) {
       Employee mike = employeeCreator.create(EmployeeCreator.JAKAS_STALA);
       eDatabase.addEmployee(mike);
       return mike;
    }
    public int countSalary(Employee mike){
        //a tu coś jeszcze innego
    }
}
⚠️ **GitHub.com Fallback** ⚠️