DEV Community

Cover image for Aplicando o "Open/Closed Principle" com Typescript e Java
Victor Lima Reboredo
Victor Lima Reboredo

Posted on

Aplicando o "Open/Closed Principle" com Typescript e Java

Conceitos

Abstração

A Abstração nos conceitos da orientação a objeto é uma prática de definir apenas aspectos essenciais que uma classe deve possuir. As classes, devem por natureza, ser incompletas e imprecisas para que possamos modelar especificidades através de classes filhas. Assim surge o conceito de classes filhas, classes mães e herança.

Herança

Herança é a representação de relacionamento entre classes em que uma classe extende a outra de modo a herdar os comportamentos da classe mãe.

SOLID

SOLID é um acrônimo que representa cinco princípios fundamentais da programação orientada a objetos, propostos por Robert C. Martin - o uncle Bob. Aqui você pode ler mais sobre o artigo dele.
Esses princípios têm como objetivo melhorar a estrutura e a manutenção do código, tornando-o mais flexível, escalável e fácil de entender. Tais princípios auxiliam o programador a criar códigos mais organizados, dividindo responsabilidades, reduzindo dependências, simplificando o processo de refatoração e promovendo a reutilização do código.

Open/Closed Principle

O "O" do acrônimo significa "Open/Closed Principle". A frase que o uncle bob utilizou para definir esse princípio foi:

"Uma classe deve estar aberta para extensão, mas fechada para modificação"

Segundo este princípio, devemos desenvolver uma aplicação garantindo que escreveremos classes ou módulos de maneira genérica de modo que sempre que sentir a necessidade de estender o comportamento da classe ou do objeto, você não precisará alterar a classe propriamente dita. Extensão aqui pode-se ler como adição ou alteração de procedimentos.

O objetivo é permitir a adição de novas funcionalidades sem a necessidade de alterar o código existente. Isso minimiza o risco de introduzir bugs e deixa o código mais manutenível.

Aplicação prática

Imagine que você tem uma classe DiscountCalculator que calcula descontos de produtos. Inicialmente, temos duas categorias de produtos: Electronics e Clothing. Vamos começar sem aplicar o OCP (Open/Closed Principle):

Java

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}

class DiscountCalculator {
    public double calculateDiscount(Product product) {
        if (product.getName().equals("Electronics")) {
            return product.getPrice() * 0.9; // 10% de desconto
        } else if (product.getName().equals("Clothing")) {
            return product.getPrice() * 0.8; // 20% de desconto
        }
        return product.getPrice();
    }
}

public class Main {
    public static void main(String[] args) {
        Product electronics = new Product("Electronics", 100);
        Product clothing = new Product("Clothing", 50);

        DiscountCalculator calculator = new DiscountCalculator();

        System.out.println(calculator.calculateDiscount(electronics)); // 90
        System.out.println(calculator.calculateDiscount(clothing)); // 40
    }
}
Enter fullscreen mode Exit fullscreen mode

Typescript

class Product {
    private _name: string;
    private _price: number;

    constructor(name: string, price: number) {
        this._name = name;
        this._price = price;
    }

    public get name() { return this.name };

    public set name(value: string) { this.name = value };

    public get price() { return this.price };

    public set price(value: number) { this.price = value };
}

class DiscountCalculator {
    public calculateDiscount(product: Product): number {
        if (product.name === 'Electronics') {
            return product.price * 0.9; // 10% de desconto
        } else if (product.name === 'Clothing') {
            return product.price * 0.8; // 20% de desconto
        }
        return product.price;
    }
}

const electronics = new Product('Electronics', 100);
const clothing = new Product('Clothing', 50);

const calculator = new DiscountCalculator();

console.log(calculator.calculateDiscount(electronics)); // 90
console.log(calculator.calculateDiscount(clothing)); // 40
Enter fullscreen mode Exit fullscreen mode

Problemas ao Não Aplicar o OCP

Violação do encapsulamento: Toda vez que um novo tipo de produto precisar de um desconto diferente, será necessário modificar o método calculateDiscount, incluindo uma nova condicional no if.

Dificuldade em manutenção: Se o método crescer com muitos if/else ou switch, ele se tornará difícil de manter e testar.

Risco de introdução de erros: Alterações no método podem introduzir bugs em outras partes do código que dependem desse método.

Como corrigir?

Agora, vamos aplicar o Open/Closed Principle refatorando o código para permitir a adição de novos tipos de descontos sem modificar o código existente.

Java

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

interface DiscountStrategy {
    double calculate(Product product);
}

class ElectronicsDiscount implements DiscountStrategy {
    @Override
    public double calculate(Product product) {
        return product.getPrice() * 0.9; // 10% de desconto
    }
}

class ClothingDiscount implements DiscountStrategy {
    @Override
    public double calculate(Product product) {
        return product.getPrice() * 0.8; // 20% de desconto
    }
}

class NoDiscount implements DiscountStrategy {
    @Override
    public double calculate(Product product) {
        return product.getPrice();
    }
}

class DiscountCalculator {
    private DiscountStrategy discountStrategy;

    public DiscountCalculator(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public double calculateDiscount(Product product) {
        return discountStrategy.calculate(product);
    }
}

public class Main {
    public static void main(String[] args) {
        Product electronics = new Product("Electronics", 100);
        Product clothing = new Product("Clothing", 50);
        Product books = new Product("Books", 30);

        DiscountCalculator electronicsDiscount = new DiscountCalculator(new ElectronicsDiscount());
        DiscountCalculator clothingDiscount = new DiscountCalculator(new ClothingDiscount());
        DiscountCalculator booksDiscount = new DiscountCalculator(new NoDiscount());

        System.out.println(electronicsDiscount.calculateDiscount(electronics)); // 90
        System.out.println(clothingDiscount.calculateDiscount(clothing)); // 40
        System.out.println(booksDiscount.calculateDiscount(books)); // 30
    }
}
Enter fullscreen mode Exit fullscreen mode

Typescript

class Product {
    private _name: string;
    private _price: number;

    constructor(name: string, price: number) {
        this._name = name;
        this._price = price;
    }

    public get name() { return this.name };

    public set name(value: string) { this.name = value };

    public get price() { return this.price };

    public set price(value: number) { this.price = value };
}

interface DiscountStrategy {
    calculate(product: Product): number;
}

class ElectronicsDiscount implements DiscountStrategy {
    calculate(product: Product): number {
        return product.price * 0.9; // 10% de desconto
    }
}

class ClothingDiscount implements DiscountStrategy {
    calculate(product: Product): number {
        return product.price * 0.8; // 20% de desconto
    }
}

class NoDiscount implements DiscountStrategy {
    calculate(product: Product): number {
        return product.price;
    }
}

class DiscountCalculator {
    private discountStrategy: DiscountStrategy;

    constructor(discountStrategy: DiscountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public calculateDiscount(product: Product): number {
        return this.discountStrategy.calculate(product);
    }
}

const electronics = new Product('Electronics', 100);
const clothing = new Product('Clothing', 50);
const books = new Product('Books', 30);

const electronicsDiscount = new DiscountCalculator(new ElectronicsDiscount());
const clothingDiscount = new DiscountCalculator(new ClothingDiscount());
const booksDiscount = new DiscountCalculator(new NoDiscount());

console.log(electronicsDiscount.calculateDiscount(electronics)); // 90
console.log(clothingDiscount.calculateDiscount(clothing)); // 40
console.log(booksDiscount.calculateDiscount(books)); // 30
Enter fullscreen mode Exit fullscreen mode

Conclusão

Aplicar o Princípio do Aberto/Fechado é imprescindível se precisamos adicionar novos recursos ou comportamentos sem ter a necessidade de modificar tão profundamente a base de código existente. Na verdade, com o tempo, vemos que é praticamente impossível evitar 100% a mudança da base do código porém é possível sim mitigar a quantidade bruta de código a ser alterado para inserção de uma nova funcionalidade.

Esse princípio torna o código mais adaptável a mudanças, seja para atender novos requisitos ou corrigir erros.

Top comments (0)