*/ ?>

Refaktoring iterátoru

Iterátory v Ruby jsou ve spojení s uzávěry (closures) velmi silným nástrojem. Každá třída, která reprezentuje nějakou kolekci, totiž obsahuje Enumerable modul. Ten s sebou přináší několik iterátorů. Jednoduchá iterace přes položky pole vypadá takto:

an_array.each do |item|
  # udelej neco s item
end

Mějme nějaká dvourozměrná data, která nejsou polem. Například objekt obsahující souřadnice mezních bodů polygonu (pravý horní, levý horní, pravý dolní a levý dolní).

# polygon0.rb
class Polygon
  attr_accessor :ur_x, :ur_y, :ul_x, :ul_y, :lr_x, :lr_y, :ll_x, :ll_y
end

Takto vypadá jednoduchá metoda, která hodnoty všech polí vypíše do konzole:

# polygon0.rb
  def print_corners
    puts ur_x, ur_y, ul_x, ul_y, lr_x, lr_y, ll_x, ll_y
  end

Stačilo zkopírovat seznam polí a odstranit dvojtečky. Kopírování kódu ale zavání problémem, takže je lepší se hned vrhnout do refaktoringu a seznam rohů si uložit do konstanty a upravit související kód.

# polygon1.rb
class Polygon

  CORNERS = [:ur_x, :ur_y, :ul_x, :ul_y, :lr_x, :lr_y, :ll_x, :ll_y]

  CORNERS.each do |corner|
    attr_accessor corner
  end

  def print_corners
    CORNERS.each do |corner|
      puts send(corner)
    end
  end
end

Volání send(corner) je dynamickým voláním metody, tady snad není nutné další vysvětlení. Takto přepsaná třída dělá to samé, co předchozí verze, ale je daleko rozšiřitelnější.

Aby se dalo přes data iterovat skutečně dvourozměrně, musíme konstanty ještě trošku poupravit.

# polygon2.rb
class Polygon

  CORNERS = ["ur", "ul", "lr", "ll"]
  PARTS = ["x", "y"]

  CORNERS.each do |corner|
    PARTS.each do |part|
      attr_accessor "#{corner}_#{part}".to_sym
    end
  end

  def print_corners
    CORNERS.each do |corner|
      PARTS.each do |part|
        puts send("#{corner}_#{part}".to_sym)
      end
    end
  end

end

To na jednu stranu přidává ohromnou flexibilitu při práci s jednotlivými souřadnicemi, na druhou stranu přináší nutnost pro každou operaci kopírovat dva vnořené iterátory. Pro příklad přidám metodu, která zkontroluje, zda jsou všechna pole nulová a pokud ano, nastaví je na nil.

# polygon2.rb
  def fix_zero_to_nil
    all_zero = true
    CORNERS.each do |corner|
      PARTS.each do |part|
        val = send("#{corner}_#{part}".to_sym)
        all_zero &&= val.nil? || val == 0
      end
    end
    return unless all_zero
    CORNERS.each do |corner|
      PARTS.each do |part|
        send("#{corner}_#{part}=".to_sym, nil)
      end
    end
  end

Na tomto kódu je krásně vidět, jak metoda díky nutnosti pokaždé iterovat přes dvě pole nabobtnala. Je třeba znova refaktorovat a udělat si vlastní iterátory. Jeden pro případ, že nás skutečně zajímá iterátor pro každou úroveň. Druhý pro případ, že nás zajímají jenom spojené položky.

# polygon3.rb
  def Polygon.each_corner_and_part
    CORNERS.each do |corner|
      PARTS.each do |part|
        yield(corner, part)
      end
    end
  end

  def Polygon.each_coordinate
    each_corner_adn_part do |corner, part|
      yield("#{corner}_#{part}")
    end
  end

Tyto iterátory přijdou vhod ve všech metodách třídy:

# polygon3.rb
class Polygon

  CORNERS = ["ur", "ul", "lr", "ll"]
  PARTS = ["x", "y"]

  def Polygon.each_corner_and_part
    CORNERS.each do |corner|
      PARTS.each do |part|
        yield(corner, part)
      end
    end
  end

  def Polygon.each_coordinate
    each_corner_and_part do |corner, part|
      yield("#{corner}_#{part}")
    end
  end

  each_coordinate do |coord|
    attr_accessor coord.to_sym
  end

  def print_corners
    Polygon.each_coordinate do |coord|
      puts send(coord.to_sym)
    end
  end

  def fix_zero_to_nil
    all_zero = true
    Polygon.each_coordinate do |coord|
      val = send(coord.to_sym)
      all_zero &&= val.nil? || val == 0
    end
    return unless all_zero
    Polygon.each_coordinate do |coord|
      send("#{coord}=".to_sym, nil)
    end
  end

end

Zdorjové kódy jsou k dispozici na githubu: refaktoring-iteratoru

  1. Démonické procesy v Ruby
  2. Démonické procesy a Rails
  3. Deployment Ruby On Rails jednoduše

Zverejnené 22.2.2009
v kategórii Ruby.

Nálepky:
, ,

Autor článku

Honza Štěrba, http://honzasterba.cz

Su z Moravy. Pracuju v Sun Microsystems. Programování mě baví.

Komentáre

Timy, 22.2.2009, 21:27

Nevím jak vy Rubysti, ale my Lispaři říkáme “Closure” česky “uzávěr” :-) . IMHO je to hezčí než ten anglický výraz.

Honza Sterba, 22.2.2009, 22:26

Timy: Děkuji za info, přeloženo, zapamatováno.

Vyjadri sa

Tvůj komentář se zobrazí, až ho některý z adminů schválí. Zveřejňovat budeme pouze hodnotné komentáře, které se přímo týkají tématu.


O projekte

Tento projekt vznikol, pretože všetky odborné weby sajú a my sme tým pádom nemali kde publikovať svoje články.