A herança, e consequentemente o polimorfismo, são sem dúvida recursos fundamentais nas linguagens orientadas a objeto e que possibilitam a criação de pontos de extensão, favorecendo o Princípio Aberto/Fechado, ou Open/Closed Principle (OCP), e fazendo com que seja possível evoluir o comportamento sem necessariamente modificar o código existente.
Considere a criação de uma classe Employee, que abstrai um funcionário, e que tenhamos dois regimes: salário fixo e horista. Dessa forma, podemos calcular o salário por hora, multiplicando as horas traablhadas pelo valor/hora ou considerar o valor do salário fixo mensal e adicionar as horas extras ou descontando as horas não trabalhadas.
Além disso, também temos uma classe chamada de TimeRecord para representar a entrada e saída de cada funcionário em cada período.
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 employee.timeRecords) {
hours += record.getPeriodInHours();
}
return hours;
}
calculateSalary (): number {
if (this.type === "hourly") {
return this.getWorkedHours() * salary;
}
if (this.type === "salaried") {
const hourlyRate = this.salary/160;
const diff = (this.getHours() - 160) * hourlyRate;
return this.salary + diff;
}
}
}
Cada vez que um novo tipo de funcionário é criado precisamos adicionar uma nova condição no método "calculateSalary", modificando o código existente e correndo o risco de ocorrer algum defeito.
Abaixo segue uma modificação na classe Employee para adicionar o tipo voluntário:
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 employee.timeRecords) {
hours += record.getPeriodInHours();
}
return hours;
}
calculateSalary (): number {
if (this.type === "hourly") {
return this.getWorkedHours() * salary;
}
if (this.type === "salaried") {
const hourlyRate = this.salary/160;
const diff = (this.getHours() - 160) * hourlyRate;
return this.salary + diff;
}
if (this.type === "volunteer") {
return 0;
}
}
}
Para aplicar o Open/Closed Principle, basta modificar a classe Employee para que ela seja abstrata e com isso favorecendo a criação uma subclasse para cada tipo de funcionário e dessa forma criando um ponto de extensão no método "calculateSalary", que deverá ser implementado por cada tipo de funcionário.
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 employee.timeRecords) {
hours += record.getPeriodInHours();
}
return hours;
}
abstract calculateSalary (): number;
}
Agora basta criar as implementações:
class HourlyEmployee extends Employee {
calculateSalary(): number {
return this.getWorkedHours() * salary;
}
}
class SalariedEmployee extends Employee {
calculateSalary(): number {
const hourlyRate = this.salary/160;
const diff = (this.getHours() - 160) * hourlyRate;
return this.salary + diff;
}
}
class VolunteerEmployee extends Employee {
calculateSalary(): number {
return 0;
}
}
Alguns padrões de projeto de encaixam bem no Open/Closed Principle como o Chain of Responsibility, Strategy e Template Method, todos padrões de comportamento.
Tente sempre favorecer este princípio para ter um código mais manutenível e reusável, lembrando sempre de respeitar outro princípio importante, o Liskov Substitution Principle, que vemos em outro artigo.