Lab5 Douglas Bokliang

aluno: Douglas Bokliang Ang Cunha
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 22/12/2008 (?)

Introdução

Neste laboratório desenvolveremos um "bolão virtual". Este será um aplicativo cliente servidor simples, no entanto será muito importante para desenvolvermos diversos conceitos aprendidos em aula: padrões builder e decorator, threads e IO. No programa final, o usuário deverá ser capaz de executar diversas tarefas, como: listar partidas atuais, ver apostas feitas por ele, fazer nova aposta etc.

Melhoria nos Decorators de Thread Safety

O código fornecido inicialmente podia causar condições de corrida dependendo da operação utilizada (make_bet e finalize de Match). Sendo assim, foi necessário criar um Decorator para resolver este problema, chamado MatchThreadSafeDecorator.

require 'monitor'
require 'decorators/decorator'
module Decorators
  class MatchThreadSafeDecorator < Decorator
    include MonitorMixin

    def make_bet(score, user)
      synchronize do
        @decorated.make_bet(score, user)
      end
    end

    def finalize(final_score)
      synchronize do
        @decorated.finalize(final_score)
      end
    end

  end
end

A seguir, precisamos decorar o MatchList para criar um MatchList sem problemas de Thread.

require 'decorators/decorator'
module Decorators
  class MatchListThreadSafeDecorator < ListThreadSafeDecorator

    def initialize(decorated)
      old_match_decorator = decorated.item_decorator
      decorated.item_decorator = lambda do |match|
        MatchThreadSafeDecorator.new( old_match_decorator.call(match) )
      end
      super
    end

  end
end

Melhoria nos Decorators de Persistência

Inicialmente, o código possui problemas de persistência ao chamar os métodos make_bet e finalize de Match (métodos que alteram o conteúdo dos objetos). Para contornar esta situação, foi criado MatchPersistanceDecorator e MatchListPersistanceDecorator (o qual cria lista com Matches decorados):

require 'monitor'
require 'decorators/decorator'
module Decorators
  class MatchPersistanceDecorator < Decorator
    include MonitorMixin

    def initialize(decorated, list)
      super(decorated)
      @list = list
    end

    def make_bet(score, user)
      @decorated.make_bet(score, user)
      @list.save
    end

    def finalize(final_score)
      @decorated.finalize(final_score)
      @list.save
    end

  end
end

require 'decorators/decorator'
module Decorators
  class MatchListPersistanceDecorator < ListPersistanceDecorator

    def initialize(decorated,filename)
      super
      old_match_decorator = decorated.item_decorator
      decorated.item_decorator = lambda do |match|
        MatchPersistanceDecorator.new( old_match_decorator.call(match),self )
      end
    end

  end
end

Criação de Builders para UserList e MatchList com as decorações

Para treinar a utilização do padrão Builder, criou-se o MatchListBuilder e o UserListBuilder. Cada builder deu suporte aos 3 tipos de decoração, e ainda permitiu que pudesse ser chamado um método add_all para adicionar todas as decorações de uma só vez (tomando sempre o cuidado de fazer o threadsafety por último para garantir que tudo fique sincronizado.

class MatchListBuilder
  def initialize
    @match_list = MatchList.new
    @added_persistance = false
  end

  def add_persistance (filename = "list.yml")
    @match_list = MatchListPersistanceDecorator(@match_list,filename)
    @added_persistance = true
  end

  def add_logging
    @match_list = MatchListLoggerDecorator(@match_list)
  end

  def add_thread_safety
    @match_list = MatchListThreadSafeDecorator(@match_list)
  end

  def add_all
    @match_list = self.add_persistance(filename).add_logging.add_thread_safety
  end

  def match_list
    @match_list.load_list if @added_persistance
    @match_list
  end

end

require 'decorators/decorator'
class UserListBuilder
  def initialize
    @user_list = new.UserList
  end

  def add_persistance
    @user_list = UserListPersistance(@user_list)
  end

  def add_logging
    @user_list = UserListLogging(@user_list)
  end

  def add_thread_safety
    @user_list = UserListThreadSafe(@user_list)
  end

  def add_all
    @user_list = self.add_persistance.add_logging.add_thread_safety
  end

  def user_list
    @user_list
  end

end

Teste

Após a modificação de poucas linhas de código para se fazer a utilização dos Decorators criados, rodou-se os testes. Segue o resultado:

teste.jpg

Criação de um método main que interprete um protocolo de entrada e saída de dados do backend

Nesta seção, criou-se um main e um protocolo de IO. O protocolo utilizado foi feito seguindo-se as dicas das instruções, as quais aconselhavam o uso da biblioteca yaml para fazê-lo. O programa foi feito para dar suporte às seguintes operações feitas pelo usuário:
* Criar Usuario
* Autenticar Usuario
o Deve ser feita antes de qualquer uma das operações a seguir. Se for mal sucedida, encera a aplicação. Bem sucedida, define qual o usuário para o qual as operações seguintes serão realizadas
* Listar partidas atuais (retornando uma lista de nomes de partidas com algumas informações básicas a respeito de cada uma).
* Listar partidas encerradas (semelhante a anterior)
* Informações detalhadas de uma partida (apostadores, ganhadores)
* Listar informações das minhas apostas (vencedoras, em andamento, finalizadas)
* Fazer aposta em uma partida
* Encerrar a conexão

Para tal, foi criado inicialmente um método main, o qual é responsável pela execução do programa, montagem das listas de partidas e chamada da verificação da requisição feita pelo usuário.

def main(_in=$stdin, out=$stdout)
  user = nil

  #criando o user_list
  user_list_builder = UserListBuilder.new
  user_list_builder.add_all
  user_list = user_list_builder.user_list

  user_list.add("jose", "123456")
  user_list.add("joao", "123456")
  user_list.add("maria", "123456")

  #criando o match_list
  match_list_builder = matchListBuilder.new
  matchr_list_builder.add_all
  match_list = match_list_builder.user_list
  match_list.add "atletico x cruzeiro", Time.now + 100000
  match_list.add "atletico x palmeiras", Time.now + 100000

  loop do
    #utiliza RequestReader para esperar a chegada de uma requisição
    request = RequestReader.new(user_list,match_list,user,_in)
    user = request.get_request

  end

end

main #executa o main.

A classe RequestReader é responsável por tratar da requisição feita pelo usuário através de um protocolo. Ela utiliza as classes 'Printers' para fornecer a resposta devida.

class RequestReader
  def initialize(user_list,match_list,user,io=$stdin)
    @user = user
    @match_list = match_list
    @user_list = user_list
    #lê a entrada linha a linha até acharmos um "INICIO"
    loop do
      string = io.gets
      break if string && string.include?("INICIO")
    end
    yaml = io.gets("\nFIM") #depois do INICIO, lê tudo até a palavra FIM
    yaml = yaml.chomp("FIM") #tira o FIM do final do string que foi lido do io
    requisicao = YAML.load(yaml) # aqui já temos o hash com a requisicao processada

    #... processa requisicão e escreve saída
    @operacao = requisicao[:request[:operation]]
    @parametros = requisicao[:request[:parameters]]

  end

  def get_request
    while(@user == nil)
      case @operacao
        when 'login_user'
          username = YamlUserPrinter.new(@parametros, @user_list).print_loggin
          @user = user_list.find_by_name(username)
        when 'new_user'
          YamlUserPrinter.new(@parametros, @user_list).print_new_user
        else
          puts "You must log in first"
      end
    end

    case @operacao
      when 'matches'
        YamlMatchListPrinter.new(@match_list).print_matches
      when 'current_matches'
        YamlMatchListPrinter.new(@match_list).print_current_matches
      when 'finalized_matches'
        YamlMatchListPrinter.new(@match_list).print_finalized_matches
      when 'match'
        YamlMatchPrinter.new(@parametros, @match_list).print_match
      when  'bets'
        YamlMatchPrinter.new(@parametros, @match_list).print_bets
      when 'make_bet'
        YamlBetPrinter.new(@parametros, @user, @match_list).print_make_bet
      when 'check_bets'
        YamlBetPrinter.new(parametros, @user, match_list).print_check_bets
      when 'quit'
        YamlQuitPrinter.new
        @user == nil
    end

    @user
  end

end

A seguir, duas classes 'Printers' como exemplo.

module Printers
  class YamlMatchPrinter
    def initialize(match_name, match_list, io = $stdout)
      @io = io
      @match = match_list.find_by_name(match_name)
    end

    def print_match
      @io.puts "INICIO"
      @io.puts(
        {
          :response =>
            {
              :name => @match.name,
              :end_time => @match.end_time.strftime("%d/%m/%Y %H:%M"),
              :finalized => "#{(@match.finalized?) ? '' : 'not ' }finalized",
              :final_score => @match.final_score || "_ x _",
              :bet_count => "#{@match.bet_count} bets",
              :winners => (@match.winners) ? @match.winners.map{|u|u.username}.join(", ") : "no winners"
            }
       }.to_yaml
      )
      @io.puts "FIM"
    end

    def print_bets
      @io.puts "INICIO"
      @io.puts(
        {
          :response => @match.bets.sort_by{|b| b.score}.map do |bet|
            {
              :score => bet.score,
              :holders => bet.holders.map{|user| user.username}.join(", ")
            }
          end
       }.to_yaml
      )
      @io.puts "FIM"
    end

  end
end

module Printers
  class YamlUserPrinter
    def initialize(parametros, user_list, io = $stdout)
      @username = parametros[0]
      @password = parametros[1]
      @user_list = user_list
      @io = io
    end

    def print_login
      @io.puts "INICIO"
      @io.puts(
        {
          :response => (@user_list.authenticate(@username, @password)) ? "logged in" : "incorrect username and/or password"
        }.to_yaml
      )
      @io.puts "FIM"
      (@user_list.authenticate(@username, @password)) ? @username : nil
    end

    def print_new_user
      @io.puts "INICIO"
      @io.puts(
        {
          :response => (@user_list.add(@username, @password)) ? "user created succesfully" : "error"
        }.to_yaml
      )
      @io.puts "FIM"
    end

  end
end

Execução do Programa

Infelizmente, como deve ser fácil de perceber, este programa não funcionou. Não consegui descobrir os erros (os quais não devem ser em pequena quantidade), e como não tinha nenhum colega para me ajudar a identificá-los, fiquei horas empacado sem sucesso na detecção dos problemas. O código todo está em anexo, espero que o meu esforço para fazer este programa seja considerado.

Conclusão

Apesar do não funcionamento do programa, considerei que aprendi bastante com esse lab, assim como em todos os realizados no semestre. Consegui absorver bem o conceito dos padrões Decorator e Builder, assim como os problemas que podem ser gerados por Threads. Por último, aprendi um pouco sobre como fazer uma ligação servidor cliente, a qual deve ser realizado através de um protocolo bem definido.

codigo

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