aluno: Fabio Eigi Imada
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 22/12/2008 (2)
Introdução
A idéia deste laboratório era criar um servidor de um bolão que fosse capaz de interagir com o cliente através de um protocolo simples. Dessa forma seriam trabalhados os conceitos de Threading e IO, além de trabalhar os padrões de projeto Builder e Decorator.
Desenvolvimento
O desenvolvimento do laboratório foi feito nas seguinte etapas, conforme as instruções do lab:
- Desenvolvimento dos Decorators
- Modificação dos testes para envolverem as modificações criadas
- Criação do builder para encapsular o processo de criação de listas decoradas
- Desenvolvimento do main, que cria as listas decoradas com os builders e cria uma instância do RequestDealer (classe que encapsula o tratamento das requisições e responde adequadamente).
- Criação da classe Server que abre um servidor TCP em localhost na porta 4344
A criação dos Decorators foi feita conforme as instruções e o seu funcionamento foi comprovado com os testes fornecidos pelo professor:
Melhoria nos matchs
Como as melhorias envolviam inserção de novas funcionalidades aos métodos das Matchs, foram criados os seguintes Decorators para as Matchs, que tornavam os métodos make_bet e finalize mais robustos uma vez que estes métodos em particular modifam o estado do objeto:
- MatchPersistanceDecorator
- Faz com que as modificações sejam salvas no momento em que ocorrem.
- MatchThreadSafeDecorator
- Protege a instância de modifições simultâneas que possam tornar seu estado insonsistente.
Exemplo da MatchPersistanceDecorator:
class MatchPersistanceDecorator < Decorator def initialize(match,list) super(match) @match_list = list end def make_bet(*args) bet = @decorated.make_bet(*args) save bet end def finalize(*args) match = @decorated.finalize(*args) save match end def save @match_list.save end end
Melhoria nas MatchLists
Como foi feito no MatchListLoggerDecorator, os Decorators dos Matchs também tinham que ser aplicados às instâncias que estavam anteriormente nas listas.
Então foram criadas MatchListDecorators que herdavam das respectivas ListDecorators.
Exemplo da MatchListThreadSafetyDecorator
class MatchListThreadSafeDecorator < ListThreadSafeDecorator def initialize(decorated) super old_match_decorator = self.decorated.item_decorator self.decorated.item_decorator = lambda do |match| MatchThreadSafeDecorator.new( old_match_decorator.call(match) ) end end end
Criação dos Builders
Foi criado um "builder abstrato" contendo apenas as assinaturas dos métodos e o método add_all que era igual para ambos os builders propostos, para deixar explícita a semelhança entre os ListBuilders.
Classe ListBuilder (teria sido um nome mais apropriado, mas foi chamado apenas de builder)
class Builder attr_accessor :persistance_file def add_all(persistance_file = nil) if persistance_file @persistance_file = persistance_file end add_persistance add_logging add_thread_safe end end
Classe MatchListBuilder
class MatchListBuilder < Builder include Decorators def initialize @persistance_file = "matchs.yml" unless @persistance_file @added_persistence = false @match_list = MatchList.new end def match_list @match_list.load_list if @added_persistence @match_list end def add_persistance @added_persistence = true @match_list = MatchListPersistanceDecorator.new(@match_list,@persistance_file) end def add_logging @match_list = MatchListLoggerDecorator.new(@match_list) end def add_thread_safe @match_list = MatchListThreadSafeDecorator.new(@match_list) end end
Desenvolvimento do main
Conforme foi proposto, foi criado o método main que recebia como parâmetros os ios de entrada e saída e que criava as listas decoradas, além de receber solicitações de modo sincronizado, ThreadSafety:
def main(input=$stdin, output=$stdout) @input = input @output = output al_builder = UserListBuilder.new al_builder.add_all("admins.yml") @admin_list = al_builder.user_list ul_builder = UserListBuilder.new ul_builder.add_all("users.yml") @user_list = ul_builder.user_list ml_builder = MatchListBuilder.new ml_builder.add_all("matchs.yml") @match_list = ml_builder.match_list reader = RequestDealer.new(@admin_list,@user_list,@match_list,@input,@output) $stdout.puts "Recebendo de #{@input.to_s}" loop do reader.receive break if @input.closed? end end
A classe RequestDealer recebe, analiza e efetua as solicitações pelo método receive.
def receive #lê a entrada linha a linha até acharmos um "INICIO" loop do string = @in.gets break if string && string.include?("INICIO") end yaml = @in.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 $stdout.puts "Recebido:\n" + yaml requisicao = YAML.load(yaml) # aqui já temos o hash com a requisicao processada #... processa requisicão e escreve saída if requisicao['request']['parameters'] __send__(requisicao['request']['operation'],requisicao['request']['parameters']) else __send__(requisicao['request']['operation']) end end
Cada requisição chama o método de mesmo nome do parâmetro 'operation', como por exemplo:
def create_user(parameters) name = parameters[0] password = parameters[1] @user_list.add(name, password) simple_response("user created") end
Foi percebido posteriormente que seria mais claro se parameters fosse um dicionário ao invés de uma lista, evitando erros futuramente e facilitando a documentação.
Dessa forma, ao invés de:
name = parameters[0]
Teríamos algo como:
name = parameters[:name]
Foram executados alguns testes com input "entrada.data" e output "saida.data".
Os resultados estão aqui e na pasta test_results.
Criação do Servidor
OBS.: Para testar o servidor foi utilizado o telnet do Windows, que apesar de não estar disponível diretamente no prompt de comando, ainda existe, mocado no meio das pastas do Windows.
A criação do servidor foi de fácil implementação após ler a documentação indicada e compreender a idéia:
def server_multithread server = TCPServer.new(4344) # 4344 é a porta de conexão de rede usada # utilize de preferência um número acima de # 2000 (e menor que 64000). loop do while ( io_socket = server.accept ) #aguarda até que conexão seja recebida, retornando um TCPSocket com a conexão. Thread.new do # dispare uma nova thread e utilize io_socket para efetuar a entrada e saída do método main, de dentro desta thread main(io_socket,io_socket)#io_socket deve ser utilizado tanto para saída quanto para entrada, portanto é passado como parâmetro in e parâmetro out de main. io_socket.close end end end end server_multithread
Foi criado ainda um arquivo load.rb para carregar as listas com dados para poderem ser testados (descaradamente copiado dos testes):
ul_builder = UserListBuilder.new ul_builder.add_all ul = ul_builder.user_list jose = ul.add("jose", "123456") joao = ul.add("joao", "123456") maria = ul.add("maria", "123456") ml_builder = MatchListBuilder.new ml_builder.add_all m_list = ml_builder.match_list m1 = m_list.add "atletico x cruzeiro", Time.now + 100000 m2 = m_list.add "santos x palmeiras", Time.now + 100000 m1.make_bet "5x0", jose m1.make_bet "5x0", joao m1.make_bet "0x1", maria m2.make_bet " 3 x 0", jose m2.make_bet "2x0", joao m1.finalize("5x0")
Conclusão
O laboratório foi interessante para aprofundar o entendimento do padrão Decorator e da utilização do Builder.
Além disso, foi o laboratório que conteve mais conteúdo envolvido, e por isso mostrou-se um pouco trabalhoso e seu desenvolvimento foi bastante "quebrado", para consultar o material sobre Decorators, ThreadSafety, Builders, YAML, Sockets e Modules (este último eu ainda estou com algumas dúvidas e acabei não utilizando onde eu imaginei que seria útil, no caso dos Builders).





