Els principis SOLID són un conjunt de cinc principis de disseny de programari que ajuden a crear sistemes més comprensibles, flexibles i mantenibles. Aquests principis van ser introduïts per Robert C. Martin (també conegut com Uncle Bob) i són fonamentals per a qualsevol desenvolupador de programari que vulgui dissenyar arquitectures robustes i escalables.

S: Principi de Responsabilitat Única (Single Responsibility Principle - SRP)

Explicació

Cada classe ha de tenir una única responsabilitat o motiu per canviar. Això significa que una classe ha de tenir només una raó per ser modificada.

Exemple

// Incorrecte: La classe Report té més d'una responsabilitat
public class Report {
    public void generateReport() {
        // Generar el report
    }

    public void saveToFile() {
        // Guardar el report en un fitxer
    }
}

// Correcte: Separar les responsabilitats en diferents classes
public class ReportGenerator {
    public void generateReport() {
        // Generar el report
    }
}

public class ReportSaver {
    public void saveToFile() {
        // Guardar el report en un fitxer
    }
}

Exercici

Refactoritza la següent classe per seguir el principi de responsabilitat única:

public class User {
    public void login(String username, String password) {
        // Lògica de login
    }

    public void register(String username, String password, String email) {
        // Lògica de registre
    }

    public void sendEmail(String email, String message) {
        // Lògica per enviar correu electrònic
    }
}

O: Principi de Obert/Cerrat (Open/Closed Principle - OCP)

Explicació

Les entitats de programari (classes, mòduls, funcions, etc.) han d'estar obertes per a l'extensió, però tancades per a la modificació. Això significa que hauríem de poder afegir noves funcionalitats sense canviar el codi existent.

Exemple

// Incorrecte: Afegir una nova forma requereix modificar la classe
public class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.radius * circle.radius;
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.length * rectangle.width;
        }
        return 0;
    }
}

// Correcte: Utilitzar polimorfisme per afegir noves formes sense modificar la classe existent
public interface Shape {
    double calculateArea();
}

public class Circle implements Shape {
    public double radius;

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

public class Rectangle implements Shape {
    public double length;
    public double width;

    @Override
    public double calculateArea() {
        return length * width;
    }
}

public class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();
    }
}

Exercici

Refactoritza la següent classe per seguir el principi de obert/cerrat:

public class DiscountCalculator {
    public double calculateDiscount(String customerType, double amount) {
        if (customerType.equals("Regular")) {
            return amount * 0.1;
        } else if (customerType.equals("Premium")) {
            return amount * 0.2;
        }
        return 0;
    }
}

L: Principi de Substitució de Liskov (Liskov Substitution Principle - LSP)

Explicació

Els objectes d'una classe derivada han de poder substituir els objectes de la seva classe base sense alterar les propietats del programa (correcció, tasques, etc.).

Exemple

// Incorrecte: La classe Square no es comporta com un Rectangle
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;
    }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width;
    }

    @Override
    public void setHeight(int height) {
        this.height = height;
        this.width = height;
    }
}

// Correcte: Separar les classes per respectar el principi de substitució de Liskov
public abstract class Shape {
    public abstract int getArea();
}

public class Rectangle extends Shape {
    protected int width;
    protected int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public int getArea() {
        return width * height;
    }
}

public class Square extends Shape {
    private int side;

    public void setSide(int side) {
        this.side = side;
    }

    @Override
    public int getArea() {
        return side * side;
    }
}

Exercici

Refactoritza la següent jerarquia de classes per seguir el principi de substitució de Liskov:

public class Bird {
    public void fly() {
        // Lògica per volar
    }
}

public class Ostrich extends Bird {
    @Override
    public void fly() {
        // Les estruços no poden volar
        throw new UnsupportedOperationException();
    }
}

I: Principi de Segregació d'Interfícies (Interface Segregation Principle - ISP)

Explicació

Els clients no haurien de dependre de mètodes que no utilitzen. Això significa que és millor tenir diverses interfícies específiques que una interfície general.

Exemple

// Incorrecte: La interfície Worker té mètodes que no són necessaris per a tots els treballadors
public interface Worker {
    void work();
    void eat();
}

public class HumanWorker implements Worker {
    @Override
    public void work() {
        // Lògica de treball
    }

    @Override
    public void eat() {
        // Lògica per menjar
    }
}

public class RobotWorker implements Worker {
    @Override
    public void work() {
        // Lògica de treball
    }

    @Override
    public void eat() {
        // Els robots no mengen
        throw new UnsupportedOperationException();
    }
}

// Correcte: Separar les interfícies per respectar el principi de segregació d'interfícies
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public class HumanWorker implements Workable, Eatable {
    @Override
    public void work() {
        // Lògica de treball
    }

    @Override
    public void eat() {
        // Lògica per menjar
    }
}

public class RobotWorker implements Workable {
    @Override
    public void work() {
        // Lògica de treball
    }
}

Exercici

Refactoritza la següent interfície per seguir el principi de segregació d'interfícies:

public interface Printer {
    void printDocument(String document);
    void scanDocument(String document);
    void faxDocument(String document);
}

D: Principi d'Inversió de Dependència (Dependency Inversion Principle - DIP)

Explicació

Els mòduls d'alt nivell no haurien de dependre de mòduls de baix nivell. Ambdós haurien de dependre d'abstraccions. Les abstraccions no haurien de dependre de detalls. Els detalls haurien de dependre d'abstraccions.

Exemple

// Incorrecte: La classe de baix nivell està directament instanciada a la classe de nivell alt
public class LightBulb {
    public void turnOn() {
        // Encendre la bombeta
    }

    public void turnOff() {
        // Apagar la bombeta
    }
}

public class Switch {
    private LightBulb lightBulb;

    public Switch() {
        this.lightBulb = new LightBulb();
    }

    public void operate() {
        // Operar l'interruptor
    }
}

// Correcte: Utilitzar una interfície per invertir la dependència
public interface Switchable {
    void turnOn();
    void turnOff();
}

public class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        // Encendre la bombeta
    }

    @Override
    public void turnOff() {
        // Apagar la bombeta
    }
}

public class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate() {
        // Operar l'interruptor
    }
}

Exercici

Refactoritza la següent classe per seguir el principi d'inversió de dependència:

public class Database {
    public void connect() {
        // Connectar a la base de dades
    }
}

public class Application {
    private Database database;

    public Application() {
        this.database = new Database();
    }

    public void start() {
        database.connect();
    }
}

Conclusió

Els principis SOLID són fonamentals per al disseny de programari de qualitat. Aquests principis ajuden a crear sistemes que són més fàcils de mantenir, escalar i entendre. En aplicar aquests principis, els desenvolupadors poden evitar molts dels problemes comuns que sorgeixen en el desenvolupament de programari, com ara el codi duplicat, les dependències rígides i les dificultats per afegir noves funcionalitats.

Arquitectures de Sistemes: Principis i Pràctiques per Dissenyar Arquitectures Tecnològiques Robustes i Escalables

Mòdul 1: Introducció a les Arquitectures de Sistemes

Mòdul 2: Principis de Disseny d'Arquitectures

Mòdul 3: Components d'una Arquitectura de Sistemes

Mòdul 4: Escalabilitat i Rendiment

Mòdul 5: Seguretat en Arquitectures de Sistemes

Mòdul 6: Eines i Tecnologies

Mòdul 7: Casos d'Estudi i Exemples Pràctics

Mòdul 8: Tendències i Futur de les Arquitectures de Sistemes

© Copyright 2024. Tots els drets reservats