RODRIGO BRANAS
5 de Janeiro de 2024

SOLID - Open/Closed Principle (OCP)

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.


#solid #ocp