Tutorium 06.05

Warmup

Welche Methode wird hier aufgerufen: Die von Dog oder die von Animal?

1Animal a = new Dog();
2a.makeSound();

Aufgabe 1

Dieser Code soll so verändert werden, dass man neue Tiere zum Zoo hinzufügen kann, ohne das dieser verändert werden muss. Anschließend sollen noch Kühe und Schweine zum Zoo hinzugefügt werden. (Der Zoo sollte sich hierfür nicht ändern)

 1import java.util.ArrayList;
 2import java.util.List;
 3
 4class Dog {
 5    public void makeSound() {
 6        System.out.println("Woof");
 7    }
 8}
 9
10class Cat {
11    public void makeSound() {
12        System.out.println("Meow");
13    }
14}
15
16class Zoo {
17    public void makeAllSounds(List<Object> animals) {
18        for (Object animal : animals) {
19            if (animal instanceof Dog) {
20                Dog dog = (Dog) animal;
21                dog.makeSound();
22            } else if (animal instanceof Cat) {
23                Cat cat = (Cat) animal;
24                cat.makeSound();
25            }
26        }
27    }
28}
29
30public class Main {
31    public static void main(String[] args) {
32        List<Object> animals = new ArrayList<>();
33
34        animals.add(new Dog());
35        animals.add(new Cat());
36
37        Zoo zoo = new Zoo();
38        zoo.makeAllSounds(animals);
39    }
40}
Lösung anzeigen
  1. Was muss der Zoo wissen?

    Der Zoo muss nur wissen, dass jedes Tier einen Laut von sich geben kann.

    interface Animal {
        void makeSound();
    }
    
  2. Die Klassen, welche das Interface implementieren stellen das sicher.

    class Dog implements Animal
    

    Da jede Klasse, die das Interface implementiert auch über die makeSound methode verfügen muss.

    void makeSound();
    
  3. Die Liste im Zoo sollte des Weiteren aus Tieren bestehen, nicht aus Objekten

    List<Animal> animals
    
  4. Die Vererbung ersetzt dann wunderbar den unschönen if/else Block.

    Alt
    if (animal instanceof Dog) {
        ...
    } else if (animal instanceof Cat) {
        ...
    }
    
    Neu
    animal.makeSound();
    
 1import java.util.ArrayList;
 2import java.util.List;
 3
 4interface Animal {
 5    void makeSound();
 6}
 7
 8class Dog implements Animal {
 9    @Override
10    public void makeSound() {
11        System.out.println("Woof");
12    }
13}
14
15class Cat implements Animal {
16    @Override
17    public void makeSound() {
18        System.out.println("Meow");
19    }
20}
21
22class Cow implements Animal {
23    @Override
24    public void makeSound() {
25        System.out.println("Moo");
26    }
27}
28
29class Zoo {
30    public void makeAllSounds(List<Animal> animals) {
31        for (Animal animal : animals) {
32            animal.makeSound();
33        }
34    }
35}
36
37public class Main {
38    public static void main(String[] args) {
39        List<Animal> animals = new ArrayList<>();
40
41        animals.add(new Dog());
42        animals.add(new Cat());
43        animals.add(new Cow());
44
45        Zoo zoo = new Zoo();
46        zoo.makeAllSounds(animals);
47    }
48}

Aufgabe 2

Dieser Code soll überarbeitet werden, damit die Wiederholungen verschwinden.

 1class EmailNotification {
 2    private String recipient;
 3
 4    public EmailNotification(String recipient) {
 5        this.recipient = recipient;
 6    }
 7
 8    public void send(String message) {
 9        if (recipient == null || recipient.isBlank()) {
10            throw new IllegalArgumentException("Recipient is missing");
11        }
12
13        System.out.println("Preparing notification...");
14        System.out.println("Sending EMAIL to " + recipient + ": " + message);
15        System.out.println("Notification sent.");
16    }
17}
18
19class SmsNotification {
20    private String recipient;
21
22    public SmsNotification(String recipient) {
23        this.recipient = recipient;
24    }
25
26    public void send(String message) {
27        if (recipient == null || recipient.isBlank()) {
28            throw new IllegalArgumentException("Recipient is missing");
29        }
30
31        System.out.println("Preparing notification...");
32        System.out.println("Sending SMS to " + recipient + ": " + message);
33        System.out.println("Notification sent.");
34    }
35}
36
37public class Main {
38    public static void main(String[] args) {
39        EmailNotification email = new EmailNotification("alice@example.com");
40        SmsNotification sms = new SmsNotification("+49123456789");
41
42        email.send("Your package was shipped.");
43        sms.send("Your code is 123456.");
44    }
45}

Bemerkung

Sollte das mit einer abstrakten Klasse oder einem Interface gelöst werden?

Lösung anzeigen
  1. Welche Wiederholungen haben wir?

    private String recipient;
    

    if (recipient == null || recipient.isBlank()) {
        throw new IllegalArgumentException("Recipient is missing");
    }
    

    System.out.println("Preparing notification...");
    System.out.println("Notification sent.");
    

    System.out.println("Sending EMAIL/SMS/PUSH ...");
    
  2. Warum eine abstrakte Klasse und kein Interface

    • Variablen

    • Konstruktor Logik

    • geteilte Methoden

    private final String recipient;
    

    public final void send(String message)
    
  3. Warum ist send() final?

    Jede Benachrichtigung sollte

    1. Den Empfänger validieren

    2. Die Vorbereitungsnachricht ausgeben

    3. Die Benachrichtigung versenden

    4. Ausgeben, ob die Operation erfolgreich war

    Damit jede Klasse eine doSend() Methode hat, deklariert man diese abstrakt.

    protected abstract void doSend(String message);
    
  4. Warum super()

    Der Empfänger ist nicht Benachrichtigungsabhängig sondern steht in der Elternklasse, damit er für alle Benachrichtigungen verwendet werden kann.

    public EmailNotification(String recipient) {
        super(recipient);
    }
    

Aufgabe 3

 1class Checkout {
 2    public void pay(String paymentType, double amount) {
 3        if (amount <= 0) {
 4            System.out.println("Invalid amount");
 5            return;
 6        }
 7
 8        if (paymentType.equals("paypal")) {
 9            System.out.println("Paying " + amount + " using PayPal");
10        } else if (paymentType.equals("creditcard")) {
11            System.out.println("Paying " + amount + " using credit card");
12        } else {
13            System.out.println("Unknown payment type");
14        }
15    }
16}
17
18public class Main {
19    public static void main(String[] args) {
20        Checkout checkout = new Checkout();
21
22        checkout.pay("paypal", 29.99);
23        checkout.pay("creditcard", 99.99);
24        checkout.pay("paypall", 10.00);
25        checkout.pay("paypal", -5.00);
26    }
27}

Wie kann dieser Code verbessert werden?

Anforderungen:
  • Keine statischen Strings wie „paypal“.

  • Ungültige Summen sollten nicht nur eine Fehlermeldung printen.

  • Wenn neue Zahlungsmethoden hinzugefügt werden, sollte der Checkout nicht größer und größer werden.

  • Für die Zahlungsmethoden sollte ein Interface verwendet werden.

  • Für die Zahlungstype sollte ein Enum verwendet werden.

Lösung anzeigen
  1. Warum sind statische Strings schlecht?

    typo
    checkout.pay("paypall", 10.00);
    

    Besser enums verwenden
    checkout.pay(PaymentType.PAYPAL, 10.00);
    

    Das geht nicht
    checkout.pay(PaymentType.PAYPAL, 10.00);
    
  2. Warum ein Interface

    Jede Bezahlungsart sollte über diese Methode verfügen

    void pay(double amount);
    

    Aber jede Zahlungsart macht es auf ihre Art und Weiße.

    interface PaymentMethod {
        PaymentType getType();
    
        void pay(double amount);
    }
    
  3. Warum eine Zahlung-registry

    Ohne eine Registry müsste man den Checkout so schreiben

    if (paymentType == PaymentType.PAYPAL) {
        ...
    } else if (paymentType == PaymentType.CREDIT_CARD) {
        ...
    }
    

    Die Registry separiert diese Logik

    PaymentMethod method = registry.getMethod(paymentType);
    
  4. Warum Exceptions

    Das ist schön in der Konsole aber ansonsten unbrauchbar
    System.out.println("Invalid amount");
    return;
    
    Jetzt kann man die Exception / den Fehler behandeln
    throw new IllegalArgumentException("Amount must be positive");
    
  5. Was passiert, wenn wir eine neue Zahlungsmethode hinzufügen?

    Wir wollen APPLE_PAY hinzufügen

     1class ApplePayPayment implements PaymentMethod {
     2    @Override
     3    public PaymentType getType() {
     4        return PaymentType.APPLE_PAY;
     5    }
     6
     7    @Override
     8    public void pay(double amount) {
     9        System.out.println("Paying " + amount + " using Apple Pay");
    10    }
    11}
    
    Registrieren
    registry.register(new ApplePayPayment());
    

    Und wir müssen den Checkout nicht verändern.

Lösung
  1import java.util.EnumMap;
  2import java.util.Map;
  3
  4enum PaymentType {
  5    PAYPAL,
  6    CREDIT_CARD,
  7    BANK_TRANSFER
  8}
  9
 10interface PaymentMethod {
 11    PaymentType getType();
 12
 13    void pay(double amount);
 14}
 15
 16class PayPalPayment implements PaymentMethod {
 17    @Override
 18    public PaymentType getType() {
 19        return PaymentType.PAYPAL;
 20    }
 21
 22    @Override
 23    public void pay(double amount) {
 24        System.out.println("Paying " + amount + " using PayPal");
 25    }
 26}
 27
 28class CreditCardPayment implements PaymentMethod {
 29    @Override
 30    public PaymentType getType() {
 31        return PaymentType.CREDIT_CARD;
 32    }
 33
 34    @Override
 35    public void pay(double amount) {
 36        System.out.println("Paying " + amount + " using credit card");
 37    }
 38}
 39
 40class BankTransferPayment implements PaymentMethod {
 41    @Override
 42    public PaymentType getType() {
 43        return PaymentType.BANK_TRANSFER;
 44    }
 45
 46    @Override
 47    public void pay(double amount) {
 48        System.out.println("Paying " + amount + " using bank transfer");
 49    }
 50}
 51
 52class PaymentRegistry {
 53    private final Map<PaymentType, PaymentMethod> methods = new EnumMap<>(PaymentType.class);
 54
 55    public void register(PaymentMethod method) {
 56        methods.put(method.getType(), method);
 57    }
 58
 59    public PaymentMethod getMethod(PaymentType type) {
 60        PaymentMethod method = methods.get(type);
 61
 62        if (method == null) {
 63            throw new IllegalArgumentException("Unsupported payment type: " + type);
 64        }
 65
 66        return method;
 67    }
 68}
 69
 70class Checkout {
 71    private final PaymentRegistry registry;
 72
 73    public Checkout(PaymentRegistry registry) {
 74        this.registry = registry;
 75    }
 76
 77    public void pay(PaymentType paymentType, double amount) {
 78        if (amount <= 0) {
 79            throw new IllegalArgumentException("Amount must be positive");
 80        }
 81
 82        PaymentMethod method = registry.getMethod(paymentType);
 83        method.pay(amount);
 84    }
 85}
 86
 87public class Main {
 88    public static void main(String[] args) {
 89        PaymentRegistry registry = new PaymentRegistry();
 90
 91        registry.register(new PayPalPayment());
 92        registry.register(new CreditCardPayment());
 93        registry.register(new BankTransferPayment());
 94
 95        Checkout checkout = new Checkout(registry);
 96
 97        checkout.pay(PaymentType.PAYPAL, 29.99);
 98        checkout.pay(PaymentType.CREDIT_CARD, 99.99);
 99        checkout.pay(PaymentType.BANK_TRANSFER, 149.99);
100    }
101}

Aufgabe 4

 1import java.util.ArrayList;
 2import java.util.List;
 3
 4class User {
 5    private final int id;
 6    private final String name;
 7
 8    public User(int id, String name) {
 9        this.id = id;
10        this.name = name;
11    }
12
13    public int getId() {
14        return id;
15    }
16
17    public String toString() {
18        return "User{id=" + id + ", name='" + name + "'}";
19    }
20}
21
22class Product {
23    private final int id;
24    private final String title;
25
26    public Product(int id, String title) {
27        this.id = id;
28        this.title = title;
29    }
30
31    public int getId() {
32        return id;
33    }
34
35    public String toString() {
36        return "Product{id=" + id + ", title='" + title + "'}";
37    }
38}
39
40class Repository {
41    private final List<Object> items = new ArrayList<>();
42
43    public void save(Object item) {
44        items.add(item);
45    }
46
47    public Object findById(int id) {
48        for (Object item : items) {
49            if (item instanceof User) {
50                User user = (User) item;
51
52                if (user.getId() == id) {
53                    return user;
54                }
55            }
56
57            if (item instanceof Product) {
58                Product product = (Product) item;
59
60                if (product.getId() == id) {
61                    return product;
62                }
63            }
64        }
65
66        return null;
67    }
68}
69
70public class Main {
71    public static void main(String[] args) {
72        Repository repository = new Repository();
73
74        repository.save(new User(1, "Alice"));
75        repository.save(new Product(2, "Keyboard"));
76
77        User user = (User) repository.findById(1);
78        Product product = (Product) repository.findById(2);
79
80        System.out.println(user);
81        System.out.println(product);
82    }
83}

Wie kann dieser Code verbessert werden?

Anforderungen:
  • Kein List<Object>

  • Keine casts in main

  • Keine instanceof in Repository

  • Das Repository sollte für Nutzer, Produkte und alle zukünftigen Klassen mit einer ID funktionieren.

  • Verwende Generics

Lösung anzeigen
  1. Was ist das Problem wenn man Object verwendet?

    private final List<Object> items = new ArrayList<>();
    

    Einmal als Object gespeichert vergisst Java was es tatsächlich ist also müsste man casten

    User user = (User) repository.findById(1);
    

    Und hier wird es zum problem

    Product product = (Product) repository.findById(1);
    
  2. Was haben Nutzer und Produkte gemeinsam?

    Beide haben den getter getId()

    public int getId()
    

    Also brauchen wir ein Interface

    interface Identifiable {
        int getId();
    }
    
  3. Warum brauchen wir einen Typparameter <T> für das Repository?

    class Repository<T extends Identifiable>
    

    Der Typparameter <T> ist ein Platzhalter für einen konkreten Typ z.b.

    Repository<User>    // hier wird das <T> durch den User ersetzt
    Repository<Product> // hier wird das <T> durch das Product ersetzt
    

    Also wird aus

    private final List<T> items = new ArrayList<>();
    

    private final List<User> items = new ArrayList<>();
    private final List<Product> items = new ArrayList<>();
    
  4. Warum T extends Identifiable?

    T extends Identifiable bedeutet, dass <T> ein belieber Typ sein kann, aber er muss Identifiable implementieren.

  5. Warum zwei Repositories?

    Repository<User> userRepository = new Repository<>();
    Repository<Product> productRepository = new Repository<>();
    

    Das Nutzer-Repository kann lediglich Nutzer speichern
    userRepository.save(new User(1, "Alice"));
    
    Das Produkt-Repository kann lediglich Produkte speichern
    userRepository.save(new Product(1, "Keyboard"));
    
 1import java.util.ArrayList;
 2import java.util.List;
 3
 4interface Identifiable {
 5    int getId();
 6}
 7
 8class User implements Identifiable {
 9    private final int id;
10    private final String name;
11
12    public User(int id, String name) {
13        this.id = id;
14        this.name = name;
15    }
16
17    @Override
18    public int getId() {
19        return id;
20    }
21
22    @Override
23    public String toString() {
24        return "User{id=" + id + ", name='" + name + "'}";
25    }
26}
27
28class Product implements Identifiable {
29    private final int id;
30    private final String title;
31
32    public Product(int id, String title) {
33        this.id = id;
34        this.title = title;
35    }
36
37    @Override
38    public int getId() {
39        return id;
40    }
41
42    @Override
43    public String toString() {
44        return "Product{id=" + id + ", title='" + title + "'}";
45    }
46}
47
48class Repository<T extends Identifiable> {
49    private final List<T> items = new ArrayList<>();
50
51    public void save(T item) {
52        items.add(item);
53    }
54
55    public T findById(int id) {
56        for (T item : items) {
57            if (item.getId() == id) {
58                return item;
59            }
60        }
61
62        return null;
63    }
64
65    public List<T> findAll() {
66        return new ArrayList<>(items);
67    }
68}
69
70public class Main {
71    public static void main(String[] args) {
72        Repository<User> userRepository = new Repository<>();
73        Repository<Product> productRepository = new Repository<>();
74
75        userRepository.save(new User(1, "Alice"));
76        userRepository.save(new User(2, "Bob"));
77
78        productRepository.save(new Product(1, "Keyboard"));
79        productRepository.save(new Product(2, "Mouse"));
80
81        User user = userRepository.findById(1);
82        Product product = productRepository.findById(2);
83
84        System.out.println(user);
85        System.out.println(product);
86    }
87}
  1. Abschließende Frage:

    Compiliert das?
    Repository<User> userRepository = new Repository<>();
    
    userRepository.save(new Product(1, "Keyboard"));