Lab4 Diego Alvarez

aluno: Diego Alvarez
ano/sem: 2008/2o.

Introdução

Este laboratório tem como objetivo a apresentação de uma linguagem orientada a objetos (de tipagem) dinâmica. Através dessa experiência, será realizada uma breve comparação entre os paradigmas (de programação orientada a objetos) estático e dinâmico (java e ruby, respectivamente).

Nesta prática de laboratório foi abordada uma introdução à Linguagem Ruby através da utilização de tal linguagem na implementação da mesma tarefa pedida no Lab 2 (solicitada anteriormente em Java).

Como recomendado, o código da aplicação foi escrito em 3 arquivos, a saber: "classes.rb" (contendo as classes relativas ao Diagrama De Classes do Lab 2), "simulador.rb" (contendo o código do simulador) e "testes.rb" (contendo o código de testes da aplicação).
Ao fim do relatório, pode ser encontrado um link para os arquivos da implementação.

Desenvolvimento

Código referente às classes de alunos:

Código enxuto é uma das características mais marcantes do Ruby. Nas classes abaixo podemos perceber um exemplo claro disso:
A declaração de getters e setters para atributos de uma classe pode ser feita através da utilização das abreviações attr_reader e attr_writer (assim como attr_accessor, como veremos posteriormente), seguido do nome do atributo.
A utilização desse recurso diminui bastante o número de linhas de códigos da aplicação, comparativamente com Java.
Além disso, no código abaixo percebe-se também a facilidade em declarar sintaticamente a herança entre classes, através do operador '<'.

Ainda no código referente às classes de alunos, percebemos que é possível lançar uma exceção sem necessitar instanciá-la.

class Aluno
  attr_reader :conhecimento, :nome
  attr_writer :comportamento
 
  def initialize(aluno_nome, queue)
    @nome = aluno_nome
    @comportamento = Esforcado.new(self)
    @queue = queue
    @conhecimento = 0
  end
 
  def inteligencia
    @comportamento.inteligencia
  end
 
  def dedicacao
    @comportamento.dedicacao
  end
 
  def faz_e_entrega_relatorio
    qualidade = (2*dedicacao + inteligencia)/3
    originalidade = (dedicacao + 2*inteligencia)/3
 
    relatorio = Relatorio.new(qualidade, originalidade, self)
    @queue.queue(relatorio);
 
    @conhecimento = @conhecimento + 1 
  end  
end
 
class AlunoITA < Aluno
  def comportamento=(comportamento)
    if comportamento.class == Burro 
      raise Exception, "Um Aluno do ITA nao pode ser Burro"
    end
    @comportamento = comportamento
  end
end
 
class AlunoUSP < Aluno
  def comportamento=(comportamento)
    if comportamento.class == Summa
      raise Exception, "Um Aluno da USP nao pode ser Summa"
    elsif comportamento.class == SafoPreguicoso
      raise Exception, "Um Aluno da USP nao pode ser SafoPreguicoso"
    end
    @comportamento = comportamento
  end
end
 
class AlunoUnicamp < Aluno
  def comportamento=(comportamento)
    if comportamento.class == Summa
      raise Exception, "Um Aluno da Unicamp nao pode ser Summa"
    elsif comportamento.class == SafoPreguicoso
      raise Exception, "Um Aluno da Unicamp nao pode ser SafoPreguicoso"
    end
    @comportamento = comportamento
  end
end

Código referente às classes de comportamento:
Nos códigos das classes abaixo, fica ainda mais claro que anteriormente a omissão do comando 'return' na implementacao dos métodos, assim como a omissão do tipo de retorno, uma vez que a tipagem é dinâmica.

Nessas classes, referentes aos comportamentos, fica explicitada uma outra diferença entre as linguagens Java e Ruby, que é a existência de classe abstrata na primeira e inexistência na segunda.
Perceba que isso faz sentido, uma vez que em uma linguagem estática é preciso de um artíficio que garanta o polimorfismo, enquanto na linguagem dinâmica a flexibilidade é maior devido ao fato da determinação do tipo da classe ser feita em tempo de execução.

No entanto, pessoalmente, em ruby (ou talvez fosse mais correto falar, em linguagens dinâmicas), sinto falta de um artifício que garanta que o desenvolvedor vá implementar determinado método de um modo específico, sem necessitar de extensiva documentação do projeto, como é feito em Java através de interfaces e classes abstratas.

class Comportamento 
  def initialize(aluno)
    @aluno = aluno
  end
end
 
class Summa < Comportamento
  def inteligencia
    1
  end
 
  def dedicacao
    1
  end
end
 
class Pemba < Comportamento
  def inteligencia
    0.5*rand()
  end
 
  def dedicacao
    0.5*rand()
  end
end
 
class Imprevisivel < Comportamento
  def inteligencia
    rand()
  end
 
  def dedicacao
    rand()
  end
end
 
class Burro < Comportamento
  def inteligencia
    0
  end
 
  def dedicacao
    0
  end
end
 
class Esforcado < Comportamento
  def inteligencia
    0.5
  end
 
  def dedicacao
    1
  end
end
 
class SafoPreguicoso < Comportamento
  def inteligencia
    1
  end
 
  def dedicacao
    0.5*rand()
  end
end

Código referente à classe Professor:

Novamente, o código mostra-se mais conciso em ruby do que seria em Java, através da omissão de comandos de retorno e omissão de declaração de variáveis/atributos.

class Professor
  def initialize(rela_queue)
    @rela_queue = rela_queue
  end
 
  def corrigir_relatorio(relatorio)
    (relatorio.qualidade + relatorio.originalidade + rand())*10/3
  end
 
  def corrigir_relatorios
    relatorio = @rela_queue.dequeue
    while(relatorio != nil)
      puts "#{relatorio.aluno.nome}" + ": " + "#{corrigir_relatorio(relatorio)}"
      relatorio = @rela_queue.dequeue
    end
  end
end

Código referente à classe Relatorio:
attr_reader em ação novamente, assim como a omissão de declaração de variáveis/atributos, para redução de código.

class Relatorio
  attr_reader :qualidade;
  attr_reader :originalidade;
  attr_reader :aluno;
 
  def initialize(qualidade, originalidade, aluno)
    @qualidade = qualidade
    @originalidade = originalidade
    @aluno = aluno
  end
end

Código referente à classe RelaQueue:

class RelaQueue
  def initialize
    @rela_queue = Array.new()    
  end
 
  def queue(relatorio)
    @rela_queue.push(relatorio)
  end
 
  def dequeue
    if @rela_queue.length == 0
      return nil
    else
      element = @rela_queue[0]
      @rela_queue.delete_at(0);
      return element
    end
  end
end

Código em "simulador.rb", referente ao simulador citado na descrição da prática:
Pela experiência que obtive em observar programas escritos em Ruby, acredito que o código do meu simulador esteja talvez grande demais, embora acredite que tenha sido feito um bom trabalho relativo à usabilidade (interface com o usuário/tratamento de casos especiais) e legibilidade do código. Possivelmente, alguém mais experiente na linguagem seria capaz de reduzí-lo bastante.

require 'classes.rb'
require 'testes.rb'
 
class Simulador
  #DECLARACAO DE MENSAGENS
  Mens_cancelar = "cancelar"
  Mens_inserir_aluno_with_cancel = "Digite o nome do aluno que deseja inserir, ou digite 'cancelar' para sair do programa: "
  Mens_inserir_aluno = "Digite o nome do aluno que deseja inserir: "
  Mens_deseja_continuar = "Voce deseja continuar inserindo alunos (s ou n)?"
  Mens_tipo_aluno = "Escolha o tipo do aluno (Digite apenas o numero): "
  Mens_tipo1 = "1 - Burro"
  Mens_tipo2 = "2 - Pemba"
  Mens_tipo3 = "3 - Imprevisivel"
  Mens_tipo4 = "4 - Esforcado"
  Mens_tipo5 = "5 - SafoPreguicoso"
  Mens_tipo6 = "6 - Summa"
  Mens_erro_tipo = "Voce inseriu um tipo invalido. Digite um dos numeros correspondentes aos tipos listados acima: " 
  Mens_universidade = "Escolha a universidade do aluno (Digite apenas o numero): "
  Mens_uni1 = "1 - ITA"
  Mens_uni2 = "2 - USP"
  Mens_uni3 = "3 - Unicamp"
  Mens_erro_uni = "Voce inseriu uma universidade invalida. Digite um dos numeros correspondentes as universidades listadas acima: "
  Mens_fim_da_execucao = "Fim da simulacao!"
  #FIM DE DECLARACAO DE MENSAGENS
 
  def criar_aluno (nome_aluno, queue) 
    puts "#{Mens_tipo_aluno} \n #{Mens_tipo1} \n #{Mens_tipo2} \n #{Mens_tipo3} \n #{Mens_tipo4} \n #{Mens_tipo5} \n #{Mens_tipo6} \n"
 
    #validacao do tipo digitado
    tipo = gets().chomp()
    while(tipo.to_i < 1 || tipo.to_i > 6)
      puts Mens_erro_tipo
      tipo = gets().chomp()
    end
 
    puts "#{Mens_universidade} \n #{Mens_uni1} \n #{Mens_uni2} \n #{Mens_uni3} \n"
 
    #validacao da universidade digitada
    uni = gets().chomp()
    while(uni.to_i < 1 || uni.to_i > 3)
      puts Mens_erro_uni
      uni = gets().chomp()
    end
 
    aluno = AlunoITA.new(nome_aluno, queue)
 
    case uni.to_i
    when 1
      aluno = AlunoITA.new(nome_aluno, queue)
    when 2
      aluno = AlunoUSP.new(nome_aluno, queue)
    when 3
      aluno = AlunoUnicamp.new(nome_aluno, queue)
    end
 
    begin
      case tipo.to_i
      when 1
        aluno.comportamento = Burro.new(aluno)
      when 2
        aluno.comportamento = Pemba.new(aluno)
      when 3
        aluno.comportamento = Imprevisivel.new(aluno)
      when 4
        aluno.comportamento = Esforcado.new(aluno)
      when 5
        aluno.comportamento = SafoPreguicoso.new(aluno)
      when 6
        aluno.comportamento = Summa.new(aluno)
      end
    rescue Exception => ex
      puts "#{ex}"
      return nil
    end
 
    return aluno 
  end
 
  def iniciar
    puts Mens_inserir_aluno_with_cancel
    nome_aluno = gets().chomp()
 
    if nome_aluno != Mens_cancelar
      queue = RelaQueue.new()
      professor = Professor.new(queue)
 
      alunos = Array.new()
 
      aluno = criar_aluno(nome_aluno, queue)
      if (aluno != nil)
        alunos.push(aluno)
      end
 
      puts Mens_deseja_continuar
      continue = gets().chomp
      while(continue == "s" || continue == "S")
        puts Mens_inserir_aluno
        nome_aluno = gets().chomp()
 
        aluno = criar_aluno(nome_aluno, queue)
        if(aluno != nil)
          alunos.push(aluno)
        end
        puts Mens_deseja_continuar
        continue = gets().chomp
      end
 
      alunos.each do |al|
        al.faz_e_entrega_relatorio()
      end
 
      professor.corrigir_relatorios
    end  
    puts Mens_fim_da_execucao
  end  
end
 
simulador = Simulador.new()
simulador.iniciar

Exemplo de execução do código contido no arquivo "simulador.rb" (Copiado da saída do console do NetBeans):

Digite o nome do aluno que deseja inserir, ou digite 'cancelar' para sair do programa:
diego
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
1
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
1
Um Aluno do ITA nao pode ser Burro
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
rafael
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
6
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
2
Um Aluno da USP nao pode ser Summa
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
vitor
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
5
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
3
Um Aluno da Unicamp nao pode ser SafoPreguicoso
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
bernardo
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
3
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
1
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
flavio
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
5
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
1
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
thiago
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
6
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
1
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
gabriel
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
4
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
3
Voce deseja continuar inserindo alunos (s ou n)?
s
Digite o nome do aluno que deseja inserir:
felipe
Escolha o tipo do aluno (Digite apenas o numero):
1 - Burro
2 - Pemba
3 - Imprevisivel
4 - Esforcado
5 - SafoPreguicoso
6 - Summa
6
Escolha a universidade do aluno (Digite apenas o numero):
1 - ITA
2 - USP
3 - Unicamp
2
Um Aluno da USP nao pode ser Summa
Voce deseja continuar inserindo alunos (s ou n)?
n
bernardo: 4.160108545830583
flavio: 5.482623643796448
thiago: 8.241083671347516
gabriel: 7.64168439632989
Fim da simulacao!

Screenshot da execução acima (simulador):

output_netbeans.jpg

Código referente aos testes de unidade da aplicação (apenas "traduzidos" do Java (Lab2) para Ruby)
A não ser pela ausência da declaração dos tipos das variáveis, em Ruby, os códigos de testes escrito nas duas linguagens (Ruby e Java) são bastante semelhantes.

require 'test/unit'
require 'classes.rb'
 
class Testes <Test::Unit::TestCase
  def test_funciona_como_o_lab1
    queue = RelaQueue.new()
    professor = 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 Exception => ex
      fail "Nao deveria lancar erro!"
    end
 
    #teste de alunoesforcado
    assert_equal(0.5, aluno1.inteligencia, 0.01)
    assert_equal(1, aluno1.dedicacao, 0.01)
 
    #teste de alunosafo
    assert_equal(1.0, aluno3.inteligencia, 0.01)
    assert(aluno3.dedicacao != aluno3.dedicacao) # deve ser randomica
    assert(aluno3.dedicacao < 0.5)
 
    #alunos comecam a fazer os relas
    aluno1.faz_e_entrega_relatorio
 
    #roubamos o relatório do primeiro aluno para investigá-lo
    rela1 = queue.dequeue
    assert_equal(aluno1, rela1.aluno)
 
    assert(rela1.aluno.nome == "John Smith")
    assert(rela1.qualidade < 0.9)
    assert(rela1.qualidade > 0.8)
    assert(rela1.originalidade < 0.7)
    assert(rela1.originalidade > 0.6)
 
    #os outros continuam a fazer os relas
    aluno2.faz_e_entrega_relatorio
    aluno3.faz_e_entrega_relatorio
    aluno4.faz_e_entrega_relatorio
 
    #deve exibir a correção dos relas dos aluno2, aluno3, e aluno4, nesta ordem
    #pois "roubamos" o relatório do aluno1
    professor.corrigir_relatorios
  end
 
  def test_os_comportamentos_funcionam_como_especificado
    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)
 
    #teste de esforcado
    assert_equal(0.5, esforcado.inteligencia, 0.01)
    assert_equal(1, esforcado.dedicacao, 0.01)
 
    #teste de burro
    assert_equal(0.0, burro.inteligencia, 0.01)
    assert_equal(0.0, burro.dedicacao, 0.01)
 
    #teste de summa
    assert_equal(1.0, summa.inteligencia, 0.01)
    assert_equal(1.0, summa.dedicacao, 0.01)
 
    #teste de safo
    assert_equal(1.0, safo.inteligencia, 0.01)
    assert(safo.dedicacao != safo.dedicacao) # deve ser randomica
    assert(safo.dedicacao < 0.5)
 
    #teste de imprevisivel
    assert(imprevisivel.inteligencia != imprevisivel.inteligencia) #deve ser randomica
    assert(imprevisivel.inteligencia < 1.0)
    assert(imprevisivel.dedicacao != imprevisivel.dedicacao) #deve ser randomica
    assert(imprevisivel.dedicacao < 1.0)
 
    #teste de pemba
    assert(pemba.inteligencia != pemba.inteligencia) #deve ser randomica
    assert(pemba.inteligencia < 0.5)
    assert(pemba.dedicacao != pemba.dedicacao) #deve ser randomica
    assert(pemba.dedicacao < 0.5)
  end
 
  def test_as_sub_classes_de_aluno_tem_restricoes
    queue = RelaQueue.new()
    alunoITA = AlunoITA.new("John Smith", queue)
    alunoUSP = AlunoUSP.new("Mark Smith", queue)
    alunoUnicamp = AlunoUnicamp.new("Joseph Smith", queue)
 
    begin
      alunoITA.comportamento = (SafoPreguicoso.new(alunoITA))
      alunoITA.comportamento = (Summa.new(alunoITA))
      alunoUSP.comportamento = (Burro.new(alunoUSP))
      alunoUnicamp.comportamento = (Burro.new(alunoUnicamp))
    rescue Exception => ex
      fail "Nao deveria lancar erro!"
    end
 
    begin
      alunoITA.comportamento = (Burro.new(alunoITA))
      fail "Deveria lancar erro!"
    rescue Exception => ex
    end
 
    begin
      alunoUSP.comportamento = (SafoPreguicoso.new(alunoUSP))
      fail "Deveria lancar erro!"
    rescue Exception => ex
    end
 
    begin 
      alunoUnicamp.comportamento = (Summa.new(alunoUnicamp))
      fail "Deveria lancar erro!"
    rescue Exception => ex
    end
  end
end

Exemplo de execução do código contido no arquivo "testes.rb" (Copiado da saída do console do NetBeans):

Loaded suite E:\Workspace - Ruby\Lab4\lib\simulador
Started
.Mark Smith: 5.577831327996655
Joseph Smith: 4.736044383554591
Robert Smith: 4.466379704108458
..
Finished in 0.083 seconds.

3 tests, 28 assertions, 0 failures, 0 errors

Screenshot da execução acima (testes):

output_netbeans_testes.jpg

Código anexado:
Lab4_Diego_Alvarez.zip

Conclusão

Com a prática foi adquirida uma primeira experiência com programação Ruby.
Eu já havia programado anteriormente em outra linguagem dinâmica (JavaScript), no entanto, a experiência com Ruby foi valiosa e incitou-me questionamentos e comparações entre os dois paradigmas de programação (estático e dinâmico) que eu ainda não havia levado em consideração.

Definitivamente, pude perceber que o código torna-se mais conciso/enxuto quando programado em Ruby, comparativamente ao mesmo código que seria gerado em Java.

Diferenças como a possibilidade de se declarar e implementar diversas classes públicas em um mesmo arquivo, a não existência de classes abstratas/interfaces e a flexibilidade de se executar código fora de uma classe/método declarado explicitamente por mim mesmo, ainda são conceitos que confesso que ainda levarei algum tempo para me acostumar e absorver, para que, então, possa aproveitar tais recursos da melhor maneira possível.

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