SOLID é uma sigla para 5 princípios de programação, que se aplicados de forma correta visam melhorar a qualidade, flexibilidade, arquitetura do código e diminuir os riscos de surgirem code smells ou alto acoplamento entre classes e métodos.
- S - Single Responsibility Principle (SRP)
- O - Open/Closed Principle (OCP)
- L - Liskov Substitution Principle (LSP)
- I - Interface Segregation Principle (ISP)
- D - Dependecy Inversion Principle (DIP)
- Uma classe só deve ser responsável por um domínio dentro da aplicação: (Classe Customer deve ter dados e comportamentos referente ao Customer, e não deve saber nada por exemplo, sobre quantas compras esse Customer fez)
- Uma método só deve ser responsável por uma ação dentro do sistema.
- Uma método só deve ter uma razão para mudar.
- Classes/Métodos que seguem esse princípio, tender a ser mais fáceis de manter, extender e refatorar.
# VIOLA SRP
class Product
attr_reader :name, :price
def initialize(name, price)
@name = name
@price = price
end
# Essa lógica pode ser extraída para uma classe separada
def price_with_discount
price * 0.90
end
end
# SEGUE SRP
# product.rb
class Product
attr_reader :price, :name
def initialize(name, price)
@name = name
@price = price
end
def price_with_discount
DiscountCalculator.new(price).calculate
end
end
# discount_calculator.rb
class DiscountCalculator
DiscountPercentage = 0.9
def initialize(value)
@value = value
end
def calculate
value * DiscountPercentage
end
private
attr_reader :value
end
- Classes/métodos devem ser abertos para extesão e fechados para uma alteração.
- Seguir OCP nos força a ter um design mais modular e pensar mais em abstrações
- Ao invés de alterar código existente, seja possível a partir de um código base extender o comportamento, evitando alterar código já existente/legado.
- O uso do Strategy Pattern ajuda a não violar o OCP.
# VIOLA OCP
class FileParser
def initialize(file)
@file = file
end
def parse
case @file.extension
when :xml then XMLParser.new(@file)
when :json then JSONParser.new(@file)
#when :x then parse_x
#when :y then parse_y
end
end
end
File.new('xunda.json').parse
# SEGUE OCP
class FileParser
def initialize(file, parser)
@file = file
@parser = parser
end
def parse
parser.parse(file)
end
private
attr_reader :parser, :file
end
class BaseParser
def parse(file)
raise 'Must Implement !'
end
end
class JSONParser < BaseParser
def parse(file)
"#{file}.json"
end
end
class XMLParser < BaseParser
def parse(file)
"#{file}.xml"
end
end
FileParser.new('file.txt', JSONParser.new).parse
FileParser.new('file.txt', XMLParser.new).parse
- Subclasses podem ser substituidas pelas suas classes bases, sem quebrar a implementação.
# VIOLA LSP
class FileGenerator
def initialize(file_name)
@file_name = file_name
end
end
class WordFile < FileGenerator
def generate_doc
p 'Generate doc file'
end
end
class PDFFile < FileGenerator
def generate_pdf
p 'Generate pdf file'
end
end
# outros geradores...XML, JSON...
class FileGeneratorService
def initialize(files)
@files = files
end
def generate
@files.each do |file|
return file.generate_pdf if file.is_a?(PDFFile)
return file.generate_doc if file.is_a?(WordFile)
# ...
end
end
end
files = Array.new(5) { |i| FileGenerator.new("file#{i}.txt") }
# SEGUE LSP
class FileGenerator
def initialize(file_name)
@file_name = file_name
end
def generate
raise 'Must implement!'
end
end
class WordFile < FileGenerator
def generate
p 'Generate doc file'
end
end
class PDFFile < FileGenerator
def generate
p 'Generate pdf file'
end
end
# outros geradores...XML, JSON...
class FileGeneratorService
def initialize(files)
@files = files
end
def generate
@files.each { |file| file.generate }
end
end
files = Array.new(5) { |i| FileGenerator.new("file#{i}.txt") }
- Classes derivadas não devem ser obrigadas a depender de métodos que não fazem uso, apenas para obedecer uma herança.
- Alteração nesses métodos podem acabar quebrando na classe pai e se propagar a classe filha, causando sua quebra, mesmo que ela não faça uso dessa interface.
- Não generalizar interfaces (não fazer o famoso class Util e jogar qualquer coisa lá).
- Classes mesmo que abstratas também precisam ter uma "especialidade".
- Evitar interfaces/abstraçõe que "sejam faz tudo".
# VIOLA ISP
class Vehicle
def open_door; end
def numbers_of_door; end
def start_engine; end
def stop_engine; end
def accelerate; end
end
class Car < Vehicle; end
class Motorcycle < Vehicle; end
# SEGUE OCP
class Vehicle
def start_engine; end
def stop_engine; end
def accelerate; end
end
class VehicleCar < Vehicle
def open_door; end
def numbers_of_door; end
end
class VehicleMoto < Vehicle; end
class Car < VehicleCar; end
class Moto < VehicleMoto; end
- Objetos de alto nível (lógica de negócio), não devem depender dos detalhes de implementação de objetos de mais baixo nível.
- Uma das formas de resolver o problema de OCP é aplicando DIP.
# VIOLA DIP
class Report
def initialize(file)
@file = file
end
def print
JSONParser.new.parse(@file)
end
end
Report.new('xunda.doc').print
# SEGUE OCP
class Report
def initialize(file, parser)
@file = file
@parser = parser
end
def print
parser.parse(file)
end
private
attr_reader :parser, :file
end
class BaseParser
def parse(file)
raise 'Must Implement !'
end
end
class JSONParser < BaseParser
def parse(file)
"#{file}.json"
end
end
class XMLParser < BaseParser
def parse(file)
"#{file}.xml"
end
end
Report.new('file.txt', JSONParser.new).print
Report.new('file.txt', XMLParser.new).print
- http://shipit.resultadosdigitais.com.br/blog/dicas-de-design-orientado-a-objetos-com-ruby-parte-1/
- http://shipit.resultadosdigitais.com.br/blog/dicas-de-design-orientado-a-objetos-com-ruby-parte-2/
- https://robots.thoughtbot.com/back-to-basics-solid
- https://subvisual.co/blog/posts/19-solid-principles-in-ruby