Artigo

SOLID - Open/Closed Principle (OCP)

Rodrigo Branas
Rodrigo Branas
05 jan 2024·1 min de leitura
#solid#ocp

Heranca e polimorfismo são recursos fundamentais em linguagens orientadas a objetos que permitem pontos de extensao, suportando o Open/Closed Principle (OCP), permitindo a evolução do comportamento sem modificar o código existente.

O Problema: O Design Inicial

Considere uma classe Employee representando trabalhadores com dois tipos de compensacao: salario fixo e por hora. Uma classe TimeRecord rastreia os horarios de entrada e saida.

class TimeRecord {
  constructor(
    readonly checkinDate: Date,
    readonly checkoutDate: Date
  ) {}

  getPeriodInHours() {
    return (this.checkoutDate.getTime() - this.checkinDate.getTime()) / (1000 * 60 * 60);
  }
}

class Employee {
  timeRecords: TimeRecord[];

  constructor(
    readonly name: string,
    readonly role: string,
    readonly type: string,
    readonly salary: number
  ) {
    this.timeRecords = [];
  }

  addTimeRecord(checkinDate: Date, checkoutDate: Date) {
    this.timeRecords.push(new TimeRecord(checkinDate, checkoutDate));
  }

  getWorkedHours() {
    let hours = 0;
    for (const record of this.timeRecords) {
      hours += record.getPeriodInHours();
    }
    return hours;
  }

  calculateSalary(): number {
    if (this.type === "hourly") {
      return this.getWorkedHours() * this.salary;
    }
    if (this.type === "salaried") {
      const hourlyRate = this.salary / 160;
      const diff = (this.getWorkedHours() - 160) * hourlyRate;
      return this.salary + diff;
    }
    return 0;
  }
}

Cada novo tipo de funcionario requer a adicao de condicoes em "calculateSalary", arriscando defeitos no código.

Adicionando um Tipo Voluntario (Violando o OCP)

calculateSalary(): number {
  if (this.type === "hourly") {
    return this.getWorkedHours() * this.salary;
  }
  if (this.type === "salaried") {
    const hourlyRate = this.salary / 160;
    const diff = (this.getWorkedHours() - 160) * hourlyRate;
    return this.salary + diff;
  }
  if (this.type === "volunteer") {
    return 0;
  }
  return 0;
}

Solução: Aplicando o OCP

Torne Employee abstrato, criando um ponto de extensao em "calculateSalary":

abstract class Employee {
  timeRecords: TimeRecord[];

  constructor(
    readonly name: string,
    readonly role: string,
    readonly type: string,
    readonly salary: number
  ) {
    this.timeRecords = [];
  }

  addTimeRecord(checkinDate: Date, checkoutDate: Date) {
    this.timeRecords.push(new TimeRecord(checkinDate, checkoutDate));
  }

  getWorkedHours() {
    let hours = 0;
    for (const record of this.timeRecords) {
      hours += record.getPeriodInHours();
    }
    return hours;
  }

  abstract calculateSalary(): number;
}

Crie implementacoes para cada tipo:

class HourlyEmployee extends Employee {
  calculateSalary(): number {
    return this.getWorkedHours() * this.salary;
  }
}

class SalariedEmployee extends Employee {
  calculateSalary(): number {
    const hourlyRate = this.salary / 160;
    const diff = (this.getWorkedHours() - 160) * hourlyRate;
    return this.salary + diff;
  }
}

class VolunteerEmployee extends Employee {
  calculateSalary(): number {
    return 0;
  }
}

Design Patterns Relacionados

Vários padrões comportamentais se alinham com o OCP: Chain of Responsibility, Strategy e Template Method.

Conclusão

Priorize o OCP para código manutenivel e reutilizavel, respeitando ao mesmo tempo o Liskov Substitution Principle.

Continue aprendendo

Já conhece nossas formações?

Os desenvolvedores que vão se manter no futuro são os que dominam a Inteligência Artificial e são arquitetos de software. Aprenda como ser esse tipo de desenvolvedor.

Cookies e privacidade

Utilizamos cookies para melhorar sua experiência, analisar o tráfego do site e personalizar conteúdo. Você pode aceitar todos, rejeitar ou personalizar suas preferências.