Ruby e Princípios SOLID
Criado por Robert Martin no início dos anos 2000. Cinco princípios que auxiliam na arquitetura de software com o propósito de criar software que atende a três critérios:
- Tolerância a alterações
- Facilidade de entendimento
- Criação de componentes que servem de base para múltiplos softwares
1. O que é Arquitetura?
1.1. Arquitetura ou Ferramentas?
Arquitetura não é qual framework, linguagem, infraestrutura ou banco de dados que uma aplicação usa. Todas essas coisas são tecnologias, apenas ferramentas.
1.2. Arquitetura em construções
Comparamos com arquiteturas de edifícios. Esses detalhes seriam apenas os tijolos, vigas, tubulações etc. A arquitetura trata da organização da estrutura.
1.3. Arquitetura em Software
É possível identificar uma igreja observando a sua planta, por exemplo. Da mesma forma, deve ser possível identificar o domínio de um software olhando sua estrutura.
2. SOLID
2.1 Single Responsibility Principle
Não deve existir mais de um motivo para uma classe ser alterada.
- Classes definidas com propósito único
Múltiplas responsabilidades em uma classe
class ShoppingItem
def save(shopping_item)
# saves shopping_item
end
def calculate_item_price
# calculates shopping item price
end
end
Uma classe responsável pela interação com o banco de dados e outra pelas regras de negócio
class ShoppingItemDataBase
def save(shopping_item)
# saves shopping_item
end
end
class ShoppingItemEntity
def calculate_item_price
# calculates shopping item price
end
end
2.2 Open Closed Principle
"Classes precisam ser abertas para extensão e fechadas para alterações."
Código precisa ser facilmente alterável. E para isso:
"[...] they must be designed to allow the behavior of those systems to be changed by adding new code, rather than changing existing code." -- Uncle Bob
Quando os requisitos são estendidos e uma mudança enorme é necessária, os desenvolvedores erraram. Esse princípio diminui os riscos da introdução de bugs limitando as alterações ao código existente.
Violação do OCP
class Rectangle
def initialize(width, height)
@width = width
@height = height
end
end
class Square
def initialize(size)
@size = size
end
end
class ShapePrinter
def draw_shape(shape)
if shape.is_a? Square
# draw square
elsif shape.is_a? Rectangle
# draw rectangle
end
end
end
De acordo com OCP
class Rectangle
def initialize(width, height)
@width = width
@height = height
end
def draw
# draw rectangle
end
end
class Square
def initialize(size)
@size = size
end
def draw
# draw square
end
end
class ShapePrinter
def draw_shape(shape)
shape.draw
# [...] code specific to ShapePrinter
end
end
2.3 Liskov Substitution Principle
Definição de subtipos de Barbara Liskov. Uma função que recebe um tipo A
deve funcionar corretamente se receber o tipo B
subtipo de A
, sem alterações ao sistema.
Para construir sistemas com partes trocáveis, essas partes precisam aderir a um contrato que permita a substituição dessas partes.
class DoPayment
def pay(billing_information, billing_calculator)
cost = billing_calculator.calculate_cost(billing_information)
PaymentStorage.save_payment(cost)
end
end
2.4 Interface Segregation Principle
Basicamente, uma classe não deve implementar uma interface que não vai utilizar um método.
- Redeploy
- Recompilação das dependências de código
Apesar de Ruby ser dinamicamente tipada e sem uma implementação (padrão) de interfaces, não quer dizer que não seja possível retirar algo desse princípio.
2.5 Dependency Inversion Principle
É um dos princípios mais referenciados e fáceis de observar, que é a base também de outros princípios, como o SRP. Em resumo:
- Módulos de alto nível não devem depender de módulos de baixo nível.
- Abstrações não dependem de detalhes.
Não se deve:
- referenciar classes concretas voláteis
- criar "herdeiros" de classes concretas voláteis
- sobrescrever métodos concretos
class DoPayment
def initialize(payment_storage)
@payment_storage = payment_storage
end
def pay(billing_information, billing_calculator)
cost = billing_calculator.calculate_cost(billing_information)
@payment_storage.save_payment(cost)
end
end
class PaymentController
def create()
payment_storage = PaymentStorage.new()
DoPayment.new(payment_storage).pay(billing_information, billing_calculator)
end
end
Rspec.describe DoPayment
let(:payment_storage_mock) { PaymentStorageMock.new() }
let(:do_payment) { DoPayment.new(payment_storage_mock) }
end
Conclusão
Esses princípios que baseiam um software robusto, manutenível e testável, podem ser implementados de formas diferentes. A Clean Architecture é uma delas.