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.