Praktik Mandiri 9: Design Patterns

1. Singleton Pattern

Tugas: Buat kelas ConfigurationManager yang memastikan hanya ada satu instance yang bisa diakses global.

  • Simpan konfigurasi seperti appName dan version.
  • Pastikan thread-safe.

Contoh Penggunaan:

ConfigurationManager config = ConfigurationManager.getInstance();
config.setAppName("MyApp");
System.out.println(config.getAppName()); // Output: MyApp

<details> <summary>Solusi</summary>

public class ConfigurationManager {
    private static volatile ConfigurationManager instance;
    private String appName;
    private String version;

    private ConfigurationManager() {}

    public static ConfigurationManager getInstance() {
        if (instance == null) {
            synchronized (ConfigurationManager.class) {
                if (instance == null) {
                    instance = new ConfigurationManager();
                }
            }
        }
        return instance;
    }

    // Getter & Setter
}

</details>

2. Factory Method Pattern

Tugas: Buat factory untuk menghasilkan objek pembayaran (CreditCardPaymentPayPalPayment) berdasarkan jenis.

PaymentProcessor creditCard = PaymentFactory.createPayment("creditcard");
creditCard.process(100.0); // Output: "Processing Credit Card payment: $100.0"

<details> <summary>Solusi</summary>

interface PaymentProcessor {
    void process(double amount);
}

class CreditCardPayment implements PaymentProcessor {
    @Override
    public void process(double amount) {
        System.out.println("Processing Credit Card payment: $" + amount);
    }
}

class PaymentFactory {
    public static PaymentProcessor createPayment(String type) {
        return switch (type.toLowerCase()) {
            case "creditcard" -> new CreditCardPayment();
            case "paypal" -> new PayPalPayment();
            default -> throw new IllegalArgumentException("Invalid payment type");
        };
    }
}

</details>

3. Observer Pattern

Tugas: Implementasikan sistem notifikasi di mana NewsAgency mengirim berita ke Subscriber (contoh: EmailSubscriber, SMSSubscriber).

NewsAgency agency = new NewsAgency();
agency.addSubscriber(new EmailSubscriber("user@example.com"));
agency.publishNews("Java 21 Released!"); 
// Output: "Email to user@example.com: Java 21 Released!"

<details> <summary>Solusi</summary>

interface Subscriber {
    void update(String news);
}

class NewsAgency {
    private List<Subscriber> subscribers = new ArrayList<>();

    public void addSubscriber(Subscriber s) {
        subscribers.add(s);
    }

    public void publishNews(String news) {
        subscribers.forEach(s -> s.update(news));
    }
}

class EmailSubscriber implements Subscriber {
    private String email;
    
    public EmailSubscriber(String email) {
        this.email = email;
    }

    @Override
    public void update(String news) {
        System.out.println("Email to " + email + ": " + news);
    }
}

</details>

4. Decorator Pattern

Tugas: Tambahkan fitur tambahan ke objek Coffee (contoh: MilkDecoratorSugarDecorator).

Coffee coffee = new BasicCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription()); // Output: "Basic Coffee, Milk, Sugar"
System.out.println(coffee.cost()); // Output: 3.5 (2.0 + 1.0 + 0.5)

<details> <summary>Solusi</summary>

interface Coffee {
    String getDescription();
    double cost();
}

class BasicCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Basic Coffee";
    }

    @Override
    public double cost() {
        return 2.0;
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Milk";
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost() + 1.0;
    }
}

</details>

5. Strategy Pattern

Tugas: Implementasikan algoritma sorting yang berbeda (BubbleSortQuickSort) dan pilih strategi secara dinamis.

SortingContext context = new SortingContext(new BubbleSort());
context.sort(Arrays.asList(3, 1, 4)); // Output: "Sorting using BubbleSort"

context.setStrategy(new QuickSort());
context.sort(Arrays.asList(5, 2, 9)); // Output: "Sorting using QuickSort"

<details> <summary>Solusi</summary>

interface SortingStrategy {
    void sort(List<Integer> list);
}

class BubbleSort implements SortingStrategy {
    @Override
    public void sort(List<Integer> list) {
        System.out.println("Sorting using BubbleSort");
        // Implementasi bubble sort
    }
}

class SortingContext {
    private SortingStrategy strategy;

    public SortingContext(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(List<Integer> list) {
        strategy.sort(list);
    }
}

</details>


6. Adapter Pattern

Tugas: Buat adapter untuk mengonversi antarmuka LegacyUser ke ModernUser.

LegacyUser legacyUser = new LegacyUser("john_doe", "John Doe");
ModernUser modernUser = new UserAdapter(legacyUser);
System.out.println(modernUser.getFullName()); // Output: "John Doe"

<details> <summary>Solusi</summary>

class LegacyUser {
    private String username;
    private String fullName;

    public LegacyUser(String username, String fullName) {
        this.username = username;
        this.fullName = fullName;
    }

    public String getUsername() { return username; }
    public String getFullName() { return fullName; }
}

interface ModernUser {
    String getFullName();
}

class UserAdapter implements ModernUser {
    private LegacyUser legacyUser;

    public UserAdapter(LegacyUser legacyUser) {
        this.legacyUser = legacyUser;
    }

    @Override
    public String getFullName() {
        return legacyUser.getFullName();
    }
}

</details>


Tantangan: MVC (Model-View-Controller)

Tugas: Implementasikan pola MVC sederhana untuk aplikasi to-do list:

  • Model: Menyimpan daftar tugas.
  • View: Menampilkan tugas ke konsol.
  • Controller: Menangani input pengguna (tambah/hapus tugas).

Contoh Output:

Tasks:
1. Belajar Java
2. Olahraga

<details> <summary>Hint</summary>

  • Gunakan Observer Pattern untuk update view saat model berubah.
  • Gunakan Command Pattern untuk operasi tambah/hapus tugas.

</details>

Tips Belajar:

  1. Pahami Kategori Design Patterns:
    • Creational (Singleton, Factory, Builder)
    • Structural (Adapter, Decorator, Facade)
    • Behavioral (Observer, Strategy, Command)
  2. Identifikasi Masalah yang Cocok:
    Contoh:
    • Butuh satu instance? → Singleton
    • Inisialisasi objek kompleks? → Builder
    • Perubahan perilaku runtime? → Strategy
  3. Baca Buku “Design Patterns: Elements of Reusable Object-Oriented Software” (Gang of Four).
  4. Latihan Refactoring: Ambil kode yang sudah ada dan coba terapkan pola yang sesuai.
  5. Buat Diagram UML untuk memvisualisasikan hubungan antar kelas.

Semangat! 😊 Coba implementasikan solusi sendiri sebelum melihat contoh, lalu bandingkan hasilnya.