zl程序教程

您现在的位置是:首页 >  后端

当前栏目

[Ruby] Blocks -- Ex

ruby -- blocks EX
2023-09-14 08:59:21 时间

Iterating with Blocks

Let's build a Library class that will manage our growing collection of games. We've already written a listmethod that prints the names of all our games, but it uses an ugly for loop to iterate the list. Refactor it to use each with a block instead.

#seed.rb

GAMES = [
  Game.new('Contra', year: 1987, system: 'NES'),
  Game.new('Civilization', year: 1991, system: 'PC'),
  Game.new('The Legend of Zelda', year: 1986, system: 'NES'),
  Game.new('Mega Man X2', year: 1995, system: 'SNES'),
  Game.new('Super Metroid', year: 1994, system: 'SNES'),
  Game.new('Sim City 2000', year: 1993, system: 'PC'),
  Game.new('Starcraft', year: 1998, system: 'PC')
]
#game.rb

class Game
  attr_accessor :name, :year, :system
  attr_reader :created_at
 
  def initialize(name, options={})
    self.name = name
    self.year = options[:year]
    self.system = options[:system]
    @created_at = Time.now
  end
end
class Library
  attr_accessor :games

  def initialize(games = [])
    self.games = games
  end

  def list
    for i in 0...(games.length)
      game = games[i]
      puts game.name
    end
  end
end

Answer:

class Library
  attr_accessor :games

  def initialize(games = [])
    self.games = games
  end

  def list
    games.each do |game|
      puts game.name
    end    
  end
end

 

Yielding to Blocks

We'd like to be able to operate on our games by system. Implement an each_on_system method that iterates over our games using each and yields to a block for every game on the requested system. To test that it's working, we'll call each_on_system with a simple block that prints a message for every Super Nintendo game in our library. See the example.rb below.

library = Library.new(GAMES)
library.each_on_system("SNES") { puts "Found a Super Nintendo game" }

Answer:

class Library
  attr_accessor :games

  def initialize(games = [])
    self.games = games
  end

  def each_on_system(system)
    self.games.each do |game|
      yield if game.system == system
    end
  end
end

 

Passing Arguments to Blocks

Our each_on_system method is working, but it's not very useful unless the block has access to each game that we find. Modify each_on_system to pass the Game object into the block so we can print its name.

class Library
  attr_accessor :games

  def initialize(games = [])
    self.games = games
  end

  def each_on_system(system)
    games.each { |game| yield game if game.system == system}  
  end
end

 

Returning Values from Blocks

Earlier we wrote a list method that prints the name of each game in our library. We can make the output formatting more flexible by allowing a block to be passed to the list method. We'll yield each game to the block and allow the block to format and return a string for us to display. Modify the list method to yield to a block and print whatever the block returns.

library = Library.new(GAMES)
library.list { |game| "#{game.name} (#{game.system}) - #{game.year}" }

Answer:

class Library
  attr_accessor :games

  def initialize(games = [])
    self.games = games
  end

  def list
    games.each do |game|
      puts yield game
    end
  end
end

 

Using Enumerable

Let's add the power of Ruby's Enumerable module to our game library. Implement an each method that yields each game in the library. Finally, include the Enumerable module so that we'll be able to call methods like select and collect on our library.

library = Library.new(GAMES)
library.select { |game| game.year == 1986 }
library.collect { |game| game.system }

Answer:

class Library
  include Enumerable
  attr_accessor :games

  def initialize(games = [])
    self.games = games
  end

  def each
    self.games.each {|game| yield game}
  end
end

 

Refactoring with Blocks

Now that our library is complete, let's play some games! A friend has given us his Emulator class to use, and we've implemented methods to play a game and grab a screenshot. But look at all that duplicated code in play and screenshot. Refactor the duplication (the beginnew and rescue parts) into a private method called emulate that handles the emulator setup and exception handling and yields the emulator instance to a block.

class Emulator
  def initialize(system)
    # Creates an emulator for the given system
  end
 
  def play(game)
    # Runs the given game in the emulator
  end
 
  def start(game)
    # Loads the given game but doesn't run it
  end
 
  def screenshot
    # Returns a screenshot of the currently loaded game
  end
end
class Game
  attr_accessor :name, :year, :system
  attr_reader :created_at

  def initialize(name, options={})
    self.name = name
    self.year = options[:year]
    self.system = options[:system]
    @created_at = Time.now
  end

  def play
    begin
      emulator = Emulator.new(system)
      emulator.play(self)
    rescue Exception => e
      puts "Emulator failed: #{e}"
    end
  end

  def screenshot
    begin
      emulator = Emulator.new(system)
      emulator.start(self)
      emulator.screenshot
    rescue Exception => e
      puts "Emulator failed: #{e}"
    end
  end
end

Answer:

class Game
  attr_accessor :name, :year, :system
  attr_reader :created_at

  def initialize(name, options={})
    self.name = name
    self.year = options[:year]
    self.system = options[:system]
    @created_at = Time.now
  end

  def play
    emulate do |emulator|
      emulator.play(self)
    end
  end

  def screenshot
    emulate do |emulator|
      emulator.start(self)
      emulator.screenshot
    end
  end
  
  private 
  
  def emulate
        emulator = Emulator.new(system)
        yield emulator
    rescue Exception => e
      puts "Emulator failed: #{e}"     
  end
  
end