:: Topo :: doCoding :: Linguagens de Programação :: Ruby :: FORPC101 ::
Week-05
Aprendizagem
- 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.
- Cada objecto “nasce” com um conjunto inato de habilidades/capacidades ou características.
- class() - método que retorna o nome da classe desse objecto.
- object_id() - método inato que retorna o identificador unívoco desse objecto.
- respond_to?() - método útil para determinar se um objecto responde ou não a um certo método.
- É possível instanciar um novo objecto através do método new() ou através de construtores literais.
- Existe garbage collector, de tipo mark-and-sweep conservativo.
- Existem métodos de instância e métodos de classe.
- 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.
- 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
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.
- 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.