The document provides an overview of SOLID principles with examples in Ruby code. It discusses the Single Responsibility Principle (SRP), Open-Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). For each principle, it shows code that violates the principle and refactors the code to follow the principle. The document emphasizes that SOLID is difficult and requires practice, and violations often involve multiple principles. It encourages reading books to learn more about applying SOLID in code.
13. class Post < ActiveRecord::Base
belongs_to :author
has_many :comments
validates_presence_of :title, :body
after_create :log_creation
after_update :log_update
after_destroy :log_deletion
private
def log_creation
Log.create(:message => "Post '#{title}' was created.")
end
def log_update
Log.create(:message => "Post '#{title}' was updated.")
end
def log_deletion
Log.create(:message => "Post '#{title}' was deleted.")
end
end
14. class Post < ActiveRecord::Base
belongs_to :author
has_many :comments Pode, Arnaldo?
validates_presence_of :title, :body
after_create :log_creation
after_update :log_update
after_destroy :log_deletion
private
def log_creation
Log.create(:message => "Post '#{title}' was created.")
end
def log_update
Log.create(:message => "Post '#{title}' was updated.")
end
def log_deletion
Log.create(:message => "Post '#{title}' was deleted.")
end
end
15. Violação do SRP
• Callbacks são conveniente, mas são
prejudiciais
• Chato de testar
• Comportamento que não tem nada
relacionado com Post
• Comportamento inesperado (violação do
Least Surprise Principle)
16. class Post < ActiveRecord::Base
belongs_to :author
has_many :comments
validates_presence_of :title, :body
end
17. class AuditedPost
def self.create(post_attributes)
post = Post.create(post_attributes)
Log.log_creation(post)
post
end
def self.update(post, post_attributes)
post.update_attributes(post_attributes)
Log.log_update(post)
post
end
def self.destroy(post)
post.destroy
Log.log_destruction(post)
post
end
end
20. class User < ActiveRecord::Base
# ...
def to_json(options=nil)
# ...
end
def to_xml
# ...
end
def to_atom
# ...
end
end
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
respond_to do |format|
format.html { render @user }
format.json { render :text => @user.to_json }
format.atom { render :text => @user.to_atom }
end
end
end
21. class User < ActiveRecord::Base
# ...
def to_json(options=nil)
# ...
end
def to_xml
# ...
end
def to_atom
# ...
end
end
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
respond_to do |format|
format.html { render @user }
format.json { render :text => @user.to_json }
format.atom { render :text => @user.to_atom }
end
end
end
23. class UsersController < ApplicationController
def show
@user = User.find(params[:id])
Horrível!
respond_to do |format|
format.html { render @user }
format.json { render :text => @user.to_json }
format.atom { render :text => @user.to_atom }
format.jsonp { render :text => "#{params[:callback]}
(#{@user.to_json});" }
end
end
end
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
respond_to do |format|
format.html { render @article }
format.json { render :text => @article.to_json }
format.atom { render :text => @article.to_atom }
format.jsonp { render :text => "#{params[:callback]}
(#{@article.to_json});" }
end
end
end
24. Nesse caso, a API de
Responders do Rails 3
funciona bem!
25. class ApplicationController < ActionController::Base
respond_to :html, :json, :atom, :jsonp
# ...
end
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
respond_with(@user)
end
end
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
respond_with(@article)
end
end
26. Detectando cheiro de
violação de OCP
• #is_a? / #is_kind_of?
• Reabrindo classes
27. LSP
• Especializações de alguma interface não
devem mudar produtos de métodos pai
• Fácil de acontecer com Ruby, não
precisamos respeitar assinaturas de
método!
43. ISP
• Difícil de ver e exemplificar no Ruby por
ser tipagem dinâmica
• Restringir/reduzir uma interface grande
para um ‘cliente’
44. ISP [2]
• Rack Apps são ótimos exemplos disso!
• Para interface Rack funcionar, basta uma
array com três itens:
• Código da resposta, headers e corpo.
45. ISP [3]
• Ou seja, apps complexa de Rails, no final,
retorna a mesma interface que a app a
seguir
48. module Outpost
module Notifiers
class Campfire
def notify(outpost, campfire_adapter=Outpost::Campfire::TinderAdapter)
campfire = campfire_adapter.new @subdomain, :token => @token
room = campfire.find_room_by_name @room
status = outpost.last_status
room.speak "System is #{status}: #{outpost.messages.join(',')}"
end
end
end
end
49. module Outpost
module Notifiers
class Campfire
def notify(outpost, campfire_adapter=Outpost::Campfire::TinderAdapter)
campfire = campfire_adapter.new @subdomain, :token => @token
room = campfire.find_room_by_name @room
status = outpost.last_status
room.speak "System is #{status}: #{outpost.messages.join(',')}"
end
end
end
end
50. DIP - Se quisermos usar outra lib,
basta usar outro adaptador
module Outpost
module Notifiers
class Campfire
def notify(outpost, campfire_adapter=Outpost::Campfire::TinderAdapter)
campfire = campfire_adapter.new @subdomain, :token => @token
room = campfire.find_room_by_name @room
status = outpost.last_status
room.speak "System is #{status}: #{outpost.messages.join(',')}"
end
end
end
end
51. • SOLID é difícil e exige prática
• YMMV
• As violações normalmente não vêm
isoladas!
• Leia LIVROS! Muita informação sobre o
assunto
• Processo contínuo de aprendizado