aluno: Thiago Brandão Damasceno
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 03/11/2008 (14)
Introdução
O código do lab 2 foi portado para a linguagem Ruby (incluindo o código de testes).
Desenvolvimento
Durante todo o projeto, foi utilizada a versão 1.8.6 da linguagem interpretada Ruby, conjuntamente com o IDE NetBeans.
Classe Professor
Poucas mudanças na classe Professor. O método corrigir_relatorios ficou mais conciso.
class Professor def initialize(relaqueue) @relaqueue = relaqueue end def corrigir_relatorios puts while rela = @relaqueue.dequeue puts "#{rela.aluno.nome} : #{corrigir_relatorio(rela)}" end end private def corrigir_relatorio(rela) (rela.qualidade + rela.originalidade + rand)/3*10 end end
Classe Relaqueue
Mantíve o mesmo nome da classe anterior, apesar dessa queue poder carregar qualquer tipo de estrutura de dado. O nome seria então apenas um indicativo de seu uso. Retirei também o requisito de no máximo 10 relatórios na fila, para manter a simplicidade do código. Aproveitei os métodos push e shift de Array para simular queue e dequeue.
class Relaqueue attr_reader :rela_array def initialize @rela_array = [] end def queue(rela) @rela_array.push(rela) end def dequeue @rela_array.shift end end
Classe Relatorio
Classe simples, apenas carrega três atributos e métodos de leitura.
class Relatorio attr_reader :aluno, :qualidade, :originalidade def initialize(qualidade, originalidade, aluno) @qualidade = qualidade @originalidade = originalidade @aluno = aluno end end
Classe Aluno
Nenhuma mudança significativa nesta classe. Poderia ter usado attr_writer no lugar do método comportamento=.
class Aluno attr_reader :nome, :comportamento, :conhecimento def initialize(nome, relaqueue) @conhecimento = 0 @comportamento = Esforcado.new(self) @nome = nome @relaqueue = relaqueue end def comportamento=(comportamento) @comportamento = comportamento end def inteligencia @comportamento.inteligencia end def dedicacao @comportamento.dedicacao end def faz_e_entrega_relatorio qualidade = (2*dedicacao + inteligencia)/3.0 originalidade = (dedicacao + 2*inteligencia)/3.0 @relaqueue.queue(Relatorio.new(qualidade, originalidade, self)) @conhecimento += 1 end end
Classes de comportamento
Devido à tipagem dinâmica de Ruby, a classe Comportamento tornou-se desnecessária. Lembremos que ela foi utilizada para delegar e possibilitar o uso de poliformismo (conceito que perde sentido em Ruby).
Apesar de uma pequena parte do código tornar-se redundante (@aluno = aluno…), cada comportamento agora é uma classe única.
Em algumas classes, deixou-se de usar os attr_reader e attr_writer diretamente devido à característica randômica de alguns atributos. De fato, esses métodos do tipo getter e setter, quando definidos "manualmente", servem para controlar a leitura/escrita dos atributos de uma determinada classe (encapsulamento).
#definições para classes de comportamento class Burro attr_reader :inteligencia, :dedicacao def initialize(aluno) @aluno = aluno @inteligencia = 0.0 @dedicacao = 0.0 end end class Esforcado attr_reader :inteligencia, :dedicacao def initialize(aluno) @aluno = aluno @inteligencia = 0.5 @dedicacao = 1.0 end end class Imprevisivel def initialize(aluno) @aluno = aluno @inteligencia = rand @dedicacao = rand end def dedicacao @dedicacao = rand end def inteligencia @inteligencia = rand end end class Pemba def initialize(aluno) @aluno = aluno @inteligencia = rand*0.5 @dedicacao = rand*0.5 end def inteligencia @inteligencia = rand*0.5 end def dedicacao @dedicacao = rand*0.5 end end class SafoPreguicoso attr_reader :inteligencia def initialize(aluno) @aluno = aluno @inteligencia = 1.0 @dedicacao = rand*0.5 end def dedicacao @dedicacao = rand*0.5 end end class Summa attr_reader :inteligencia, :dedicacao def initialize(aluno) @aluno = aluno @inteligencia = 1.0 @dedicacao = 1.0 end end
Tipos de Aluno
Todos os tipos de Aluno são subclasses, como no lab original. Duas características interessantes:
1) Quando chamamos o construtor da superclasse utilizando a palavra-chave super, ao contrário de Java, Ruby trata como se fosse uma expressão executável que reinvoca o método da superclasse, ignorando a definição na classe corrente. Em Java, super é uma referência à superclasse.
2) Não é necessário explicitar na definição do método que ele poderá lançar uma exceção. Escolhí a exceção RuntimeError por achar mais condizente com o tipo de erro que deverá ser processado (digitação de um comportamento que não é válido para determinado tipo de aluno). Somente utilizei a palavra-chave raise seguida de uma string que irá ser chamada na variável global $! no Simulador, ao ocorrer o erro.
class AlunoITA < Aluno def initialize(nome,relaqueue) super(nome, relaqueue) end def comportamento=(comportamento) if comportamento.is_a?(Burro) raise "Aluno do ITA não pode ser burro!" end super comportamento = comportamento end end class AlunoUSP < Aluno def initialize(nome,relaqueue) super(nome, relaqueue) end def comportamento=(comportamento) if (comportamento.is_a?(Summa) || \ comportamento.is_a?(SafoPreguicoso)) raise "Aluno da USP não pode ser summa ou safo preguiçoso!" end super comportamento= comportamento end end class AlunoUnicamp < Aluno def initialize(nome,relaqueue) super(nome, relaqueue) end def comportamento=(comportamento) if (comportamento.is_a?(Summa) || \ comportamento.is_a?(SafoPreguicoso)) raise "Aluno da Unicamp não pode ser summa ou safo preguiçoso!" end super comportamento= comportamento end end
Simulador
Uma das características mais importantes em Ruby é a linguagem ser totalmente orientada à objetos. Isso significa maior liberdade em termos de organização de código. Em relação à Java, não é necessário que cada arquivo seja uma classe específica. Em Ruby, podemos escrever um bloco de códigos que implicitamente, estende a classe Object, que já é carregada no início da execução do interpretador. Assim, no meu exemplo, a classe Simulador é somente uma série de códigos que irão ser executados pelo interpretador, sem a necessidade de instanciar um objeto Simulador, por exemplo.
A classe não sofreu muitas mudanças, apenas modifiquei algumas partes para aproveitar a concisão da sintaxe de Ruby. Para retirar o caractere de nova linha, utilizei o método chomp!, para evitar erros quando da comparação nos if/else. No bloco rescue, mandei imprimir na sáida padrão o valor da variável global !$, que guarda a informação da exceção lançada (nesse caso, a string customizada nas definições de tipo de aluno).
require 'classes' print "Digite o número de alunos: " alunos = gets alunos = alunos.to_i rela_queue = Relaqueue.new aluno_array = [] professor = Professor.new(rela_queue) for i in 1..alunos print "\nDigite o nome do #{i}° aluno: " nome = gets nome.chomp! print "Digite o nome da instituição em que estuda: " inst = gets inst.chomp! print "Digite o tipo do aluno: " tipo = gets tipo.chomp! if inst.eql?("ITA") aluno_array.push(AlunoITA.new(nome,rela_queue)) elsif inst.eql?("USP") aluno_array.push(AlunoUSP.new(nome,rela_queue)) elsif inst.eql?("Unicamp") aluno_array.push(AlunoUnicamp.new(nome,rela_queue)) else aluno_array.push(AlunoUSP.new(nome,rela_queue)) end begin if tipo.eql?("burro") aluno_array.last.comportamento = Burro.new(aluno_array.last) elsif tipo.eql?("esforçado") aluno_array.last.comportamento = Esforcado.new(aluno_array.last) elsif tipo.eql?("imprevisível") aluno_array.last.comportamento = Imprevisivel.new(aluno_array.last) elsif tipo.eql?("pemba") aluno_array.last.comportamento = Pemba.new(aluno_array.last) elsif tipo.eql?("safo preguiçoso") aluno_array.last.comportamento = SafoPreguicoso.new(aluno_array.last) elsif tipo.eql?("summa") aluno_array.last.comportamento = Summa.new(aluno_array.last) else aluno_array.last.comportamento = Esforcado.new(aluno_array.last) end rescue print "Exceção lançada: #{$!}" end end aluno_array.each {|aluno| aluno.faz_e_entrega_relatorio} professor.corrigir_relatorios puts 'Fim do programa'
Resultados do simulador
Resultados utilizando o mesmo exemplo do lab2. Também foi feito o teste da exceção
Classe de Testes
A classe de teste á praticamente idêntica à classe feita em Java, exceto pela sintaxe de lançamento de exceção e algumas asserções. Logo abaixo segue o resultado do teste.
require 'classes' require 'test/unit' class ClasseDeTestes < Test::Unit::TestCase def test_funciona_como_o_lab1 queue = Relaqueue.new prof = Professor.new(queue) aluno1 = Aluno.new("John Smith", queue) aluno2 = Aluno.new("Mark Smith", queue) aluno3 = Aluno.new("Joseph Smith", queue) aluno4 = Aluno.new("Robert Smith", queue) begin aluno3.comportamento = SafoPreguicoso.new(aluno3) aluno4.comportamento = SafoPreguicoso.new(aluno4) rescue fail("Não deveria lançar erro!") end assert_equal(0.5, aluno1.inteligencia, 0.01) assert_equal(1, aluno1.dedicacao, 0.01) assert_equal(1.0, aluno3.inteligencia, 0.01) assert(aluno3.dedicacao != aluno3.dedicacao) assert(aluno3.dedicacao < 0.5) aluno1.faz_e_entrega_relatorio rela1 = queue.dequeue assert_equal(aluno1, rela1.aluno) assert(rela1.aluno.nome.eql?("John Smith")) assert(rela1.qualidade < 0.9) assert(rela1.qualidade > 0.8) assert(rela1.originalidade < 0.7) assert(rela1.originalidade > 0.6) aluno2.faz_e_entrega_relatorio aluno3.faz_e_entrega_relatorio aluno4.faz_e_entrega_relatorio prof.corrigir_relatorios end def test_os_comportamentos_funcionam_como_esperado queue = Relaqueue.new aluno1 = Aluno.new("John",queue) summa = Summa.new(aluno1) pemba = Pemba.new(aluno1) imprevisivel = Imprevisivel.new(aluno1) safo = SafoPreguicoso.new(aluno1) esforcado = Esforcado.new(aluno1) burro = Burro.new(aluno1) assert_equal(0.5, esforcado.inteligencia, 0.01) assert_equal(1, esforcado.dedicacao, 0.01) assert_equal(0.0, burro.inteligencia, 0.01) assert_equal(0.0, burro.dedicacao, 0.01) assert_equal(1.0, summa.inteligencia, 0.01) assert_equal(1.0, summa.dedicacao, 0.01) assert_equal(1.0, safo.inteligencia, 0.01) assert(safo.dedicacao != safo.dedicacao) assert(safo.dedicacao < 0.5) assert(imprevisivel.inteligencia != imprevisivel.inteligencia) assert(imprevisivel.inteligencia < 1.0) assert(imprevisivel.dedicacao != imprevisivel.dedicacao) assert(imprevisivel.dedicacao < 1.0) assert(pemba.inteligencia != pemba.inteligencia) assert(pemba.inteligencia < 0.5) assert(pemba.dedicacao != pemba.dedicacao) assert(pemba.dedicacao < 0.5) end def test_as_subclasses_de_aluno_tem_restricoes queue = Relaqueue.new aluno_ita = AlunoITA.new("John Smith", queue) aluno_usp = AlunoUSP.new("Mark Smith", queue) aluno_unicamp = AlunoUnicamp.new("Joseph Smith", queue) begin aluno_ita.comportamento = SafoPreguicoso.new(aluno_ita) aluno_ita.comportamento = Summa.new(aluno_ita) aluno_usp.comportamento = Burro.new(aluno_usp) aluno_unicamp.comportamento = Burro.new(aluno_unicamp) rescue fail("Não deveria lançar erro!") end begin aluno_ita.comportamento = Burro.new(aluno_ita) fail("Deveria lançar erro!") rescue end begin aluno_usp.comportamento = SafoPreguicoso.new(aluno_usp) fail("Deveria lançar erro!") rescue end begin aluno_unicamp.comportamento = Summa.new(aluno_unicamp) fail("Deveria lançar erro!") rescue end end end
Resultados dos testes
Download do projeto (Netbeans)
Conclusão
Ruby é, tal como idealizada por Matz, uma linguagem que oferece um entendimento ao nível do vocabulário quase humano. Isso é demonstrado por sua rica sintaxe, que permite uma maior flexibilidade do programador, facilidade na escrita do código, além da concisão na sintaxe. Essa flexibilidade, no entanto, pode exigir mais atenção do mesmo, principalmente se formos considerar o dinamismo da linguagem, que requer verificações de (possíveis) inconsistências a mais.
Em relação às características de Orientação a Objetos, como já explicitado anteriormente, trata-se de uma linguagem totalmente orientada à objetos (em suma, tudo é objeto). Algumas facilidades, no entanto, podem fazer com que o programador "esqueça" dos princípios básicos de OO como encapsulamento (o uso indiscriminado de attr, é um exemplo).
Ruby é uma ótima linguagem para tarefas do dia-dia que requer pouco processamento (já que se trata de uma linguagem interpretada) e pode servir como uma alternativa para os já conhecidos Bash e Python. Entretanto, é ruim para processamentos mais pesados, cuja tarefa delegaria para linguagens compiladas como C/C++. Comparada com Java, é uma linguagem que possui uma (aparente?) padronização melhor, o que evita a perda de tempo ao analisar os inúmeros possíveis caminhos de codificação, muito comum em Java. Além disso, é visível o grau de simplicidade da sintaxe de Ruby, se compararmos com a verbosidade presente nos códigos escritos em Java.