Design Patterns & SOLID Principles

Ghid complet cu exemple interactive în Java

🔒 Singleton Pattern

Singleton Pattern este un design pattern creațional care asigură că o clasă are o singură instanță și oferă un punct global de acces la aceasta. Exemplul tău folosește implementarea Lazy Holder, care este thread-safe și eficientă.

OrdersQueryServiceSingleton.java
public class OrdersQueryServiceSingleton {
    // Clasa internă statică - încărcată doar când este accesată
    private static class LazyHolder {
        private static final OrdersQueryService ORDERS_QUERY_SERVICE = 
            new OrdersQueryServiceImpl();
    }
    
    // Metodă publică pentru a obține instanța unică
    public static OrdersQueryService getInstance() {
        return LazyHolder.ORDERS_QUERY_SERVICE;
    }
}

🛡️ Thread-Safe

Inițializarea este garantată thread-safe de JVM

⚡ Lazy Loading

Instanța este creată doar când este necesară

🚀 Performanță

Nu necesită sincronizare explicită

Demo Interactiv - Singleton

Apasă butoanele pentru a vedea cum funcționează!

🏗️ Builder Pattern - Standard & cu Moștenire

Builder Pattern este un design pattern creațional care permite construirea obiectelor complexe pas cu pas. Vom explora două implementări: versiunea clasică și versiunea cu moștenire pentru ierarhii complexe de clase.

📊 Builder Standard vs Builder cu Moștenire

Builder Standard
  • ✅ Simplu de implementat
  • ✅ Perfect pentru clase simple
  • ❌ Dificil de extins
  • ❌ Nu suportă ierarhii
VS
Builder cu Moștenire
  • ✅ Suportă ierarhii complexe
  • ✅ Type-safe method chaining
  • ✅ Ușor de extins
  • ⚠️ Mai complex de implementat
Order.java - Builder Pattern Standard
public class Order {
    private final String orderId;
    private final String customerName;
    private final String product;
    private final int quantity;
    private final double price;
    private final String shippingAddress;
    
    // Constructor privat - accesibil doar prin Builder
    private Order(Builder builder) {
        this.orderId = builder.orderId;
        this.customerName = builder.customerName;
        this.product = builder.product;
        this.quantity = builder.quantity;
        this.price = builder.price;
        this.shippingAddress = builder.shippingAddress;
    }
    
    // Clasa Builder internă
    public static class Builder {
        private String orderId;
        private String customerName;
        private String product;
        private int quantity = 1;
        private double price;
        private String shippingAddress;
        
        public Builder orderId(String orderId) {
            this.orderId = orderId;
            return this;
        }
        
        public Builder customerName(String customerName) {
            this.customerName = customerName;
            return this;
        }
        
        public Builder product(String product) {
            this.product = product;
            return this;
        }
        
        public Order build() {
            // Validări
            if (orderId == null || customerName == null) {
                throw new IllegalStateException(
                    "Order ID și Customer Name sunt obligatorii!"
                );
            }
            return new Order(this);
        }
    }
}
Product.java - Builder cu Moștenire (Generic Self-Referencing)
// Clasa de bază abstractă
public abstract class Product {
    protected final String id;
    protected final String name;
    protected final double price;
    protected final String description;
    
    protected Product(Builder<?> builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.price = builder.price;
        this.description = builder.description;
    }
    
    // Generic Builder base class
    public abstract static class Builder<T extends Builder<T>> {
        protected String id;
        protected String name;
        protected double price;
        protected String description;
        
        // Abstract method pentru type-safe chaining
        protected abstract T self();
        
        public T id(String id) {
            this.id = id;
            return self();
        }
        
        public T name(String name) {
            this.name = name;
            return self();
        }
        
        public T price(double price) {
            this.price = price;
            return self();
        }
        
        public abstract Product build();
    }
}

// Produs fizic cu proprietăți specifice
class PhysicalProduct extends Product {
    private final double weight;
    private final boolean fragile;
    
    private PhysicalProduct(Builder builder) {
        super(builder);
        this.weight = builder.weight;
        this.fragile = builder.fragile;
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder extends Product.Builder<Builder> {
        private double weight;
        private boolean fragile;
        
        @Override
        protected Builder self() {
            return this;
        }
        
        public Builder weight(double weight) {
            this.weight = weight;
            return this;
        }
        
        public Builder fragile(boolean fragile) {
            this.fragile = fragile;
            return this;
        }
        
        @Override
        public PhysicalProduct build() {
            return new PhysicalProduct(this);
        }
    }
}

// Produs digital cu proprietăți specifice
class DigitalProduct extends Product {
    private final String downloadUrl;
    private final String licenseType;
    
    private DigitalProduct(Builder builder) {
        super(builder);
        this.downloadUrl = builder.downloadUrl;
        this.licenseType = builder.licenseType;
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder extends Product.Builder<Builder> {
        private String downloadUrl;
        private String licenseType = "SINGLE_USER";
        
        @Override
        protected Builder self() {
            return this;
        }
        
        public Builder downloadUrl(String url) {
            this.downloadUrl = url;
            return this;
        }
        
        public Builder licenseType(String type) {
            this.licenseType = type;
            return this;
        }
        
        @Override
        public DigitalProduct build() {
            return new DigitalProduct(this);
        }
    }
}

🔗 Avantajele Builder-ului cu Moștenire:

  • Type Safety: Method chaining cu tipuri corecte
  • Extensibilitate: Ușor de adăugat noi tipuri de produse
  • Code Reuse: Logica comună în clasa de bază
  • Polimorfism: Toate produsele pot fi tratate uniform

📦 Builder Standard - Comandă

📱 Builder Moștenire - Produs Fizic

💾 Builder Moștenire - Produs Digital

🎯 Rezultatele construirii vor apărea aici...

Testează diferite tipuri de builder-e pentru a vedea diferențele!

SOLID Principles

Cele 5 principii fundamentale ale programării orientate pe obiecte

S
O
L
I
D

S - Single Responsibility Principle (SRP)

O clasă ar trebui să aibă un singur motiv pentru a se schimba

Fiecare clasă ar trebui să aibă o singură responsabilitate și această responsabilitate ar trebui să fie complet încapsulată în clasă.

❌ Exemplu care încalcă SRP:

Employee.java - Prea multe responsabilități
public class Employee {
    private String name;
    private double salary;
    
    // Responsabilitate 1: Logică de business pentru angajat
    public void calculateSalary() {
        // calcul salariu
    }
    
    // Responsabilitate 2: Persistență în baza de date
    public void saveToDatabase() {
        // salvare în DB
    }
    
    // Responsabilitate 3: Generare rapoarte
    public void generateReport() {
        // generare PDF
    }
}

✅ Soluție care respectă SRP:

Clase separate pentru fiecare responsabilitate
// Clasa pentru logica de business
public class Employee {
    private String name;
    private double salary;
    
    // Doar metode legate de angajat
    public double calculateSalary() {
        return salary * 1.2; // exemplu calcul
    }
}

// Clasa separată pentru persistență
public class EmployeeRepository {
    public void save(Employee employee) {
        // logica de salvare
    }
}

// Clasa separată pentru rapoarte
public class EmployeeReportGenerator {
    public void generateReport(Employee employee) {
        // logica de generare raport
    }
}

Demo SRP

O - Open/Closed Principle (OCP)

Clasele ar trebui să fie deschise pentru extensie, dar închise pentru modificare

Ar trebui să putem extinde comportamentul unei clase fără a modifica codul sursă existent.

❌ Exemplu care încalcă OCP:

DiscountCalculator.java - Necesită modificare pentru fiecare tip nou
public class DiscountCalculator {
    public double calculateDiscount(Order order, String customerType) {
        if (customerType.equals("REGULAR")) {
            return order.getAmount() * 0.05;
        } else if (customerType.equals("SILVER")) {
            return order.getAmount() * 0.10;
        } else if (customerType.equals("GOLD")) {
            return order.getAmount() * 0.20;
        }
        // Pentru fiecare tip nou, trebuie să modificăm această metodă!
        return 0;
    }
}

✅ Soluție care respectă OCP:

Folosind Strategy Pattern și interfețe
// Interfață pentru strategii de discount
public interface DiscountStrategy {
    double calculateDiscount(Order order);
}

// Implementări concrete
public class RegularCustomerDiscount implements DiscountStrategy {
    public double calculateDiscount(Order order) {
        return order.getAmount() * 0.05;
    }
}

public class SilverCustomerDiscount implements DiscountStrategy {
    public double calculateDiscount(Order order) {
        return order.getAmount() * 0.10;
    }
}

public class PlatinumCustomerDiscount implements DiscountStrategy {
    public double calculateDiscount(Order order) {
        return order.getAmount() * 0.25; // Nou tip adăugat fără modificări!
    }
}

// Calculator care nu necesită modificare pentru tipuri noi
public class DiscountCalculator {
    public double calculateDiscount(Order order, DiscountStrategy strategy) {
        return strategy.calculateDiscount(order);
    }
}

Demo OCP - Calculator de Discount

L - Liskov Substitution Principle (LSP)

Obiectele unei clase derivate trebuie să poată înlocui obiectele clasei de bază fără a afecta corectitudinea programului

Subtipurile trebuie să fie substituibile pentru tipurile lor de bază. Dacă S este un subtip al lui T, atunci obiectele de tip T pot fi înlocuite cu obiecte de tip S fără a altera proprietățile programului.

❌ Exemplu care încalcă LSP:

Rectangle și Square - Exemplu clasic
public class Rectangle {
    protected int width;
    protected int height;
    
    public void setWidth(int width) {
        this.width = width;
    }
    
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int getArea() {
        return width * height;
    }
}

// Pătratul încalcă LSP!
public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // Forțăm lățimea = înălțimea
    }
    
    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height; // Forțăm lățimea = înălțimea
    }
}

✅ Soluție care respectă LSP:

Folosind o ierarhie corectă
// Interfață comună
public interface Shape {
    int getArea();
}

// Rectangle independent
public class Rectangle implements Shape {
    private int width;
    private int height;
    
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    
    public int getArea() {
        return width * height;
    }
}

// Square independent
public class Square implements Shape {
    private int side;
    
    public Square(int side) {
        this.side = side;
    }
    
    public int getArea() {
        return side * side;
    }
}

Demo LSP

I - Interface Segregation Principle (ISP)

Clienții nu ar trebui să fie forțați să depindă de interfețe pe care nu le folosesc

Este mai bine să avem mai multe interfețe specifice decât o singură interfață generală. Interfețele mari ar trebui împărțite în interfețe mai mici și mai specifice.

❌ Exemplu care încalcă ISP:

Worker.java - Interfață prea mare
public interface Worker {
    void work();
    void eat();
    void sleep();
    void receiveSalary();
}

// Un robot nu mănâncă sau doarme!
public class RobotWorker implements Worker {
    public void work() {
        // Robot lucrează
    }
    
    public void eat() {
        throw new UnsupportedOperationException("Roboții nu mănâncă!");
    }
    
    public void sleep() {
        throw new UnsupportedOperationException("Roboții nu dorm!");
    }
    
    public void receiveSalary() {
        // Robot primește întreținere
    }
}

✅ Soluție care respectă ISP:

Interfețe segregate
// Interfețe specifice
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

public interface Payable {
    void receiveSalary();
}

// Angajat uman implementează toate interfețele necesare
public class HumanWorker implements Workable, Eatable, Sleepable, Payable {
    public void work() {
        System.out.println("Angajatul lucrează");
    }
    
    public void eat() {
        System.out.println("Angajatul mănâncă");
    }
    
    public void sleep() {
        System.out.println("Angajatul doarme");
    }
    
    public void receiveSalary() {
        System.out.println("Angajatul primește salariu");
    }
}

// Robot implementează doar interfețele relevante
public class RobotWorker implements Workable, Payable {
    public void work() {
        System.out.println("Robotul lucrează non-stop");
    }
    
    public void receiveSalary() {
        System.out.println("Robotul primește întreținere");
    }
}

Demo ISP - Tipuri de Angajați

D - Dependency Inversion Principle (DIP)

Modulele de nivel înalt nu ar trebui să depindă de module de nivel jos. Ambele ar trebui să depindă de abstracții

Abstracțiile nu ar trebui să depindă de detalii. Detaliile ar trebui să depindă de abstracții. Acest principiu promovează loose coupling între componente.

❌ Exemplu care încalcă DIP:

EmailService.java - Dependență directă
public class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending email: " + message);
    }
}

// Clasa de nivel înalt depinde direct de implementare
public class OrderService {
    private EmailService emailService = new EmailService();
    
    public void processOrder(Order order) {
        // Procesare comandă
        emailService.sendEmail("Order processed: " + order.getId());
    }
}

✅ Soluție care respectă DIP:

Folosind abstracții și dependency injection
// Abstracție pentru notificări
public interface NotificationService {
    void sendNotification(String message);
}

// Implementări concrete
public class EmailNotificationService implements NotificationService {
    public void sendNotification(String message) {
        System.out.println("Email: " + message);
    }
}

public class SMSNotificationService implements NotificationService {
    public void sendNotification(String message) {
        System.out.println("SMS: " + message);
    }
}

public class PushNotificationService implements NotificationService {
    public void sendNotification(String message) {
        System.out.println("Push: " + message);
    }
}

// Clasa de nivel înalt depinde de abstracție
public class OrderService {
    private final NotificationService notificationService;
    
    // Dependency injection prin constructor
    public OrderService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
    
    public void processOrder(Order order) {
        // Procesare comandă
        notificationService.sendNotification("Order processed: " + order.getId());
    }
}

Demo DIP - Sistem de Notificări