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:
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.