Lab4 Thiago Brandão

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

figura_2.png
figura_3.png

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

figura_1.png

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.

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License