Your SlideShare is downloading. ×
Refatoração + Design Patterns em Ruby
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Saving this for later?

Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime - even offline.

Text the download link to your phone

Standard text messaging rates apply

Refatoração + Design Patterns em Ruby

5,383
views

Published on

Palestra feita no dia 29/05/2010 no evento Ruby e Rails no Mundo Real.

Palestra feita no dia 29/05/2010 no evento Ruby e Rails no Mundo Real.

Published in: Technology

3 Comments
29 Likes
Statistics
Notes
No Downloads
Views
Total Views
5,383
On Slideshare
0
From Embeds
0
Number of Embeds
5
Actions
Shares
0
Downloads
212
Comments
3
Likes
29
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

































































































  • Transcript

    • 1. Refatorando Ruby Técnicas de Orientação a Objetos e Design Patterns Aplicados a Linguagens Dinâmicas Cássio Marques Ruby e Rails no Mundo Real 2010
    • 2. cassiomarques.wordpress.com @cassiomarques cassiommc@gmail.com
    • 3. http://www.flickr.com/photos/docman/ Refatoração
    • 4. Motivações
    • 5. Muitos programadores usando Ruby não sabem usar OO
    • 6. “Raciocínio estático”
    • 7. Linguagem nova, mas hábitos antigos
    • 8. Organização
    • 9. Modularizar o código
    • 10. Facilitar manutenção
    • 11. Facilitar compreensão do código para novos desenvolvedores
    • 12. Design Patterns
    • 13. Motivações
    • 14. Ajudar o Ruby a ser “Enterprise”
    • 15. Patterns implementados X Enterprisy
    • 16. YAGNI
    • 17. Um exemplo simples...
    • 18.  1 class Bhaskara  2   def solve(a, b, c)  3     # Calcula o delta  4     d = b ** 2 - 4 * a * c  5  6     if d < 0  7       puts 'Raízes complexas - Não sei resolver!'  8     else  9       # calculando raízes 10       r1 = (-b + Math.sqrt(d))/(2.0 * a) 11       r2 = (-b - Math.sqrt(d))/(2.0 * a) 12 13       # imprimindo resultados 14       puts "Raiz 1: #{r1}" 15       puts "Raiz 2: #{r2}" 16     end 17   end 18 end
    • 19. 1 class Bhaskara   2 end
    • 20. 1 class Bhaskara 2   def initialize(a, b, c) 3     @a, @b, @c = a, b, c 4   end 5 end
    • 21.  1 class Bhaskara  2   def initialize(a, b, c)  3     @a, @b, @c = a, b, c  4   end  5  6   private  7   def delta  8     @b ** 2 - 4 * @a * @c      9   end 10 11   def solve 12     @r1 = (-@b + Math.sqrt(delta))/(2.0 * @a) 13     @r2 = (-@b - Math.sqrt(delta))/(2.0 * @a) 14   end 15 end
    • 22.  1 class Bhaskara  2   def initialize(a, b, c)  3     @a, @b, @c = a, b, c  4   end  5  6   def print_results  7     if delta < 0  8       puts 'Raízes complexas - Não sei resolver!'  9     else 10       solve 11       puts "Raiz 1: #{@r1}" 12       puts "Raiz 2: #{@r2}" 13     end 14   end 15 16   private 17   def delta 18     @b ** 2 - 4 * @a * @c     19   end 20 21   def solve 22     @r1 = (-@b + Math.sqrt(delta))/(2.0 * @a) 23     @r2 = (-@b - Math.sqrt(delta))/(2.0 * @a) 24   end 25 end
    • 23.  1 class Bhaskara  2   def initialize(a, b, c)  3     @a, @b, @c = a, b, c  4   end  5  6   def print_results  7     delta < 0 ? print_complex_results : print_real_results  8   end  9 10   private 11   def delta 12     @b ** 2 - 4 * @a * @c     13   end 14 15   def solve 16     @r1 = (-@b + Math.sqrt(delta))/(2.0 * @a) 17     @r2 = (-@b - Math.sqrt(delta))/(2.0 * @a) 18   end 19 20   def print_real_results 21     solve 22     puts "Raiz 1: #{@r1}" 23     puts "Raiz 2: #{@r2}" 24   end 25 26   def print_complex_results 27     puts 'Raízes complexas - Não sei resolver!' 28   end 29 end
    • 24. O que eu preciso para refatorar?
    • 25. TESTES
    • 26. Mantenha seus métodos pequenos http://www.flickr.com/photos/davidden/
    • 27.  1 class ShoppingCart  2   def total_value  3     total = 0  4     for item in @items  5       sub_total = item.value * item.quantity  6       if item.quantity > 3  7         sub_total *= 0.90  8       elsif item.quantity > 6  9         sub_total *= 0.85 10       elsif item.quantiy > 9 11         sub_total *= 0.80 12       end 13       total += sub_total 14     end 15     total 16   end 17 end
    • 28.  1 # - Elimina variáveis temporárias em favor de métodos  2 class ShoppingCart  3   def total_value  4     total = 0  5     for item in @items  6       total += if item.quantity > 3  7         sub_total(item) * 0.90  8       elsif item.quantity > 6  9         sub_total(item) * 0.85 10       elsif item.quantiy > 9 11         sub_total(item) * 0.80 12       end 14     end 15     total 16   end 17 18   def sub_total(item) 19     item.value * item.quantity 20   end 21 end 22
    • 29.  1 # - Método usa apenas atributos de outro objeto  2 #  => Mover!  3 class ShoppingCart  4   def total_value  5     total = 0  6     for item in @items  7       total += if item.quantity > 3  8         item.total_value * 0.90  9       elsif item.quantity > 6 10         item_total_value * 0.85 11       elsif item.quantiy > 9 12         item.total_value = 0.80 13       end        14     end 15     total 16   end 17 end 18 19 class Item 20   def total_value 21     @value * @quantity 22   end 23 end
    • 30.  1 # - Método fazendo coisa demais  2 # => Extrair!  3 class ShoppingCart  4   def total_value  5     total = 0  6     for item in @items  7       total += item.total_value * discount(item.quantity)  8     end  9     total 10   end 11 12   def discount(quantity) 13     case quantity 14     when (1..2); 1.00 15     when (3..5); 0.90 16     when (6..8); 0.85 17     else 0.80     18     end 19   end 20 end
    • 31.  1 # - Use Ruby!  2 class ShoppingCart  3   def total_value  4     items.inject(0.0) { |sum, item|   5       sum += item.total_value * discount(item.quantity)  6     }  7   end  8  9   def discount(quantity) 10     case quantity 11     when (1..2); 1.00 12     when (3..5); 0.90 13     when (6..8); 0.85 14     else 0.80     15     end 16   end 17 end
    • 32. Faça objetos se comportarem como coleções http://www.flickr.com/photos/deepsignal/366774303/sizes/l/
    • 33.  1 class Library  2   def search_book_by_title(title)  3     @books.select { |book| book.title == title }  4   end  5  6   def search_book_by_author(author)  7     @books.select { |book| book.author == author }  8   end  9 10   def sort_by_title 11     @books.sort_by { |book| book.title } 12   end 13 14   def authors 15     @books.map { |book| book.author } 16   end 17 18   # ... 19 end
    • 34.  1 class Library  2   include Enumerable  3  4   def each  5     @books.each { |book| yield book }  6   end  7 end  8  9 library.each { |book| ... } 10 library.inject { |book| ... } 11 library.map { |book| ... } 12 library.sort { |book| ... } 13 # ...
    • 35. Dê nomes aos parâmetros dos métodos http://www.flickr.com/photos/giantginkgo/
    • 36.  1 class Dialog  2   def initialize(height, width, name, title)  3     @height = height  4     @width  = width  5     @name   = name  6     @title  = title  7   end  8 end  9 10 dialog = Dialog.new 200, 400, 'dialog1', 'A Dialog'
    • 37.  1 class Dialog  2   def initialize(options)  3     @height = options[:height]  4     @width  = options[:width]  5     @name   = options[:name]  6     @title  = options[:title]  7   end  8 end  9 10 dialog = Dialog.new( 11   :height => 200, 12   :width  => 400, 13   :name   => 'dialog1', 14   :title  => 'A Dialog' 15 )
    • 38. Anotações http://www.flickr.com/photos/matski_98/
    • 39.  1 class Person  2   def state=(state)  3     unless [:sick, :healthy].include?(state)  4       raise ArgumentError, "unknown state: #{state}"   5     end  6     @state = state  7   end  8 end  9 10 class Vehicle 11   def state=(state) 12     unless [:parked, :running].include?(state) 13       raise ArgumentError, "unknown state: #{state}"  14     end 15     @state = state 16   end 17 end
    • 40.  1 module HasStates  2   def has_states(*args)  3     define_method :state= do |new_state|  4       unless args.include?(new_state)  5         raise ArgumentError, "unknown state: #{state}"   6       end  7       @state = new_state  8     end  9   end 10 end
    • 41.  1 class Person  2   extend HasStates  3  4   has_states :sick, :healthy  5 end  6  7 class Vehicle  8   extend HasStates  9 10   has_states :parked, :running 11 end 12 13 Object.extend HasStates
    • 42. Uma classe não deve realizar trabalhos que não lhe pertencem...
    • 43. 1 class Person 2   attr_accessor :telephone_number, :telephone_area_code 3 4   def formatted_telephone 5     "(#{telephone_area_code}) #{telephone_number}" 6   end 7 end
    • 44.  1 class Person  2   def initialize  3     @telephone_number = TelephoneNumber.new  4   end  5 end  6  7 class TelephoneNumber  8   attr_accessor :area_code, :number  9 10   def formatted 11     "(#{area_code}) #{number}" 12   end 13 end
    • 45. Delegue, delegue, delegue...
    • 46.  1 class Queue  2   def initialize  3     @queue = []  4   end  5  6   def enqueue(element)  7     @queue.unshift element      8   end  9 10   def dequeue 11     @queue.pop 12   end 13 end
    • 47. O Ruby pode dar uma ajudinha...
    • 48.  1 require 'forwardable'  2  3 class Queue  4   extend Forwardable  5  6   def initialize  7     @queue = []  8   end  9 10   def_delegator :@queue, :unshift, :enqueue 11   def_delegator :@queue, :pop, :dequeue 12 end
    • 49. Não exponha o interior de seus objetos sem necessidade http://www.flickr.com/photos/mwichary/
    • 50.  1 class Room  2   attr_reader :air_conditioner  3 end  4  5 class AirConditioner  6   attr_reader :sensor  7 end  8  9 class TemperatureSensor 10   def temperature 11     #... 12   end 13 end 14 15 if room.air_conditioner.sensor.temperature > 28 16   room.air_conditioner.on 17 end
    • 51.  1 class Room  2   attr_reader :air_conditioner  3  4   def temperature  5     air_conditioner.sensor.temperature  6   end  7 end  8  9 if room.temperature > 28 10   room.air_conditioner.on 11 end
    • 52. Substitua números mágicos por constantes http://decluttr.com/4524127771_white
    • 53. 1 class Circle 2   def area 3     3.14159265 * (r ** 2) 4   end 5 end
    • 54. 1 class Circle 2   PI = 3.14159265  3 4   def area 5     PI * (r ** 2) 6   end 7 end
    • 55. Encapsule coleções de objetos http://www.flickr.com/photos/eagleglide/
    • 56.  1 class Cart  2   attr_accessor :products  3 end  4  5 class Product  6   attr_reader :value, :name  7  8   def initialize(name, value)  9     @name, @value = name, value 10   end 11 end
    • 57. 1 products = [] 2 products << Product.new('Camiseta', 35.00) 3 products << Product.new('Bermuda', 38.00) 4 products << Product.new('Boné', 20.00) 5 cart = Cart.new 6 cart.products = products 7 cart.products.pop # => alterei o estado do carrinho
    • 58.  1 class Cart  2   def initialize  3     @products = []  4   end  5  6   def add_product(product)  7     @products << product      8   end  9 10   def remove_product(product) 11     @products.delete product 12   end 13 end 14 15 cart = Cart.new 16 cart.add_product(Product.new('Camiseta', 35.00))
    • 59. Substitua condicionais por polimorfismo http://www.flickr.com/photos/randyread/
    • 60.  1 class Tax  2   def initialize(type, value)  3     @type = type      4     @value = value  5   end  6  7   def retained_value  8     case @type  9     when :irpj;   @value * 0.015 10     when :pis;    @value * 0.0065 11     when :cofins; @value * 0.030 12     when :iss;    0.0 13     end 14   end 15 16   def due_value 17     case @type 18     when :irpj;   @value * 0.033 19     when :pis;    0.0 20     when :cofins; 0.0 21     when :iss;    @value * 0.02 22     end 23   end 24 end
    • 61.  1 module Tax  2   def due_value  3     @due_rate * @value      4   end  5  6   def retained_value  7     @retained_rate * @value  8   end  9 end 10 11 class IrpjTax 12   include Tax 13 14   def initialize(value) 15     @value         = value 16     @due_rate      = 0.033 17     @retained_rate = 0.015 18   end 19 end 20 21 # o mesmo para as demais classes...
    • 62.  1 class Invoice  2   def initialize(value)  3     @value = value  4     create_taxes  5   end  6  7   def add_tax(tax)  8     @taxes << tax  9   end 10    11   def total_taxes_value 12     @taxes.inject(0.0) { |sum, tax| 13       sum += tax.due_value + tax.retained_value 14     } 15   end 16 17   private 18   def create_taxes 19     @taxes = [] 20     @taxes << IrpjTax.new(@value) 21     @taxes << PisTax.new(@value) 22     @taxes << CofinsTax.new(@value) 23     @taxes << IssTax.new(@value) 24   end 25 end 26 27 invoice = Invoice.new 1400.00 28 puts invoice.total_taxes_value
    • 63. Simplifique expressões condicionais
    • 64. 1 if product.release_date < 3.months.ago || quantity > 100 2   self.value *= 0.90 3 end
    • 65.  1 if elegible_for_discount?  2   self.value *= 0.90  3 end  4  5 def elegible_for_discount?  6   old? && great_quantity?  7 end  8  9 def old? 10   product.release_date < 3.months.ago 11 end 12 13 def great_quantity? 14   quantity > 100 15 end
    • 66. http://www.flickr.com/photos/jeff_oliver/ Mas uma expressão condicional pode ser melhor que ifs aninhados...
    • 67. 1 if age > 40 2   if gender == :male 3     if last_visit > 1.month.ago 4       # ... 5     end 6   end 7 end
    • 68. 1 if age > 40 && gender == :male && last_visit > 1.month 2   # ... 3 end 4 5 # e a partir daqui podemos extrair as condições para 6 # métodos...
    • 69. Às vezes faz mais sentido juntar do que separar...
    • 70. 1 class Bicycle 2   def press_front_brake 3     brakes[:front].press 4   end 5 6   def press_rear_brake 7     brakes[:rear].press 8   end 9 end
    • 71. 1 class Bicycle 2   def press_brake(brake) 3     brakes[brake].press 4   end 5 end
    • 72. Verifique se a mensagem pode ser enviada http://www.flickr.com/photos/funtik/
    • 73. 1 def do_something(target) 2   target.prepare rescue nil 3   target.explode 4 end
    • 74. 1 def do_something(target) 2   target.prepare if target.respond_to? :prepare 3   target.explode 4 end
    • 75. Herança
    • 76.  1 class CdPlayer  1 class TapeDeck  2   def turn_on  2   def turn_on  3     @on = true  3     @on = true  4   end  4   end  5  5  6   def turn_off  6   def turn_off  7     @on = false  7     @on = false  8   end  8   end  9  9 10   def play 10   def play 11     play_disc 11     play_tape 12   end 12   end 13 end 13 end
    • 77.  1 class AudioDevice  2   def turn_on  3     @on = true  4   end  5  6   def turn_off  7     @on = false  8   end  9 end 10 11 class CdPlayer < AudioDevice 12   def play 13     play_disk 14   end 15 end 16 17 class TapeDeck < AudioDevice 18   def play 19     play_tape 20   end 21 end
    • 78. Strategy
    • 79.  1 class Text  2   attr_reader :contents  3  4   def left_aligned_format  5     # ...  6   end  7  8   def right_aligned_format  9     # ... 10   end 11 12   def justified_format 13     # ... 14   end 15 end
    • 80.  1 class LeftAlignedFormatter  2   def format(context)  3     align_left context.contents  4   end  5 end  6  7 class RightAlignedFormatter  8   def format(context)  9     align_right context.contents 10   end 11 end 12 13 class RightJustifiedFormatter  14   def format(context) 15     justify context.contents 16   end 17 end
    • 81.  1 class Text  2   attr_reader :contents  3   attr_accessor :formatter  4  5   def initialize(contents, formatter)  6     @contents, @formatter = contents, formatter  7   end  8  9   def format 10     @formatter.format self 11   end 12 end
    • 82. Blocos, ao resgate! http://www.flickr.com/photos/bekahstargazing/
    • 83.  1 class Text  2   attr_reader :contents  3   attr_accessor :formatter  4  5   def initialize(contents, &formatter)  6     @contents, @formatter = contents, formatter  7   end  8  9   def format 10     @formatter.call self 11   end 12 end 13 14 left_aligned_text = Text.new 'some text' do |context| 15   align_left context.contents 16 end
    • 84. Use quando você precisar alterar o algorítmo em tempo de execução
    • 85. http://www.flickr.com/photos/micahdowty/
    • 86.  1 class Document  2   def change_text_color(color)  3     @text.color = color  4   end  5  6   def change_background_color(color)  7     @text.background_color = color      8   end  9 10   def change_font_size(size) 11     @text.font_size = size     12   end 13 14   def change_font(font) 15     @text.font = font 16   end 17 end
    • 87. ctrl-z ?
    • 88.  1 class Command  2   attr_reader :description  3  4   def initialize(description)  5     @description = description      6   end  7 end
    • 89.  1 class ChangeTextColorCommand < Command  2   def initialize(target)  3     super("Altera a cor do texto")      4     @target = target  5   end  6  7   def execute(color)  8     @old_color = @target.text.color  9     @target.text.color = color 10   end   11 12   def unexecute 13     @target.text.color = @old_color if @old_color 14   end 15 end
    • 90.  1 class Document  2   def initialize  3     @commands = []  4   end  5  6   def change_text_color(color)  7     run_command ChangeTextColorCommand.new(self), color  8   end  9 10   def change_font(font) 11     run_command ChangeFontCommand.new(self), font 12   end 13 14   def undo_last_action 15     @commands.pop.unexecute unless @commands.empty? 16   end 17 18   private 19   def run_command(command, param) 20     command.execute param 21     @commands << command 22   end 23 end
    • 91. Use quando você precisar realizar tarefas específicas e mantê-las em um registro
    • 92. Proxy
    • 93.  1 class Task   2   def initialize(creator)  3     @creator = creator  4   end  5  6   def owner=(owner)  7     @owner = owner      8   end  9 10   def add_comment(comment) 11     @comments << comment 12   end 13 14   def update_description(new_description) 15     @description = description 16   end 17 18   def close 19     @status = :closed 20   end 21 end
    • 94.  1 class TaskProtectionProxy  2   def initialize(target)  3     @target = target      4   end  5  6   def owner=(owner)  7     check_permissions :owner=  8     @owner = owner      9   end 10 11   def add_comment(comment)     12     check_permissions :add_comment 13     @comments << comment 14   end 15 16   # ... 17 18   private 19   def check_permissions(what) 20     unless can_do?(current_user, what) 21       raise 'Acesso negado!'  22     end 23   end 24 end
    • 95.  1 class TaskProtectionProxy  2   def initialize(target)  3     @target = target  4   end  5  6   def method_missing(method, *args)  7     check_permissions(method)  8     @target.send method, args  9   end 10 11   private  12   def check_permissions(what) 13 unless can_do?(current_user, what) 14       raise 'Acesso negado!'  15     end 16   end 17 end
    • 96. Perguntas?
    • 97. Obrigado!