:: Topo :: doCoding :: Linguagens de Programação :: Ruby :: FORPC101 ::

Week-05

Aprendizagem

  1. Quando se desenha uma classe deve-se pensar nos objectos que serão criados nas instanciações dessa classe. Deve-se pensar na coisas que o objecto “conhece” - instance variables - e nas coisas que o objecto “faz” - methods.
  2. Cada objecto “nasce” com um conjunto inato de habilidades/capacidades ou características.
  3. class() - método que retorna o nome da classe desse objecto.
  4. object_id() - método inato que retorna o identificador unívoco desse objecto.
  5. respond_to?() - método útil para determinar se um objecto responde ou não a um certo método.
  6. É possível instanciar um novo objecto através do método new() ou através de construtores literais.
  7. Existe garbage collector, de tipo mark-and-sweep conservativo.
  8. Existem métodos de instância e métodos de classe.
  9. Quando se executa uma aplicação Ruby o objecto main de classe Object é automaticamente criado. Este objecto disponibiliza automaticamente os métodos de classe Kernel.
  10. As variáveis só por si não têm qualquer tipo associado, nem são só por si objectos. Apenas são referências a objectos.
An object is an entity that serves as a container for data and also controls access to the data. Associated with an object is a set of attributes, which are essentially no more than variables belonging to that object. Also associated with an object is a set of functions that provide an interface to the functionality of the object, called methods. - Hal Fulton

Exercícios

Exercício 1

exercise-01.rb

=begin
Write a class called Dog, that has name as an instance variable and the following methods:
bark(), eat(), chase_cat()
I shall create the Dog object as follows:
d = Dog.new('Leo')
=end
 
class Dog
 
  def initialize(name)
    @name = name
    puts 'I\'m a ' + self.class.to_s + ' and my name is ' + @name
  end
 
  def capable_of(capability)
    puts 'I\'m ' + @name + ' and I can ' + capability + '...' unless !self.respond_to?(capability)
  end
 
  def bark
  end
 
  def eat
  end
 
  def chase_cat
  end
 
  def method_missing(method_name, *arguments)
    puts 'I\'m sorry, but I don\'t understand what ' + method_name.to_s + ' means...'
  end
 
end
 
 
d = Dog.new('Leo')
 
capability = 'bark'
d.capable_of(capability)
 
capability = 'eat'
d.capable_of(capability)
 
capability = 'chase_cat'
d.capable_of(capability)
 
capability = 'chase_car'
d.capable_of(capability)
 
d.chase_car

Exercício 2

exercise-02.rb

=begin
Write a Rectangle class. I shall use your class as follows:
 
r = Rectangle.new(23.45, 34.67)
puts 'Area is = ' + r.area().to_s
puts 'Perimeter is = ' + r.perimeter.to_s
=end
 
class Rectangle
 
  def initialize(width = 0.0, height = 0.0)
    @width  = width
    @height = height
  end
 
  def area
    @width * @height
  end
 
  def perimeter
    @width * 2 + @height * 2
  end
 
  def method_missing(method_name, *arguments)
    puts 'I\'m sorry, but I don\'t understand what ' + method_name.to_s + ' means...'
  end
 
end
 
r = Rectangle.new(23.45, 34.67)
puts 'Area is = ' + r.area().to_s
puts 'Perimeter is = ' + r.perimeter.to_s

Questionário

Tuesday's Simple Game

Este exercício começa com a elaboração do conjunto de requisitos e especificação funcional do draft publicado pelo Satish nesta semana.

Foi publicada uma página no Course Wiki para documentar o nosso progresso. Actualmente existem 3 especificações diferentes, elaboradas sobre o mesmo conjunto de requisitos - mais técnicos do que o draft.

Resolvi desenvolver o código com a metodologia TDD, para o caso da SingleClassGame. E apreciei bastante a “evolução” e “segurança” dadas pela metodologia no desenrolar da codificação. Para já, ainda com algumas pontas por limar, obtive este código:

test_single_class.rb

=begin
=end
 
require 'test/unit'
require 'single_class'
 
def rand number = 1
  4
end
 
class TestSingleClass < Test::Unit::TestCase
 
  def setup
    @game = SingleClass.new
    @input_choices = [0, 1, 1, 3, 4, 5, 5, 6]
    @output_hits   = [0, 0, 0, 0, 1, 2, 2, 3]
    @output_tries  = [1, 2, 3, 4, 5, 6, 7, 8]
  end
 
  def teardown
  end
 
  def test_default_game_creation
    assert_instance_of SingleClass, @game, 'SingleClass object created.'
  end
 
  def test_default_game_creation_settings
    assert_equal 7, @game.number_of_slots, 'Default Number of Slots is 7.'
    assert_equal 3, @game.selection_size , 'Default Number of Selected Slots is 3.'
    assert_equal 5, @game.allowed_chances, 'Default Allowed Chances is 5.'
  end
 
  def test_game_slot_is_hit
    assert_not_equal true, @game.slot_hit?(1), '1 doesn\'t match any of the default selected slots.'
    assert_equal     true, @game.slot_hit?(5), '5 matches one of the default selected slots.'
  end
 
  def test_game_creation_settings_using_optionals
    game = SingleClass.new(9)
 
    assert_equal 9, game.number_of_slots, 'Number of Slots is 9.'
    assert_equal 3, game.selection_size , 'Default Number of Selected Slots is 3.'
    assert_equal 7, game.allowed_chances, 'Default Allowed Chances = (Slots - Selected) - 1.'
  end
 
  def test_game_creation_with_explicit_settings
    game = SingleClass.new(9, 4, 5)
 
    assert_equal 9, game.number_of_slots, 'Number of Slots is 9.'
    assert_equal 4, game.selection_size , 'Number of Selected Slots is 4.'
    assert_equal 5, game.allowed_chances, 'Allowed Chances is 5.'
  end
 
  def test_game_creation_with_invalid_settings
    game_invalid_select_slots = SingleClass.new(5, 6, 7)
 
    assert_equal 5, game_invalid_select_slots.number_of_slots, 'Number of Slots is 5.'
    assert_equal 5, game_invalid_select_slots.selection_size , 'Number of Selected Slots <= Number of Slots.'
    assert_equal 7, game_invalid_select_slots.allowed_chances, 'Allowed Chances is 7.'
 
    game_invalid_allowed_chances = SingleClass.new(6, 3, 2)
 
    assert_equal 6, game_invalid_allowed_chances.number_of_slots, 'Number of Slots is 6.'
    assert_equal 3, game_invalid_allowed_chances.selection_size , 'Number of Selected Slots is 3.'
    assert_equal 3, game_invalid_allowed_chances.allowed_chances, 'Allowed Chances => Selected Slots.'
  end
 
  def test_game_counts_hits
    assert_equal 0, @game.number_of_hits, 'Starts at 0.'
 
    @game.check_slot 5
    assert_equal 1, @game.number_of_hits, 'Increments on each hit.'
 
    @game.check_slot 0
    assert_equal 1, @game.number_of_hits, 'Remains as were on each non-hit.'
  end
 
  def test_game_counts_unique_hits
    test_game_counts_hits
 
    @game.check_slot 5
    assert_equal 1, @game.number_of_hits, 'Remains as were on each non-hit or duplicated hit.'
  end
 
  def test_choice_is_valid
    assert_not_equal true, @game.is_valid?(8) , 'Choice < Total Number of Slots is Invalid.'
    assert_not_equal true, @game.is_valid?(-1), 'Choice < 0 is Invalid.'
    assert_equal     true, @game.is_valid?(3) , 'Valid Choice if >= 0 and < Total Number of Slots.'
  end
 
  def test_game_play
    @input_choices.each_with_index do | choice, index |
      @game.check_slot choice.to_i
      assert_equal @output_hits[index],
                   @game.number_of_hits,
                   'Checking GamePlay: Hits...'
      assert_equal @output_tries[index],
                   @game.number_of_tries,
                   'Checking GamePlay: Tries...'
    end
  end
 
end
O código foi desenvolvido no “ritmo” Red-Green-Refactor, sendo este o culminar de diversas “iterações” sobre o código dos UnitTests e da classe propriamente dita.

single_class.rb

=begin
=end
 
def rules
  puts ('*' * 5) + ' GameRules ' + ('*' * 25)
  puts 'The goal of the game is to guess the computer-selected "slots" in the minimum number of tries.'
  puts
  puts 'A "slot" is a number from 0 to 7 (by default) and the computer selects 3 (by default) at random.'
  puts 'By default, the player has 5 attempts to guess.'
  puts
  puts 'These default values - number of slots, selected slots, allowed tries - can be defined at start.'
  puts '-' * 20
  print 'Enter "D" or simply press Return for default settings, or "U" to define game settings: '
end
 
class SingleClass
 
  attr_reader :number_of_slots, :selection_size
  attr_reader :allowed_chances
  attr_reader :number_of_hits, :number_of_tries
 
  # Define Constant Messages, as Symbols.
  @@WINNER_MESSAGE      = :'You won! Congratulations.'
  @@LOOSER_MESSAGE      = :'You loose! All allowed chances were used.'
  @@INVALID_SLOT_NUMBER = :'Cannnot accept that choice as a Valid Slot Number!'
  @@INPUT_CHOICE        = :'Enter your choice: '
 
  def initialize(slots = 7, size = 3, allowed_chances = (slots - size) + 1)
    @number_of_slots     = slots
    @selection_size      = (size <= slots)           ? size            : slots
    @allowed_chances     = (allowed_chances >= size) ? allowed_chances : size
 
    @selected_slot_start = rand(slots - size + 1)
 
    @number_of_hits      = 0
    @number_of_tries     = 0
    @slots_attempted     = []
  end
 
  def slot_hit? number
    number >= @selected_slot_start                       and
    number <= @selected_slot_start + @selection_size - 1
  end
 
  def is_valid? number
    number >= 0 and number < number_of_slots
  end
 
  def check_slot number
    # If @number_of_tries > @allowed_chances then
    # Raise Exception: Max. allowed chances used!
    @number_of_tries += 1
    if slot_hit? number then
      @number_of_hits += 1
      @slots_attempted << number
    end unless @slots_attempted.include? number
    # If @number_of_hits > @selection_size then
    # Raise Exception: All slots have been hit already!
  end
 
  def play
    while @number_of_hits  < @selection_size  and
          @number_of_tries < @allowed_chances
      print @@INPUT_CHOICE
      choice = gets.chomp.to_i
      current_number_of_hits = @number_of_hits
      current_number_of_tries = @number_of_tries
      check_slot choice if is_valid? choice
      if current_number_of_tries == @number_of_tries
        puts @@INVALID_SLOT_NUMBER
      else
        puts "Hit ##{@number_of_hits.to_s} - Miss ##{@number_of_tries - @number_of_hits}"
      end
    end
 
    puts (@number_of_hits == @selection_size) ? @@WINNER_MESSAGE : @@LOOSER_MESSAGE
    puts 'Your Rating: ' + format("%03.2f", (@number_of_hits * 100.0 / @number_of_tries)) + '%.'
  end
 
end

Por fim, o código de interactividade com o utilizador, que no entanto não foi desenvolvido com base em TDD - o que é pena:

single_class_play.rb

=begin
=end
 
require 'single_class'
 
rules
game_mode = gets.chomp
 
valid_settings = false
while not valid_settings
 
  valid_settings = case game_mode.upcase
 
    when '', 'D':
      number_of_slots = 7
      selected_slots  = 3
      allowed_tries   = (number_of_slots - selected_slots) + 1
      true
 
    when 'U':
      # Must not accept <= 0
      print 'Total Number of Slots: '
      number_of_slots = gets.chomp.to_i
 
      # Must not accept <= 0
      print 'Computer Selected Slots: '
      selected_slots = gets.chomp.to_i
      if selected_slots > number_of_slots then
        puts 'Selected Slots must be lesser or equal to Total Number of Slots!'
        false
      else
 
        # Must not accept <= 0
        print 'Maximum Allowed Tries: '
        allowed_tries = gets.chomp.to_i
        if allowed_tries < selected_slots then
          puts 'Allowed Tries must be higher or equal to Selected Slots!'
          false
        else
          true
        end
 
      end
 
    else
      print 'Invalid GameMode! Enter "D" or "U", please: '
      game_mode = gets.chomp
      false
  end
end
 
game = SingleClass.new(number_of_slots, selected_slots, allowed_tries)
game.play

Referências

Considerações

Pontos Altos

Esta secção regista o que considero serem os pontos altos da linguagem Ruby quando comparada com outras linguagens que conheço.

  1. Em Ruby tudo é considerado um Objecto.

Pontos Baixos

Esta secção regista o que considero serem os pontos baixos da linguagem Ruby quando comparada com outras linguagens que conheço.

 
docoding/languages/ruby/forpc101/week-05.txt · Modificado em: 2008/09/25 10:09 por straider     Voltar ao topo