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
Arquitetura não é qual framework, linguagem, infraestrutura ou banco de dados que uma aplicação usa. Todas essas coisas são tecnologias, apenas ferramentas.
Comparamos com arquiteturas de edifícios. Esses detalhes seriam apenas os tijolos, vigas, tubulações etc. A arquitetura trata da organização da estrutura.
É 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.
Não deve existir mais de um motivo para uma classe ser alterada.
- Classes definidas com propósito único
class ShoppingItem
def save(shopping_item)
# saves shopping_item
end
def calculate_item_price
# calculates shopping item price
end
end
class ShoppingItemDataBase
def save(shopping_item)
# saves shopping_item
end
end
class ShoppingItemEntity
def calculate_item_price
# calculates shopping item price
end
end
"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.
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
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
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
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.
É 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
Esses princípios que baseiam um software robusto, manutenível e testável, podem ser implementados de formas diferentes. A Clean Architecture é uma delas.