João Batista Neto 448 Denunciar post Postado Março 1, 2012 você usara um padrao de projeto: o composite... Não, esse NÃO é um caso de uso de composite! Não, esse NÃO é um caso de uso de composite!um objeto pergunta nao poderia agregar alternativas?... Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 1, 2012 um objeto pergunta nao poderia agregar alternativas?... Agregação é uma coisa. Composite existe toda uma motivação e aplicabilidade, que não se enquadra nesse caso de uso. Ps: sorry pelas respostas muito curtas, estou no tab e é péssimo escrever por aqui. Agregação é uma coisa. Composite existe toda uma motivação e aplicabilidade, que não se enquadra nesse caso de uso. Ps: sorry pelas respostas muito curtas, estou no tab e é péssimo escrever por aqui. ok... Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 1, 2012 ok... No post abaixo eu falo sobre motivação e aplicabilidade de Composite: http://forum.imasters.com.br/topic/457414-obter-indices-de-mc-em-grid-ao-completar-o-array-chamar-uma-funcao/page__view__findpost__p__1812242 No post abaixo eu falo sobre motivação e aplicabilidade de Composite: http://forum.imaster...ost__p__1812242 sim, foi o q eu disse, uma pergunta agrega alternativas, mas uma alternativa nao pode ser a questao seguinte, num questionario? ae se torna um composite, com no exemplo da biblioteca, um livro pertence a a tal estante, tal estante tem tantos livros, e uma biblioteca tem tantas estantes....a nao ser q o composite so aceite objetos do mesmo tipo... Compartilhar este post Link para o post Compartilhar em outros sites
Henrique Barcelos 290 Denunciar post Postado Março 1, 2012 Igor, leia: Composite é um padrão de projeto de software utilizado para representar um objeto que é constituído pela composição de objetos similares a ele. Neste padrão, o objeto composto possui um conjunto de outros objetos que estão na mesma hierarquia de classes a que ele pertence. O padrão composite é normalmente utilizado para representar listas recorrentes - ou recursivas - de elementos. Além disso, esta forma de representar elementos compostos em uma hierarquia de classes permite que os elementos contidos em um objeto composto sejam tratados como se fossem um único objeto. Desta forma, todos os métodos comuns às classes que representam objetos atômicos da hierarquia poderão ser aplicáveis também ao conjunto de objetos agrupados no objeto composto. Perguntas e respostas não tem nada a ver um com outro no quesito hierarquia, não são unidos por herança, e sim por agregação (ou composição, se preferir), ou seja, uma pergunta não é constituída de várias respostas, uma pergunta é constituída de um frase interrogativa. PONTO. Que métodos em comum teriam pergunta e resposta? Você está sofrendo de "paternite", ou seja, está enxergando padrões onde eles não existem, tome cuidado com isso. Todos os que entendem um pouco do assunto também já devem ter passado por essa situação, mas tente tomar cuidado ao dar sugestões a outros membros e indicar-lhes o caminho errado. Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 1, 2012 com no exemplo da biblioteca, um livro pertence a a tal estante, tal estante tem tantos livros, e uma biblioteca tem tantas estantes....a nao ser q o composite so aceite objetos do mesmo tipo... Você lê uma estante? Você armazena estantes em um livro? Você pode tratar uma estante como um livro, ou um livro como uma estante? Se a resposta para qualquer uma das perguntas acima for positiva, significa que você pode ignorar as diferenças entre esses objetos e, assim, dizer que uma estante É UM livro e que um livro É UMa estante e, dessa forma, utilizar uma composição recursiva como Composite. Se a resposta for negativa e você achar que uma estante TEM livros, então trata-se de uma associação 1..*, assim como uma empresa está associada com 1..* funcionários, uma estante possui livros que não dependem da estante para existir, bem como a estante não depende do livro para existir. Compartilhar este post Link para o post Compartilhar em outros sites
Bruno Augusto 417 Denunciar post Postado Março 2, 2012 E existe um padrão específico para esse exemplo da estante ou dos funciona´rios ou é apenas uma Coleção simples? Compartilhar este post Link para o post Compartilhar em outros sites
Henrique Barcelos 290 Denunciar post Postado Março 2, 2012 Um exemplo onde se aplica o Composite: implementação do DOM. O PHP já possui uma excelente biblioteca para trabalharmos com o DOM, mas suponha que você queira criar a sua própria implementação. interface DOMNode { public function getNodeValue(); } class DOMTag implements DOMNode { private $tagName; private $attrs = array(); public function getNodeValue() { return $this->generateHTML(); } public function generateHTML($isXhtml = true) { $output = '<' . $this->tagName . $this->serializeAttrs(); if($isXhtml){ $output .= '/'; } $output = '>'; } private function serializeAttrs() { $ret = array(); foreach($this->attrs as $attrName => $attrValue){ $ret[] = $attrName . '="' . $attrValue . '"'; } return join(' ', $ret); } } class DOMCompositeTag extends DOMTag { private $children = array(); public function generateHtml($isXhtml = true) { $output = '<' . $this->tagName . $this->serializeAttrs() . '>'; foreach($this->children as $child){ if($child instanceof DOMTag){ $output .= $child->generateHtml($isXhtml) . PHP_EOL; } else { $output .= $child->getNodeValue() . PHP_EOL; } } $output = '</' . $this->tagName . '>'; } public function appendChild(DOMNode $node) { // código omitido... } public function removeChild(DOMNode $node) { // código omitido... } } class DOMText implements DOMNode { private $value; public function getNodeValue() { return $this->value; } } Note que apenas DOMCompositeTag pode ter 'filhos', que podem ser qualquer DOMNode, seja tag, composite tag ou texto. Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 2, 2012 E existe um padrão específico para esse exemplo da estante ou dos funciona´rios ou é apenas uma Coleção simples? Nesse caso da estante ou da empresa, suas representações enquanto entidades, são meras associações. O diagrama abaixo exemplifica os tipos: O primeiro relacionamento é uma associação simples, uma empresa pode ter 1..* funcionários. Se um funcionário morrer, a empresa não deixará de funcionar. O segundo relacionamento chama-se agregação, se tirarmos as rodas do carro, ele não andará, mas também não deixará de existir, assim como as rodas não deixarão de existir. O terceiro relacionamento chama-se composição, é o relacionamento mais forte que temos. Se cortarmos a cabeça, Fulano morre. Da mesma forma, se removermos um capítulo de um livro, vamos estragá-lo. ;) Compartilhar este post Link para o post Compartilhar em outros sites
Luis Paullo 47 Denunciar post Postado Março 2, 2012 um explicacao simples deixa as coisas tao claras . Queria que tudo fosse assim kkkkk.. +1 pelo ultimo post Joao.. =D Compartilhar este post Link para o post Compartilhar em outros sites
visitante_php 0 Denunciar post Postado Março 2, 2012 Você lê uma estante? Você armazena estantes em um livro? Você pode tratar uma estante como um livro, ou um livro como uma estante? Se a resposta para qualquer uma das perguntas acima for positiva, significa que você pode ignorar as diferenças entre esses objetos e, assim, dizer que uma estante É UM livro e que um livro É UMa estante e, dessa forma, utilizar uma composição recursiva como Composite. Se a resposta for negativa e você achar que uma estante TEM livros, então trata-se de uma associação 1..*, assim como uma empresa está associada com 1..* funcionários, uma estante possui livros que não dependem da estante para existir, bem como a estante não depende do livro para existir. beleza, eu nao tinha prestado atencao ao detalhe de ter q ser do mesmo tipo....como o exemplo dos menus e submenus... Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 3, 2012 beleza, eu nao tinha prestado atencao ao detalhe de ter q ser do mesmo tipo....como o exemplo dos menus e submenus... É exatamente isso Igor, poder ignorar as diferenças entre os elementos da composição e poder tratá-los como do mesmo tipo. Um caso onde a situação proposta pelo Juliano caberia Composite, é na exibição. Se tratarmos, tanto "a pesquisa" quanto as perguntas e as alternativas como elementos renderizáveis, então poderemos utilizar Composite para representá-los em uma View. Juliano, esse fragmento que você nos passou é insuficiente para qualquer sugestão, não há como lhe dizer se é uma boa ou má solução, apenas vendo o esqueleto de três classes, é preciso algo que represente, de fato, o problema. Vamos analisar seu problema: Eu estou desenvolvendo um sistema de pesquisa. Este sistema tem perguntas e respostas (doh). Em um determinado momento, o pesquisador terá que digitar as respostas que coletou. A descrição do seu problema também é insuficiente, teremos: 1. Perguntas e respostas 2. O pesquisador informará as respostas, pelo que entendi, será uma pesquisa de campo, o pesquisador irá até o entrevistado e, depois de coletar as informações de que precisa, preencherá um formulário com os dados. Coisas que faltaram: 1. Seu sistema terá várias pesquisas acontecendo simultaneamente? 2. Uma determinada pesquisa será restrita ao pesquisador, ou seja, apenas ele terá acesso? 3. As respostas das perguntas serão sempre fixas, A, B, C ou D? Não haverá a possibilidade de uma resposta dissertativa? 4. O pesquisador informou as alternativas, e ai? O que acontece depois disso? Percebe amigo, não há a menor possibilidade de se "pensar orientado a objetos" se você não "pensar em resolver um problema antes". A base da orientação a objetos é definir o relacionamento entre os participantes e delegar responsabilidades. Coesão e coerência são duas palavras chave da orientação a objetos, e você só alcançará a coesão e coerência quando você compreender o propósito da sua aplicação e como as coisas devem acontecer. Vejamos, uma entrevista é uma conversação entre duas pessoas, um entrevistador e o entrevistado. Entrevistar é a ação do entrevistador de fazer perguntas da entrevista e obter a resposta do entrevistado, que usará uma das alternativas, okay? Um exemplo de código utilizando esse relacionamento fica assim: People.php <?php /** * Representação básica das pessoas envolvidas em uma entrevista * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class People { /** * @var string */ private $name; /** * Recupera o nome da pessoa. * @return string */ public function getName() { return $this->name; } /** * Define o nome da pessoa. * @param string $name */ public function setName( $name ) { $this->name = $name; } } Interviewer.php <?php require_once 'People.php'; /** * Representação do entrevistador, que fará uma entrevista com um entrevistado * e coletará suas respostas. * @author João Batista Neto <neto.joaobatista@imasters.com.br */ class Interviewer extends People { /** * Faz a entrevista com um entrevistado. * @param Interview $interview A entrevista que será feita. * @param Interviewee $interviewee O entrevistado, que responderá a * entrevista. */ public function interview( Interview $interview , Interviewee $interviewee ) { foreach ( $interview->getQuestions() as $question ) { $this->ask( $question , $interviewee ); } } /** * Faz uma pergunta ao entrevistado. * @param Question $question A pergunta que será feita. * @param Interviewee $to */ public function ask( Question $question , Interviewee $to ) { $interviewerName = $this->getName(); $intervieweeName = $to->getName(); printf( "%s - %s, %s\n" , $interviewerName , $intervieweeName , $question->getQuestion() ); printf( "%s - %s, %s\n" , $intervieweeName , $interviewerName , $to->answer( $question )->getAlternative()->getAlternative() ); } } Interviewee.php <?php require_once 'People.php'; require_once 'Answer.php'; /** * Representação de um entrevistado, que responderá perguntas de uma entrevista * para um entrevistador. * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class Interviewee extends People { /** * Responde uma pergunta utilizando uma das alternativas da pergunta. * @param Question $question A pergunta que será respondida. * @return Answer A resposta do entrevistado. */ public function answer( Question $question ) { $alternatives = $question->getAlternatives(); shuffle( $alternatives ); return new Answer( $question , array_shift( $alternatives ) ); } } Interview.php <?php /** * Representação de uma entrevista, que contém várias perguntas que serão feitas * por um entrevistador para um entrevistado. * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class Interview { /** * @var array[Question] */ private $questions = array(); /** * Adiciona uma pergunta na entrevista. * @param Question $question A pergunta que será adicionada na entrevista. */ public function addQuestion( Question $question ) { $this->questions[] = $question; } /** * Recupera a lista de perguntas da entrevista. * @return array[Question] */ public function getQuestions() { return $this->questions; } } Question.php <?php /** * Representação de uma pergunta da entrevista. * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class Question { /** * @var string */ private $question; /** * @var array[Alternative] */ private $alternatives = array(); /** * Constroi uma instância de Question definindo o texto da pergunta. * @param string $question O texto da pergunta. */ public function __construct( $question ) { $this->question = $question; } /** * Adiciona uma alternativa para a pergunta. * @param Alternative $alternative A alternativa que será adicionada. */ public function addAlternative( Alternative $alternative ) { $this->alternatives[] = $alternative; } /** * Recupera a lista de alternativas da pergunta. * @return array[Alternative] */ public function getAlternatives() { return $this->alternatives; } /** * Recupera o texto da pergunta; * @return string */ public function getQuestion() { return $this->question; } } Alternative.php <?php /** * Representação de uma alternativa para uma pergunta. * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class Alternative { /** * @var string */ private $alternative; /** * Cria uma instância de Alternative definindo o texto da alternativa. * @param string $alternative Texto da alternativa. */ public function __construct( $alternative ) { $this->alternative = $alternative; } /** * Recupera o texto da alternativa. * @return string */ public function getAlternative() { return $this->alternative; } } Answer.php <?php /** * Representação de uma resposta para uma pergunta. * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class Answer { /** * @var Question */ private $question; /** * @var Alternative */ private $alternative; /** * Constroi uma instância de Answer definindo a pergunta e a alternativa * escolhida pelo entrevistado. * @param Question $question A pergunta feita pelo entrevistador. * @param Alternative $alternative A alternativa escolhida pelo entrevistado. */ public function __construct( Question $question , Alternative $alternative ) { $this->question = $question; $this->alternative = $alternative; } /** * Recupera a pergunta da resposta. * @return Question */ public function getQuestion() { return $this->question; } /** * Recupera a alternativa escolhida. * @return Alternative */ public function getAlternative() { return $this->alternative; } } Usando isso ai, teríamos: <?php require_once 'Question.php'; require_once 'Alternative.php'; require_once 'Interview.php'; require_once 'Interviewee.php'; require_once 'Interviewer.php'; $question1 = new Question( 'Você gosta de OOP?' ); $question1->addAlternative( new Alternative( 'Sim!' ) ); $question1->addAlternative( new Alternative( 'Não!' ) ); $question1->addAlternative( new Alternative( 'Isso é de comer?' ) ); $question2 = new Question( 'O que é mais legal em OOP?' ); $question2->addAlternative( new Alternative( 'O relacionamento entre objetos.' ) ); $question2->addAlternative( new Alternative( 'A delegação de responsabilidades.' ) ); $question2->addAlternative( new Alternative( 'Já perguntei se isso é de comer?' ) ); $interview = new Interview(); $interview->addQuestion( $question1 ); $interview->addQuestion( $question2 ); $interviewer = new Interviewer(); $interviewer->setName( 'João Batista Neto' ); $interviewee = new Interviewee(); $interviewee->setName( 'Juliano Amadeu' ); $interviewer->interview( $interview , $interviewee ); A saída do exemplo é aleatória, mas vai se parecer com alguma coisa assim: João Batista Neto - Juliano Amadeu, Você gosta de OOP? Juliano Amadeu - João Batista Neto, Isso é de comer? João Batista Neto - Juliano Amadeu, O que é mais legal em OOP? Juliano Amadeu - João Batista Neto, Já perguntei se isso é de comer? ;) PS: Para os loucos por padrões de design, existe um problema nesse modelo que postei que pode ser resolvido com um padrão GoF. Imaginem que a próxima pergunta seja condicionada pela resposta dada pelo entrevistado, como podemos fazer nesse caso? Alguém sabe responder? Compartilhar este post Link para o post Compartilhar em outros sites
JCMais 75 Denunciar post Postado Março 3, 2012 Não tenho o devido conhecimento com Design Patterns, o JB deve ter percebido isso :pinch:, mas não seria o caso de usar Chain of Responsibility, porque vejamos bem o problema: A próxima pergunta deve ser condicionada pela resposta dada pelo entrevistado. No caso não deve-se montar uma cadeia de responsabilidade? Mas aí eu me pergunto, como seria definido a próxima questão? No momento que cria uma Questão: $question1 = new Question( 'Você gosta de OOP?' ); Como defini qual deve ser a resposta dada na questão anterior para que, esta questão seja exibida? Ou isso é responsabilidade da classe Interview? :ermm: Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 3, 2012 mas não seria o caso de usar Chain of Responsibility, Vejamos Chain of Responsibility: Chain of Responsibility :seta: Comportamental Intenção: Desacoplar quem envia a requisição de quem recebe a requisição. Com Chain of Responsibility mais do que um objeto tem a chance de manipular uma mesma requisição. Os objetos manipuladores são encadeados e a requisição é passada pela cadeia até que um objeto possa manipulá-la. Motivação: Muitas vezes precisamos executar uma determinada ação mas não queremos saber, exatamente, quem vai manipulá-la. Imagine uma aplicação que recebe uma requisição HTTP do usuário, que deseja obter um recurso localizado em determinado path. Se o recurso solicitado existir, a aplicação entregará o recurso, mas se não existir ela entregará, então, uma página 404 seguida do cabeçalho HTTP Not Found. Quando a requisição chegar no servidor, a aplicação delegará a responsabilidade de manipulação da requisição para um determinado objeto, se esse objeto não souber ou não puder manipular a requisição, ele pode delegar a responsabilidade para o próximo objeto da cadeia. Por ter um receptor implícito, ou seja, o objeto que faz a requisição não sabe qual objeto vai manipular a requisição, temos os objetos que fazem as requisições desacoplados dos objetos que a manipulam. Aplicabilidade: Podemos usar Chain of Responsibility quando: Mais do que um objeto pode manipular uma requisição e o objeto manipulador não é conhecido. Precisamos enviar uma requisição para um entre muitos objetos sem ter especificar o objeto explicitamente. O conjunto de objetos que podem manipular determinada requisição podem ser definidos dinamicamente. Estrutura: Participantes: Handler Define a interface para os objetos manipuladores e, opcionalmente, define a ligação com o sucessor da cadeia. ConcreteHandler Manipula a requisição da qual é responsável. Pode acessar seu sucessor. Se puder manipular a requisição, ele manipula, do contrário delega a requisição ao seu sucessor. Client Envia uma requisição à um ConcreteHandler do encadeamento. :seta: Um encadeamento onde vários objetos podem ter a chance manipular uma requisição. Será que isso resolve nosso problema? Variar a pergunta baseado na resposta do entrevistado? Compartilhar este post Link para o post Compartilhar em outros sites
Aprendiz CSS 3 Denunciar post Postado Março 3, 2012 Não li todo o post, mas darei meus 2 cents pelo titulo do post. 1 cent Não pense orientado a objetos, isso é errado! Calma, calma, esse foi o meu momento fanfarrão. 2 cents Agora vamos ao que interessa. Pelo titulo do post, você tem muito a aprender, muito mesmo. Começar com os design patterns não é recomendado, pelo menos na minha opinião. Você provavelmente deve ter ouvido falar de: - DAO (Data Access Object) - DTO (Data Transfer Object) - MVC (Model View Controller) Esses nomes acima, servem apenas para você pensar em responsabilidades diferentes. Pense que cada Classe tenha uma responsabilidade especifica. Ok! mas o que significa ter uma responsabilidade especifica? Simples, não coloque um SELECT * FROM tabela na VIEW. Se você fizer isso, você ferra com tudo (existem exceções, mas não entrarei nelas) O grande X da questão é a separação de responsabilidade. Por exemplo, na view receba um objeto e faça um foreach nele para montar uma lista de alguma coisa, uma tabela, sei lá o que, mas não faça o select diretamente na view. Para ficar mais fácil de exemplificar isso, teriamos que definir um cenário e falar exclusivamente dele, pois é muito vasto a orientação a objeto. Um detalhe importante, que gosto de comentar quando falo de orientação a objetos é a respeito de Getter e Setter. Principalmente o Setter, cara, ele não deve ser assim public set_nome($nome){ $this->nome = $nome; } Você deve ter isso public set_nome($nome){ if( strlen($nome) > 120 ) $this->nome = substr($nome, 0, 120); else $this->nome = $nome; } meu objetivo com o exemplo acima foi mostrar que você deve ter alguma validação antes de setar o objeto, pois se não fizer, pode dar um grande problema depois, mais pra frente. Acho que deu pra contribuir um pouco, desculpem se os outros posters desse tópico já falaram isso que mandei. Mas você está no caminho certo, está perguntando e querendo aprender :) Compartilhar este post Link para o post Compartilhar em outros sites
Brayan Rastelli 2 Denunciar post Postado Março 3, 2012 Acredito que o pattern Strategy seria o ideal nesse caso O padrão Strategy permite definir novas operações sem alterar as classes dos elementos sobre os quais opera. Definir uma família de algoritmos e encapsular cada algoritmo como uma classe, permitindo assim que elas possam ter trocados entre si. Este padrão permite que o algoritmo possa variar independentemente dos clientes que o utilizam. http://pt.wikipedia.org/wiki/Strategy Compartilhar este post Link para o post Compartilhar em outros sites
Henrique Barcelos 290 Denunciar post Postado Março 3, 2012 Como defini qual deve ser a resposta dada na questão anterior para que, esta questão seja exibida? Ou isso é responsabilidade da classe Interview? Pensando um pouco no mundo real, vamos pensar num programa de entrevistas: Quando o entrevistado responde a uma pergunta, cabe ao entrevistador decidir qual a próxima pergunta a ser respondida. Em questionários, normalmente encontramos algo do tipo: pule para a questão Y se responder a questão X com 'não'. Nessa caso, a responsabilidade é da entrevista. Nosso questionário se aproxima mais da 2ª situação. Eu consigo pensar numa solução utilizando árvores de perguntas. Se fôssemos representar através de um grafo, os nós seriam as perguntas e as arestas seriam as possíveis respostas. Não sei se Strategy se aplica aí. Mesmo se fosse aplicável, quais seriam os parâmetros necessários??? Brian, esse Strategy aí da wikipedia tem uma implementação meio burra. Toda vez que você adicionar um novo tipo de funcionário, terá que mexer na classe Venda. O correto seria fazer isso por injeção de dependências. Além disso, o cálculo da comissão, segundo o que sei, é algo intrínseco ao vendedor, não à venda. interface Strategy{ public double getValorComissao(Venda v); } class ComissaoVendedor implements Strategy { public double getValorComissao(Venda v) { return v.getValor() * 0.05; } } class ComissaoGerente implements Strategy { public double getValorComissao(Venda v) { return v.getValor() * 0.02; } } public class Funcionario { private String nome; private Strategy calc; private List<double> comissoes; public Funcionario(String nome, Strategy calc){ this.nome = nome; this.calc = calc; } public String getNome(){ return this.nome; } public Strategy getCalc(){ return this.calc; } } public class Venda { private Funcionario funcionario; private double valor; public Venda(double valor, Funcionario func) { setValor(valor); this.funcionario = func; } public void setValor(double v){ this.valor = v; } public double getValor(){ return this.valor; } public void registrarComissao(){ double valor = this.funcionario.getCalc().getValorComissao(this.valor); DecimalFormat twoDForm = new DecimalFormat("#.##"); double rounded = Double.valueOf(twoDForm.format(valor); this.funcionario.getComissoes().append(valor); System.out.println("Comissão de R$ " + rounded + " para o vendedor " + this.funcionario.getNome()); } } public class Application { public static void main(String[] argv){ Funcionario f1 = new Funcionario('José', new ComissaoVendedor()); Funcionario f2 = new Funcionario('Maria', new ComissaoGerente()); Venda venda1 = new Venda(400, f1); Venda venda2 = new Venda(200, f1); Venda venda3 = new Venda(600, f2); venda1.registrarComissao(); venda2.registrarComissao(); venda3.registrarComissao(); } } Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 4, 2012 Acredito que o pattern Strategy seria o ideal nesse caso Na mosca! [+1] Strategy :seta: comportamental Intenção Definir uma família de algorítimos, encapsulá-los e fazê-los intercambiáveis. Strategy permite que o algorítimo varie independentemente do client que o utiliza. Motivação Assim como Chain of Responsibility, Strategy é um padrão de design comportamental. A questão é que precisamos variar a requisição em si, não o manipulador dela. Chain of Responsibility permite que desacoplemos o manipulador de quem envia a requisição, mas e se o algorítimo da requisição precisar variar? No caso do Q&A, uma nova pergunta pode depender de determinada resposta, sabemos que o Interviewee vai responder a pergunta, mas como vamos selecionar a pergunta? E se a pergunta não depende da resposta anterior? E se a entrevista precisar tomar um rumo diferente, dependendo das respostas dadas pelo entrevistado? Se formos montar uma estrutura if/elseif ou switch/case com as possibilidades de perguntas baseando nas possíveis respostas do entrevistado, teremos uma estrutura muito grande e complexa. Ficaria inviável manter essa estrutura. Aplicabilidade Podemos utilizar Strategy quando: Muitas classes diferem apenas em seu comportamento. Strategy permite que configuremos uma classe com um entre vários comportamentos. Precisamos variar um algorítimo. Precisamos evitar a exposição de estruturas de dados complexas, específicas do algorítimo. Uma classe define múltiplos comportamentos, que aparecem em uma estrutura condicional muito grande, em vez de muitos ifs/elseifs ou um switch/case muito grande, podemos ter as ramificações em sua própria Strategy. Estrutura Participantes Strategy Define uma interface comum para todos os algorítimos. Context utiliza essa interface para executar um algorítimo definido por um ConcreteStrategy. ConcreteStrategy Implementa um algorítimo utilizando a interface Strategy Context É configurado com um ConcreteStrategy. Mantém uma referência para o Strategy. Pode definir uma interface que permite que o Strategy acesse seus dados. Quando o entrevistado responde a uma pergunta, cabe ao entrevistador decidir qual a próxima pergunta a ser respondida. Fato, mas se formos pensar também, deixar para o entrevistado decidir qual a próxima pergunta será respondida, ele terá que utilizar uma estrutura de controle como if/elseif ou switch/case. Se a entrevista for muito grande, com muitas ramificações, além do entrevistador precisar conhecer todas as respostas, ele terá um encadeamento de if/elseif tão grande e complexo, que a manutenção do código seria inviável. Em questionários, normalmente encontramos algo do tipo: pule para a questão Y se responder a questão X com 'não'. Nessa caso, a responsabilidade é da entrevista. Nosso questionário se aproxima mais da 2ª situação. Eu consigo pensar numa solução utilizando árvores de perguntas. Se fôssemos representar através de um grafo, os nós seriam as perguntas e as arestas seriam as possíveis respostas. De qualquer forma, quando temos mais do que uma possível pergunta para ser a próxima, teremos uma arvore, mas ainda teremos que ter uma decisão em algum lugar. Para qual ramificação caminharemos? Strategy, nesse caso, nos permitirá variar a pergunta baseando na última resposta, sem precisar de condicionais de verificação das respostas. No nosso caso, Strategy é a própria pergunta (Question), não precisamos modificar essa interface. Vamos precisar fazer um ajuste na interface do entrevistador para recuperar a última resposta obtida na entrevista: Interviewer.php <?php require_once 'People.php'; /** * Representação do entrevistador, que fará uma entrevista com um entrevistado * e coletará suas respostas. * @author João Batista Neto <neto.joaobatista@imasters.com.br */ class Interviewer extends People { /** * @var Answer */ private $lastAnswer; /** * Faz a entrevista com um entrevistado. * @param Interview $interview A entrevista que será feita. * @param Interviewee $interviewee O entrevistado, que responderá a * entrevista. */ public function interview( Interview $interview , Interviewee $interviewee ) { foreach ( $interview->getQuestions() as $question ) { $this->ask( $question , $interviewee ); } } /** * Faz uma pergunta ao entrevistado. * @param Question $question A pergunta que será feita. * @param Interviewee $to */ public function ask( Question $question , Interviewee $to ) { $interviewerName = $this->getName(); $intervieweeName = $to->getName(); $answer = $to->answer( $question ); printf( "%s - %s, %s\n" , $interviewerName , $intervieweeName , $question->getQuestion() ); printf( "%s - %s, %s\n" , $intervieweeName , $interviewerName , $answer->getAlternative()->getAlternative() ); $this->lastAnswer = $answer; } /** * Recupera a última resposta dada pelo entrevistado. * @return Answer */ public function getLastAnswer() { return $this->lastAnswer; } } O entrevistador (ou Interviewer) será nosso Context, vamos utilizar o método de interface getLastAnswer para obter a última resposta dada pelo entrevistado. Por fim, teremos um novo participante, que atuará como ConcreteStrategy: ConditionalQuestion.php <?php require_once 'Question.php'; /** * Representa uma pergunta condicionada por uma resposta anterior dada pelo * entrevistado. * @author João Batista Neto <neto.joaobatista@imasters.com.br> */ class ConditionalQuestion extends Question { /** * @var Interviewer */ private $interviewer; /** * @var array[Question] */ private $questions = array(); /** * Constroi uma pergunta que depende de uma resposta anterior. * @param Interviewer $interviewer O entrevistador que contém o método de * acesso à última pergunta dada pelo entrevistado. */ public function __construct( Interviewer $interviewer ) { parent::__construct( null ); $this->interviewer = $interviewer; } /** * Define uma pergunta condicionada a uma determinada alternativa anterior. * @param Question $question A pergunta que será definida. * @param Alternative $alternative A alternativa que condiciona a pergunta. */ public function setQuestionForAlternative( Question $question, Alternative $alternative ) { $this->questions[ spl_object_hash( $alternative ) ] = $question; } /** * @return Question */ private function getConditionalQuestion() { $answer = $this->interviewer->getLastAnswer(); if ( $answer !== null ) { $hash = spl_object_hash( $answer->getAlternative() ); if ( isset( $this->questions[ $hash ] ) ) { return $this->questions[ $hash ]; } } } /* (non-PHPdoc) * @see Question::getAlternatives() */ public function getAlternatives() { $question = $this->getConditionalQuestion(); if ( $question !== null ) { return $question->getAlternatives(); } } /* (non-PHPdoc) * @see Question::getQuestion() */ public function getQuestion() { $question = $this->getConditionalQuestion(); if ( $question !== null ) { return $question->getQuestion(); } return null; } } ConditionalQuestion encapsula o entrevistador para, quando for necessário, obter a última resposta dada. Além disso, ele armazenará as possíveis próximas perguntas, utilizando a alternativa dada como chave. Utilizando o novo código: <?php require_once 'Question.php'; require_once 'ConditionalQuestion.php'; require_once 'Alternative.php'; require_once 'Interview.php'; require_once 'Interviewee.php'; require_once 'Interviewer.php'; $interviewer = new Interviewer(); $interviewer->setName( 'João Batista Neto' ); $interviewee = new Interviewee(); $interviewee->setName( 'Juliano Amadeu' ); $sim = new Alternative( 'Sim!' ); $nao = new Alternative( 'Não!' ); $question1 = new Question( 'Você gosta de OOP?' ); $question1->addAlternative( $sim ); $question1->addAlternative( $nao ); $question2a = new Question( 'O que é mais legal em OOP?' ); $question2a->addAlternative( new Alternative( 'O relacionamento entre objetos.' ) ); $question2a->addAlternative( new Alternative( 'A delegação de responsabilidades.' ) ); $question2a->addAlternative( new Alternative( 'A abstração das coisas?' ) ); $question2b = new Question( 'Porque você não gosta de OOP?' ); $question2b->addAlternative( new Alternative( 'Não entendo isso.' ) ); $question2b->addAlternative( new Alternative( 'Prefiro procedural.' ) ); $question2b->addAlternative( new Alternative( 'Prefiro funcional?' ) ); $question2 = new ConditionalQuestion( $interviewer ); $question2->setQuestionForAlternative( $question2a , $sim ); $question2->setQuestionForAlternative( $question2b , $nao ); $interview = new Interview(); $interview->addQuestion( $question1 ); $interview->addQuestion( $question2 ); $interviewer->interview( $interview , $interviewee ); Se a resposta para a primeira pergunta for não, a possível saída será: João Batista Neto - Juliano Amadeu, Você gosta de OOP? Juliano Amadeu - João Batista Neto, Não! João Batista Neto - Juliano Amadeu, Porque você não gosta de OOP? Juliano Amadeu - João Batista Neto, Prefiro procedural. Se a resposta para a primeira pergunta for sim, a possível saída será: João Batista Neto - Juliano Amadeu, Você gosta de OOP? Juliano Amadeu - João Batista Neto, Sim! João Batista Neto - Juliano Amadeu, O que é mais legal em OOP? Juliano Amadeu - João Batista Neto, O relacionamento entre objetos. ;) public class Application { public static void main(String[] argv){ } } [+1] Pelo Java like! Compartilhar este post Link para o post Compartilhar em outros sites
Henrique Barcelos 290 Denunciar post Postado Março 4, 2012 Eu me pus a matutar sobre o assunto e comecei a enxergar o princípio dessa implementação aí, mas fui interrompido. Não sei onde eu chegaria, mas a implementação do JBN ficou mais uma vez fantástica (ah, vah?). Dei uma analisada rápida aqui e cheguei nessa modelagem: EDIT: só percebi depois de exportar que havia um erro na modelagem, atualizei a imagem agora. Notem que ela não é exatamente trivial... Pronto Juliano amadeu, agora você tem a faca e o queijo não mão... bom divertimento... [+1] Pelo Java like! O exemplo estava em Java, resolvi manter em Java =]... Estou iniciando no mundo Java, pra falar a verdade... Neste trecho, por exemplo: DecimalFormat twoDForm = new DecimalFormat("#.##"); double rounded = Double.valueOf(twoDForm.format(valor); tive que recorrer ao Google... :grin: Compartilhar este post Link para o post Compartilhar em outros sites
JCMais 75 Denunciar post Postado Março 4, 2012 Da forma implementada com o Strategy, somente poderia ter uma única questão condicionada pela resposta da questão anterior. Se digamos, fosse necessário escolher várias outras questões, seria interessante criar um agrupamento dessas questões? Tipo QuestionSet? Aí criaria uma nova implementação da strategy, ConditionalQuestionSet? Compartilhar este post Link para o post Compartilhar em outros sites
João Batista Neto 448 Denunciar post Postado Março 4, 2012 Da forma implementada com o Strategy, somente poderia ter uma única questão condicionada pela resposta da questão anterior. Não compreendi a questão. Veja, não há como fazer várias perguntas ao mesmo tempo para um entrevistado. Ele responderá, sempre, uma pergunta de cada vez. Se digamos, fosse necessário escolher várias outras questões, seria interessante criar um agrupamento dessas questões? Tipo QuestionSet? Aí criaria uma nova implementação da strategy, ConditionalQuestionSet? Matematicamente falando, Set é um conjunto de itens únicos. Se você for ver bem, apesar de não ter definido nenhuma regra de verificação de unicidade, estamos definindo várias questões únicas. <?php require_once 'Question.php'; require_once 'ConditionalQuestion.php'; require_once 'Alternative.php'; require_once 'Interview.php'; require_once 'Interviewee.php'; require_once 'Interviewer.php'; $interviewer = new Interviewer(); $interviewer->setName( 'João Batista Neto' ); $interviewee = new Interviewee(); $interviewee->setName( 'Juliano Amadeu' ); $a1_1 = new Alternative( 'Alternativa 1 da questão 1' ); $a2_1 = new Alternative( 'Alternativa 2 da questão 1' ); $a3_1 = new Alternative( 'Alternativa 3 da questão 1' ); $question1 = new Question( 'Questão 1' ); $question1->addAlternative( $a1_1 ); $question1->addAlternative( $a2_1 ); $question1->addAlternative( $a3_1 ); $a1_2a = new Alternative( 'Alternativa 1 da questão 2a' ); $a2_2a = new Alternative( 'Alternativa 2 da questão 2a' ); $a3_2a = new Alternative( 'Alternativa 3 da questão 2a' ); $question2a = new Question( 'Questão 2a que depende da alternativa 1 da questão 1' ); $question2a->addAlternative( $a1_2a ); $question2a->addAlternative( $a2_2a ); $question2a->addAlternative( $a3_2a ); $a1_2b = new Alternative( 'Alternativa 1 da questão 2b' ); $a2_2b = new Alternative( 'Alternativa 2 da questão 2b' ); $a3_2b = new Alternative( 'Alternativa 3 da questão 2b' ); $question2b = new Question( 'Questão 2b que depende da alternativa 2 da questão 1' ); $question2b->addAlternative( $a1_2b ); $question2b->addAlternative( $a2_2b ); $question2b->addAlternative( $a3_2b ); $a1_2c = new Alternative( 'Alternativa 1 da questão 2c' ); $a2_2c = new Alternative( 'Alternativa 2 da questão 2c' ); $a3_2c = new Alternative( 'Alternativa 3 da questão 2c' ); $question2c = new Question( 'Questão 2c que depende da alternativa 3 da questão 1' ); $question2c->addAlternative( $a1_2c ); $question2c->addAlternative( $a2_2c ); $question2c->addAlternative( $a3_2c ); $question2 = new ConditionalQuestion( $interviewer ); $question2->setQuestionForAlternative( $question2a , $a1_1 ); $question2->setQuestionForAlternative( $question2b , $a2_1 ); $question2->setQuestionForAlternative( $question2c , $a3_1 ); $question3a = new Question( 'Questão 3a que depende da alternativa 1 da questão 2a' ); $question3a->addAlternative( new Alternative( 'Alternativa 1 da questão 3a' ) ); $question3a->addAlternative( new Alternative( 'Alternativa 2 da questão 3a' ) ); $question3a->addAlternative( new Alternative( 'Alternativa 3 da questão 3a' ) ); $question3b = new Question( 'Questão 3b que depende da alternativa 2 da questão 2a' ); $question3b->addAlternative( new Alternative( 'Alternativa 1 da questão 3b' ) ); $question3b->addAlternative( new Alternative( 'Alternativa 2 da questão 3b' ) ); $question3a->addAlternative( new Alternative( 'Alternativa 3 da questão 3b' ) ); $question3c = new Question( 'Questão 3c que depende da alternativa 3 da questão 2a' ); $question3c->addAlternative( new Alternative( 'Alternativa 1 da questão 3c' ) ); $question3c->addAlternative( new Alternative( 'Alternativa 2 da questão 3c' ) ); $question3c->addAlternative( new Alternative( 'Alternativa 3 da questão 3c' ) ); $question3d = new Question( 'Questão 3d que depende da alternativa 1 da questão 2b' ); $question3d->addAlternative( new Alternative( 'Alternativa 1 da questão 3d' ) ); $question3d->addAlternative( new Alternative( 'Alternativa 2 da questão 3d' ) ); $question3d->addAlternative( new Alternative( 'Alternativa 3 da questão 3d' ) ); $question3e = new Question( 'Questão 3e que depende da alternativa 2 da questão 2b' ); $question3e->addAlternative( new Alternative( 'Alternativa 1 da questão 3e' ) ); $question3e->addAlternative( new Alternative( 'Alternativa 2 da questão 3e' ) ); $question3e->addAlternative( new Alternative( 'Alternativa 3 da questão 3e' ) ); $question3f = new Question( 'Questão 3f que depende da alternativa 3 da questão 2b' ); $question3f->addAlternative( new Alternative( 'Alternativa 1 da questão 3f' ) ); $question3f->addAlternative( new Alternative( 'Alternativa 2 da questão 3f' ) ); $question3f->addAlternative( new Alternative( 'Alternativa 3 da questão 3f' ) ); $question3g = new Question( 'Questão 3g que depende da alternativa 1 da questão 2c' ); $question3g->addAlternative( new Alternative( 'Alternativa 1 da questão 3g' ) ); $question3g->addAlternative( new Alternative( 'Alternativa 2 da questão 3g' ) ); $question3g->addAlternative( new Alternative( 'Alternativa 3 da questão 3g' ) ); $question3h = new Question( 'Questão 3h que depende da alternativa 2 da questão 2c' ); $question3h->addAlternative( new Alternative( 'Alternativa 1 da questão 3h' ) ); $question3h->addAlternative( new Alternative( 'Alternativa 2 da questão 3h' ) ); $question3h->addAlternative( new Alternative( 'Alternativa 3 da questão 3h' ) ); $question3i = new Question( 'Questão 3i que depende da alternativa 3 da questão 2c' ); $question3i->addAlternative( new Alternative( 'Alternativa 1 da questão 3i' ) ); $question3i->addAlternative( new Alternative( 'Alternativa 2 da questão 3i' ) ); $question3i->addAlternative( new Alternative( 'Alternativa 3 da questão 3i' ) ); $question3 = new ConditionalQuestion( $interviewer ); $question3->setQuestionForAlternative( $question3a , $a1_2a ); $question3->setQuestionForAlternative( $question3b , $a2_2a ); $question3->setQuestionForAlternative( $question3c , $a3_2a ); $question3->setQuestionForAlternative( $question3d , $a1_2b ); $question3->setQuestionForAlternative( $question3e , $a2_2b ); $question3->setQuestionForAlternative( $question3f , $a3_2b ); $question3->setQuestionForAlternative( $question3g , $a1_2c ); $question3->setQuestionForAlternative( $question3h , $a2_2c ); $question3->setQuestionForAlternative( $question3i , $a3_2c ); $interview = new Interview(); $interview->addQuestion( $question1 ); $interview->addQuestion( $question2 ); $interview->addQuestion( $question3 ); $interviewer->interview( $interview , $interviewee ); Veja que temos agora 3 possíveis "segunda questão", e várias possíveis "terceira questão", todas dependentes das respostas anteriores (não vou alongar nas possibilidades pois se trata de uma P.G.), A possível saída será: João Batista Neto - Juliano Amadeu, Questão 1 Juliano Amadeu - João Batista Neto, Alternativa 1 da questão 1 João Batista Neto - Juliano Amadeu, Questão 2a que depende da alternativa 1 da questão 1 Juliano Amadeu - João Batista Neto, Alternativa 1 da questão 2a João Batista Neto - Juliano Amadeu, Questão 3a que depende da alternativa 1 da questão 2a Juliano Amadeu - João Batista Neto, Alternativa 3 da questão 3b Como pode ver, essa estrutura comportamental provida pelo Strategy nos forneceu uma entrevista, praticamente, como um R.P.G., onde o resultado é dependente de ações anteriores. ;) Compartilhar este post Link para o post Compartilhar em outros sites