Skip to content

Instantly share code, notes, and snippets.

@lcezermf
Last active April 13, 2017 17:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lcezermf/901d5fedd1f83ec37107a5b4587c3e5c to your computer and use it in GitHub Desktop.
Save lcezermf/901d5fedd1f83ec37107a5b4587c3e5c to your computer and use it in GitHub Desktop.
SOLID - Pudim

QQ É ISSO ?

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)

Single Responsibility Principle (SRP)

  • 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

Open/Closed Principle (OCP)

  • 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

Liskov Substitution Principle (LSP)

  • 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") }

Interface Segregation Principle (ISP)

  • 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

Dependecy Inversion Principle (DIP)

  • 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

Refs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment