Dando continuidade a série inciada no ultimo post sobre os princípios SOLID, hoje o post será sobre o Princípio Aberto/Fechado (Open Closed Principle em inglês), ou apenas OCP.
Sua definição diz:
“Entidades de software (classes, módulos, funções, etc.) devem ser abertas para extensão, mas fechadas para modificações”
O que isso significa? Significa que: quando precisar estender um código, crie um novo ao invés de alterar o código existente.
Vamos entender melhor com alguns exemplos:
Exemplo de violação
Imagine que temos algumas classes que representam componentes. Primeiro a classe Card
que possui o atributo description
.
class Card {
constructor(description) {
this._description = description;
}
get description() {
return this._description;
}
set description(value) {
this._description = value;
}
}
E depois a classe List
com o atributo items
.
class List {
constructor(items) {
this._items = items;
}
get items() {
return this._items;
}
set items(values) {
this._items = values;
}
}
Além das classes, vamos precisar de algo para renderizá-los, mas como faremos isso? Simples, criamos uma função que recebe um elemento do DOM e uma instância de um componente, verifica seu tipo e decide qual template será renderizado dentro do elemento.
function renderComponent(element, component) {
if (component instanceof Card) {
element.innerHTML = `
<div class="card">
<p class="card__description">${ component.description }</p>
</div>
`;
} else if (component instanceof List) {
element.innerHTML = `
<ul>
${ component.items.map(item => `<li>${ item }</li>`).join('') }
</ul>
`;
} else {
throw new Error('Component not supported');
}
}
Dessa forma conseguimos renderizar nossos componentes dentro de qualquer elemento do DOM, mas e se quisermos adicionar novos componentes? Para cada classe adicionada será preciso um novo “else if” para checar o tipo e um template para ser renderizado, isso claramente é uma violação do OCP.
Resolvendo a violação
Em JavaScript não existe uma implementação para classes abstratas ou interfaces, mas isso não nos impede de simular esses conceitos.
Nesse exemplo vamos simular o conceito de interface criando a classe Component
, que servirá apenas como um “contrato” obrigando todos que o “assinarem” (suas subclasses) a implementar o método render
.
class Component {
constructor() {
if (!this.render) {
throw new Error('Component subclass has no implementation for the render method');
}
}
}
Em seguida, vamos fazer com que as classes Card
e List
tornem-se subclasses de Component
.
class Card extends Component {
constructor(description) {
super();
this._description = description;
}
// getters/setters ...
render(element) {
element.innerHTML = `
<div class="card">
<p class="card__description">${ this.description }</p>
</div>
`;
}
}
class List extends Component {
constructor(items) {
super();
this._items = items;
}
// getters/setters ...
render(element) {
element.innerHTML = `
<ul>
${ this.items.map(item => `<li>${ item }</li>`).join('') }
</ul>
`;
}
}
Dessa forma cada subclasse terá seu template definido dentro da sua própria implementação do método render
, sempre que quisermos estender nosso código, podemos simplesmente criar uma nova classe sem precisar alterar o código existente.
Conclusão
Esse princípio nos atenta a usar abstrações e polimorfismo de forma consciente, diminuindo o acoplamento e tornando nosso código mais fácil de manter.
Espero que tenham gostado, e só para reforçar, se tiverem dúvidas ou sugestões não deixem de comentar. Abraço!