Lab5 Felipe Moraes

aluno: Felipe Corrêa de Moraes
ano/sem: 2008/2o.
data do laboratório : 23/12/2008 (2)

Introdução

Neste laboratório tivemos que implementar uma aplicação cliente-servidor, implementando um sistema de apostas em jogos de futebol, no qual o usuário deveria ser capaz de fazer as apostas, ter acessos a informações como jogos não finalizados, jogos finalizados, apostas feitas, etc. Uma grande parte do código do backend já foi feita pelo professor, ficando à nossa responsabilidade 'melhorar' algumas classes, modificando seus métodos através do padrão de projetos decorator. Também implementamos o padrão builder para facilitar a instanciação das classes 'decoradas', e então, criamos um método main que deveria interpretar um protocolo de saída e entrada de dados para o backend. Por fim, deveriamos criar um servidor simples para que várias threads pudessem conectar-se ao servidor simultaneamente.

Desenvolvimento

Decorator em MatchList e UserList
No desenvolvimento do backend, as classes MatchList e UserList devem possuir novas funcionalidades (persistir os dados, escrever um log e serem thread safety). Para tal, fizemos uso do padrão de projeto Decorator, estudado em aula. Particularmente, adicionamos 4 classes aos decorators: MatchThreadSafeDecorator, MatchListThreadSafeDecorator, MatchPersistanceDecorator e MatchListPersistanceDecorator.

require 'monitor'
require 'decorators/decorator'
module Decorators 
  class MatchThreadSafeDecorator < Decorator
    include MonitorMixin
 
    def finalize(*args)
      synchronize do
      @decorated.finalize(*args)
      end
    end
 
    def make_bet(*args)
      synchronize do
        @decorated.make_bet(*args)
      end
    end
 
  end
end

Exemplo do padrão decorator aplicado a classe MatchList

Interessante notarmos que, para esta aplicação, a utilização do decorator serviu mais para reforçar nosso aprendisado do que para melhorar o programa. Os objetos matchlist e userlist do programa apenas são utilizados quando instanciados nas classes decoradas, ou seja, não tem uso sem ser decorados. Poderiamos simplesmente ter alterado o código das respectivas classes. Acredito que este padrão é mais funcional quando queremos os objetos tanto decorados quanto não decorados, isto é, o objeto decorado é utilizado em apenas alguns trechos do programa, onde se faz necessário novos atributos a este objeto.

Builder para a instanciação de MatchList e UserList
O padrão builder é muito interessante e muito fácil de ser utilizado. Sua utilização facilitou a instância dos métodos decorados.

require 'decorators/match_list_thread_safe_decorator'
require 'decorators/match_list_persistance_decorator'
require 'decorators/match_list_logger_decorator'
require 'match_list'
 
class MatchListBuilder
  include Decorators  
 
  def initialize
    @match_list = MatchList.new
    @added_persistance = false
  end
 
  def add_persistance(filename)
    @match_list = MatchListPersistanceDecorator.new(@match_list,filename)
    @added_persistance = true
  end
 
  def add_logging
    @match_list = MatchListLoggerDecorator.new(@match_list)
  end
 
  def add_thread_safety
    @match_list = MatchListThreadSafeDecorator.new(@match_list)
  end
 
  def add_all(filename)
    @match_list = MatchListThreadSafeDecorator.new(MatchListLoggerDecorator.new(MatchListPersistanceDecorator.new(@match_list,filename)))
    @added_persistance = true
  end
 
  def match_list
    @match_list.load_list if @added_persistance
    @match_list
  end
end

Exemplo do padrão Builder aplicado a classe MatchList

Protocolo de entrada e saída do backend e o método main
Para o protocolo de entrada e saída foi sugerida a utilização do yaml, json, xml ou um padrão customizado. Na própria instrução deste laboratório havia a sugestão do formato customizado 'tabela'. Optei por faz algo baseado nesse formato 'tabela'.

Os comandos de entrada para minha aplicação são os seguintes:
- register - cria um novo usuário (precisa de dois parâmetros: o nome do usuário e a senha)
- login - autentica o usuário (precisa de dois parâmetros: o nome do usuário e a senha)
- list current matches - lista na saída as partidas em andamento
- list finalized matches - lista na saída as partidas finalizadas
- match info - lista as informações de uma determinada partida (precisa do nome da partida como parâmetro)
- bets info - lista as informações de todas as apostas feitas
- make bet - realiza uma aposta (precisa de dois parâmetros: a aposta(o placar) e o nome da partida)
-logout - encerra a conexão

Na saída, para cada uma das entradas válidas, há uma determinada resposta. Para os comandos list current matches, list finalized matches, match info e bets info as respostas são, respectivamente, no seguinte formato:
'nome da partida' | 'data do fim' | not finalized | _ x _ | 'número de apostas' | no winners
.
.
.

'nome da partida' | 'data do fim' | finalized | 'placar final' | 'número de apostas' | 'ganhadores'
.
.
.
'nome da partida' | 'data do fim' | finalized ou not finalized | _ x _ ou 'placar final' | 'número de apostas' | 'ganhadores'
.
.
.
'nome da partida' | 'placar da aposta' | finalized ou not finalized | 'data do fim' | 'placar final da partida'
.
.
.
Para os outros comandos as respostas são simplesmente uma linha com uma informação, do tipo "User Felipe logged in!", "User João has been registered!" etc.
Para facilitar a leitura dos dados, toda entrada e saída deve estar entre 'INICIO' e 'FIM', como no padrão YAML. Não ficou muito original, mas foi uma forma de facilitar a leitura da entrada e evitar ambiguidades na saída.

Para a implementação deste protocolo, criamos algumas classes no modulo printers, que servem para escrever na saída as respectivas respostas.
A classe RequestReader foi criada, servindo para processar o pedido contido na entrada, e responder corretamente a ele na saída.

require 'printers/logger_printer'
require 'printers/failure_printer'
require 'printers/match_list_printer'
require 'printers/match_printer'
require 'printers/bet_printer'
 
class RequestReader
  include Printers
 
  def initialize(input = $stdin, output = $stdout)
    @in = input
    @out = output
    @user = nil
    @connected = true
  end
 
  attr_reader :connected
 
  def select_operation
    loop do
      string = @in.gets
      break if string && string.include?("INICIO")
    end
    tabela = @in.gets("\nFIM")
    tabela = tabela.chomp("FIM")
    request = tabela.split("\n")
    case request[0]
    when "register"
      if @user != nil
        printer = FailurePrinter.new(@out)
        printer.print_cantregister
      end
      unless @user != nil
        parameters = request[1].split("|")
        user_name = parameters[0]
        password = parameters[1]
        @user = $user_list.add(user_name, password)
        printer = LoggerPrinter.new(@user,@out)
        printer.print_registered
      end      
    when "login"
      if @user != nil
        printer = FailurePrinter.new(@out)
        printer.print_alreadylogged
      end
      unless @user != nil
        parameters = request[1].split("|")
        user_name = parameters[0]
        password = parameters[1]
        @user = $user_list.authenticate(user_name, password)
        unless @user == nil
          printer = LoggerPrinter.new(@user,@out)
          printer.print_login
        end
        if @user == nil
          printer = FailurePrinter.new(@out)
          printer.print_invalidated
          @connected = false
        end
      end      
    when "list current matches"
      unless @user == nil
        match_list = $match_list.list_current
        printer = MatchListPrinter.new(match_list, @out)
        printer.print_matches
      end
      if @user == nil
        printer = FailurePrinter.new(@out)
        printer.print_forbidden
      end
    when "list finalized matches"
      unless @user == nil
        match_list = $match_list.list_finalized
        printer = MatchListPrinter.new(match_list, @out)
        printer.print_matches
      end
      if @user == nil
        printer = FailurePrinter.new(@out)
        printer.print_forbidden
      end      
    when "match info"
      unless @user == nil
        match = $match_list.find_by_name(request[1])
        printer = MatchPrinter.new(match, @out)
        printer.print_match
      end
      if @user == nil
        printer = FailurePrinter.new(@out)
        printer.print_forbidden
      end
    when "bets info"
      unless @user == nil
        printer = BetPrinter.new(@user,$match_list,@out)
        printer.print_bets
      end
      if @user == nil
        printer = FailurePrinter.new(@out)
        printer.print_forbidden
      end
    when "make bet"
      unless @user == nil
        parameters = request[1].split("|")
        score = parameters[0]
        match_name = parameters[1]
        m = $match_list.find_by_name(match_name)
        m.make_bet(score,@user)
        @out.puts("INICIO")
        @out.puts("Bet successfully made!")
        @out.puts("FIM")
      end
      if @user == nil
        printer = FailurePrinter.new(@out)
        printer.print_forbidden
      end
    when "logout"
      unless @user == nil
        @connected = false
        printer = LoggerPrinter.new(@user,@out)
        printer.print_logout
      end
      if @user == nil
        printer = FailurePrinter.new(@out)
        printer.print_forbidden
      end
    else
      printer = FailurePrinter.new(@out)
      printer.print_unexpected
    end
 
  end
end

Classe RequestReader

Segue um exemplo de Printer feito:

module Printers
 
  class BetPrinter
    def initialize(user, match_list, io = $stdout)
      @user = user
      @match_list = match_list
      @io = io
    end
 
    def print_bets
      currentbets = @user.current_bets(@match_list)
      finalizedbets = @user.finalized_bets(@match_list)
      @io.puts "INICIO"
      @io.puts(
        currentbets.map do |b|
          [b.match.name,
            b.score,
            "not finalized",
            b.match.end_time
          ].join(" | ")
        end.join("\n")
      )
      @io.puts(
        finalizedbets.map do |b|
          [b.match.name,
            b.score,
            "finalized",
            b.match.end_time,
            "final score: #{b.match.final_score}"
          ].join(" | ")
        end.join("\n")
      )
      @io.puts "FIM"
    end
 
  end
end

Classe BetPrinter

O metodo main, contido no arquivo main.rb, instancia os objetos MatchList e UserList (através dos builders), intancia RequestReader
para a entrada e saída sendo, respectivamente, input.txt e entrada.txt.

require 'initialize'
require 'request_reader'
require 'match_list_builder'
require 'user_list_builder'
 
match_list_builder = MatchListBuilder.new
match_list_builder.add_all("matches.yml")
$match_list = match_list_builder.match_list
user_list_builder = UserListBuilder.new
user_list_builder.add_all("users.yml")
$user_list = user_list_builder.user_list
 
#m1 = $match_list.add "atletico x cruzeiro", Time.now + 1000000
#m2 = $match_list.add "sao paulo x corinthians", Time.now + 1000000
#$match_list.add "palmeiras x goias", Time.now + 1000000
#$match_list.add "santos x sport", Time.now + 1000000
 
#u1 = $user_list.add("Felipe", "fff123")
#u2 = $user_list.add("Pedro", "ppp456")
#u3 = $user_list.add("LaSagra", "pqp171")
 
#m1.make_bet "2x1", u1
#m1.make_bet "3x2", u1
#m1.make_bet "4x4", u2
#m2.make_bet "6x0", u1
#m2.make_bet "3x1", u2
#m2.make_bet "6x0", u3
#m2.make_bet "5x0", u3
 
#m1.finalize("2x0")
#m2.finalize("6x0")
 
def main(input = $stdin, output = $stdout)  
  request = RequestReader.new(input,output)
  loop do
    request.select_operation
    break unless request.connected
  end
  input.close
  output.close
end
 
main(File.open("input.txt", "r"),File.open("output.txt", "a"))

arquivo main.rb

Para verificar a funcionalidade do programa, rodamos a primeira vez com o código descomentado, e, nas próximas execuções, comentamos um trecho do código, como esta acima. O programa foi executado várias vezes, com inputs diferentes a cada vez. Segue abaixo o resultado dos testes:

input.txt
INICIO
login
Felipe|fff123
FIM
INICIO
bets info
FIM
INICIO
list current matches
FIM
INICIO
logout
FIM

output.txt
INICIO
User Felipe logged in!
FIM
INICIO
palmeiras x goias | 2x2 | not finalized | Sat Jan 03 12:40:39 -0200 2009
atletico x cruzeiro | 3x2 | finalized | Sat Jan 03 12:40:39 -0200 2009 | final score: 2x0
sao paulo x corinthians | 6x0 | finalized | Sat Jan 03 12:40:39 -0200 2009 | final score: 6x0
FIM
INICIO
palmeiras x goias | 03/01/2009 12:40 | not finalized | _ x _ | 1 bets | no winners
santos x sport | 03/01/2009 12:40 | not finalized | _ x _ | 0 bets | no winners
FIM
INICIO
User Felipe logged out!
FIM

input.txt
INICIO
login
Pedro|ppp456
FIM
INICIO
match info
palmeiras x goias
FIM
INICIO
make bet
4x2|palmeiras x goias
FIM
INICIO
register
PP|123456
FIM
INICIO
list finalized matches
FIM
INICIO
logout
FIM

output.txt
INICIO
User Pedro logged in!
FIM
INICIO
palmeiras x goias | 03/01/2009 12:40 | not finalized | _ x _ | 1 bets | no winners
FIM
INICIO
Bet successfully made!
FIM
INICIO
ERROR: Cant register another account!
FIM
INICIO
atletico x cruzeiro | 03/01/2009 12:40 | finalized | 2x0 | 2 bets | no winners
sao paulo x corinthians | 03/01/2009 12:40 | finalized | 6x0 | 3 bets | Felipe
FIM
INICIO
User Pedro logged out!
FIM

input.txt
INICIO
login
LaSagra|pqp171
FIM
INICIO
make bet
3x2|santos x sport
FIM
INICIO
make bet
2x4|palmeiras x goias
FIM
INICIO
bets info
FIM
INICIO
list current matches
FIM
INICIO
logout
FIM

output.txt
INICIO
User LaSagra logged in!
FIM
INICIO
Bet successfully made!
FIM
INICIO
Bet successfully made!
FIM
INICIO
palmeiras x goias | 2x4 | not finalized | Sat Jan 03 12:40:39 -0200 2009
santos x sport | 3x2 | not finalized | Sat Jan 03 12:40:39 -0200 2009
sao paulo x corinthians | 5x0 | finalized | Sat Jan 03 12:40:39 -0200 2009 | final score: 6x0
FIM
INICIO
palmeiras x goias | 03/01/2009 12:40 | not finalized | _ x _ | 3 bets | no winners
santos x sport | 03/01/2009 12:40 | not finalized | _ x _ | 1 bets | no winners
FIM
INICIO
User LaSagra logged out!
FIM

input.txt
INICIO
register
Paulo|ppp987
FIM
INICIO
make bet
2x2|santos x sport
FIM
INICIO
logout
FIM

output.txt
INICIO
User Paulo has been registered!
FIM
INICIO
Bet successfully made!
FIM
INICIO
User Paulo logged out!
FIM

input.txt
INICIO
login
Paulo|ppp987
FIM
INICIO
list current matches
FIM
INICIO
bets info
FIM
INICIO
logout
FIM

output.txt
INICIO
User Paulo logged in!
FIM
INICIO
palmeiras x goias | 03/01/2009 12:40 | not finalized | _ x _ | 3 bets | no winners
santos x sport | 03/01/2009 12:40 | not finalized | _ x _ | 2 bets | no winners
FIM
INICIO
santos x sport | 2x2 | not finalized | Sat Jan 03 12:40:39 -0200 2009
FIM
INICIO
User Paulo logged out!
FIM

Por fim, o arquivo server_muiltithread.rb contém o código que implementa um servidor multithread:

require 'main'
 
server = TCPServer.new(4344)
while (io_socket = server.accept)
  Thread.new do
    io_socket.puts "Conected!"
    main(io_socket,io_socket)
    io_socket.puts "Disconected!"
    io_socket.close
  end
end

A figura a seguir mostra a conexão simultânea no servidor:
servidor.JPG

Conclusão

A criação do código e o entendimento do código já fornecido certamente foram trabalhosos. A linguagem ruby é muito interessante, e também muito diferente de algo com que eu ja tive experiência. Constantemente tive que procurar documentações sobre as classes como array, io, enumerable etc. A parte mais trabalhosa foi, de fato, implementar o programa nesta linguagem. Entretanto, este trabalho serviu muito para a aprendisagem e o entendimento da diferença entre uma linguagem dinâmica e uma estática.
Outro fato que pode ser encarado como dificuldade é a liberdade que tivemos. Apesar do guia explicar passo a passo o que deveriamos fazer, cada um desses 'passos' poderiam ser feitos de uma forma muito variada. No meu caso, eu perdi muito tempo para entender e criar o protocolo. A parte menos trabalhosa foi a implementação dos padrões de projeto builder e decorator.
Acredito ter atendido a todas as requisições pedidas e, no entando, o programa não está muito funcional e completo. Não sei se essa era a idéia, mas pode haver apenas uma autenticação de conta por execução (ou seja, cada vez que um usuário diferente for logar, o programa deve ser re-executado). Além disso, realizar testes no programa também não é muito prático.
Contudo, o programa funciona e os padrões pedidos foram aplicados de forma correta.

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