TypeScript + PlantUML - W Internecie znajduję różne oznaczenia, więc może jest to kwestia konwencji - chciałbym jednak się poradzić, w jaki sposób powinienem poprawnie oznaczyć relacje na diagramie Budowniczego pomiędzy:
a). interfejsem Builder
i klasą Director
- czy ciągła strzałka z niezamalowanym rombem mówi "klasa zawiera egzemplarz interfejsu"?
b). klasą Crew
i klasą House
- czy ciągłą strzałka mówi "klasa zawiera egzemplarz innej klasy"?
c). w sumie... Director
bez Builder
a nic nie zbuduje, więc może romb powinien być zamalowany?
- Rejestracja:prawie 6 lat
- Ostatnio:ponad 3 lata
- Postów:12

- Rejestracja:około 21 lat
- Ostatnio:prawie 3 lata
- Lokalizacja:Space: the final frontier
- Postów:26433
a) To jest "agregacja" czyli zawieranie, ale obiekty maja osobną "tożsamość", tzn ten builder może spokojnie istnieć bez twojego directora i można go używać samodzielnie
b) Na diagramie masz asocjacje, która normalnie sugeruje że obiekty danych klas ze sobą współpracują i nie ma tu mowy o żadnym "zawieraniu"
c) Ale to zamalowanie znaczyłoby coś zupełnie innego - znaczyłoby że nie twój Builder nie ma sensu bez Directora, co nie jest prawdą.
Ogólnie to jakiś srogi WTF ten cały diagram i generalnie w 99% przypadków wcale się takiego cyrku nie robi. W praktyce builder wygląda mniej wiecej tak:
class A{
private final int x;
public A(int x){
this.x=x;
}
public static Builder builder(){
return new Builder();
}
public static class Builder{
int x;
public Builder withX(int value){
this.x = value;
return this;
}
public A build(){
return new A(x);
}
}
}
(normalnie oczywiście pól jest więcej i wiele z nich jest opcjonalna, plus build
może robic jakieś walidacje parametrów i ewentualnie budować obiekty pod-klas)
Od biedy można tych builderów mieć niby więcej, ale to już wtedy jakis mix buildera z factory.

- Rejestracja:ponad 13 lat
- Ostatnio:prawie 3 lata
Jeśli chodzi o buildera można ograniczyć zakres jego dostępności (z głowy na podst. jakiejś książki o FP, chyba Functional Programming in Java):
class A{
private final int x;
private A(int x){
this.x=x;
}
public int getX() { return x; }
public static A build(Function<Builder, Builder> builderFiller){
Builder builder = new Builder();
builder = builderFiller.apply(builder);
return builder.build();
}
public static class Builder{
private int x;
private Builder() {}
public Builder withX(int value){
this.x = value;
return this;
}
private A build(){
return new A(x);
}
}
}
Użycie:
A a = A.build(builder -> builder.withX(11));

- Rejestracja:ponad 7 lat
- Ostatnio:5 dni
- Postów:3277
Pewnie dostanę brązową łopatę za odgrzebanie tego wątku...
Nie wiem co przedstawia diagram z pierwszego postu, ale nie jest to nic wspólnego ze znanymi mi wzorcami Builder.
Nie lubię tego wzorca, bo w 99% używa się go do przykrycia faktu że jakaś klasa ma 500 zależności i analiza statyczna ma SO po zaczytaniu samej sygnatury konstruktora. Pozostały 1% to użycie zgodnie z przeznaczeniem, ale wynikające z ubóstwa języka.
Sens użycia tego wzorca, to tworzenie obiektów (captain obvious) w sposób względnie wygodny dla programisty (bo język nie pozwala lepiej) i bezpieczny, czyli z wymuszeniem i sprawdzeniem, że wszystko co miało zostać przekazane, zostało przekazane. Głównym powodem użycia Buildera jest brak w języku parametrów opcjonalnych / domyślnych.
Gdybym NAPRAWDĘ MUSIAŁ zrobić jakąś skomplikowaną w inicjalizacji bibliotekę, to zakładając niezmienność stanów powstałby taki potworek:
package org.example.builder;
import java.time.Clock;
import java.time.Instant;
import java.util.Optional;
public class Person {
private final String firstname;
private final String lastname;
private final Instant birthDate;
private Person(String firstname, String lastname, Instant birthDate) {
this.firstname = firstname;
this.lastname = lastname;
this.birthDate = birthDate;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public Instant getBirthDate() {
return birthDate;
}
public static class Builder{
private final String fistName;
private final String lastName;
private final Instant birthDate;
private Builder(String firstName, String lastName, Instant birthDate) {
if(firstName == null || lastName == null || birthDate == null){
throw new RuntimeException("Parameter is null");
}
this.fistName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public Builder(String firstName, String lastName) {
//just born...
this(firstName, lastName, Clock.systemUTC().instant());
}
public Builder withBirthDdate(Instant birthDate){
return new Builder(this.fistName, this.lastName, birthDate);
}
public Person build(){
return new Person(this.fistName, this.lastName, this.birthDate);
}
}
}
Przykłady użycia:
Person person = new Person.Builder(first, last).build();
Person person = new Person.Builder(first, last)
.withBirthDdate(birthDate)
.build();
Dla kontrastu implementacja tego co wyżej w czymś odrobinę bardziej normalnym
class KotlinPerson(val firstName:String, val lastName:String, val birthDate: Instant = Clock.systemUTC().instant())
jaj
.null
. Nie można było zrobić innego wyjątku dla argumentów?NullPointerException
to bardzo dobry wyjątek na walidację -użyłeś złego argumentu - null
. W pakiecie bonusowym kompilator generuje to w większości sam i nie trzeba explicite sprawdzać. Warto zauważyć, że jest tam jeszcze@NonNull
(zresztą to przez tą adnotację dodatkowy check został wstawiony) . Normalniekompilator zlomboczonylinter nullowalnego argumentu nie powinien przepuścić. Ta asercja to takie sprawdzenie ostatniej szansy - jeśli wiele rzeczy poszłonie tak
.