Evandro Oliveira 331 Denunciar post Postado Outubro 24, 2013 Eu sempre tive muita dificuldade com esses princípios mas pelo pseudo-código que você postou, Evandro, não estaria violando outro dos princípios? Porque a cada modificação no Cardápio, seria preciso mexer na classe não para refatorar. Eu vejo que na verdade, o pseudo-código correto deveria ser (após algumas adições): class Cook { private $menu; public function __construct( Menu $menu ) { $this -> menu =& $menu; } public function make( Food $food ) { if( $this -> menu -> available( $food ) ) { // Cook it! } else { throw new RestaurantException( sprintf( 'Sorry! %s is not available at this time', ucfirst( (string) $food ) ) ); } } } Sendo que, nesse caso, Menu, suprimida do exemplo, seria uma coleção estendível de pratos possíveis de serem preparados. Seu código é análogo à proposta da @Lorena Ribeiro aqui. Ela chama de [inline]Cardápio[/inline] você chama de [inline]Menu[/inline]. Portanto, a resposta segue a mesma...[...] Eu poderia simplesmente dizer que o princípio de responsabilidade única prega que uma Entidade deve ter um único motivo para alterar sua interface.Por entidade não me refiro às entidades mapeadas das tabelas de db, mas sim às entidades de um ecossistema OO. Interfaces, classes (abstratas ou não), traits, closures, etc. Uma abordagem mais abstraída do cozinheiro do meu exemplo, além de válida é encorajada. Mas por uma série de outros benefícios que não a Violação ou não do SRP. Note que a minha implementação tange exatamente à pergunta tecida no primeiro post: [...] Afinal se uma classe tem que ter uma única responsabilidade, ela terá somente um método?? [...] [...] se prevermos todos os comportamentos de um objeto, isso não estaria fugindo do princípio da responsabilidade única da classe Exemplos: vamos prever comportamentos de um pessoa (andar, correr, falar, comer, cair, etc ...) se eu estou prevendo todos os comportamento de um objeto estou fazendo uma godclass?? estou certo?? 2 - Como saber se todos os métodos estão aplicando uma única responsabilidade na classe?? [...] [...] Por favor me deem um exemplo: de como "fazer" e "não fazer", dentro dessa regra de única responsabilidade da classe. Principalmente no trecho que cita comportamentos, note que um cozinheiro tem que se comportar de formas diferentes conforme o prato desejado. O objetivo era, apenas, demonstrar uma forma de se implementar 9347572923024753 métodos numa única classe que não violem o SRP. Que existem formas mais otimizadas e melhoradas de se fazer isso, não tenham dúvidas que sim. Compartilhar este post Link para o post Compartilhar em outros sites
Bruno Augusto 417 Denunciar post Postado Outubro 24, 2013 Não exatamente a mesma coisa porque ao invés de estar verificando se pratos podem ser feitos eu estou verificando se aquele prato em particular pode ser feito, ao invés de ter uma mega-classe servindo apenas de ponte para N instâncias requerendo que ela seja modificada a cada adição OU remoção. Eu posso ter rolado a tela um pouco rápido demais. Mas mesmo se transformar o Cozinheiro numa Factory seria estranho, pra não dizer errado já que o cozinheiro não produz apenas pratos. Compartilhar este post Link para o post Compartilhar em outros sites
Evandro Oliveira 331 Denunciar post Postado Outubro 24, 2013 Eu posso ter rolado a tela um pouco rápido demais. Mas mesmo se transformar o Cozinheiro numa Factory seria estranho, pra não dizer errado já que o cozinheiro não produz apenas pratos. Uma abordagem mais abstraída do cozinheiro do meu exemplo, além de válida é encorajada. Mas por uma série de outros benefícios que não a Violação ou não do SRP. [...] O objetivo era, apenas, demonstrar uma forma de se implementar 9347572923024753 métodos numa única classe que não violem o SRP. Que existem formas mais otimizadas e melhoradas de se fazer isso, não tenham dúvidas que sim. Compartilhar este post Link para o post Compartilhar em outros sites
Bruno Augusto 417 Denunciar post Postado Outubro 25, 2013 Tá bem, não está mais aqui quem falou. <_< Porém isso só reafirma minha opinião sobre o porquê de a Orientação a Objetos em PHP, pelo menos nos fóruns brasileiros, é a porcaria que é. Não é porque você pode construir uma casa com qualquer tipo de material que você vai de fato fazê-lo, assim como não é porque existem virtualmente infinitas formas de se codificar que logo de cara se demonstra a menos plausível possível. Compartilhar este post Link para o post Compartilhar em outros sites
Manoel Barros 1 Denunciar post Postado Outubro 28, 2013 1 - Aproveitando o tópico, alguém poderia dar um exemplo na prática de alto acoplamento e alta coesão ? O ideal é acoplamento BAIXO e coesão ALTA. Vou pensar em algo aqui e posto. Realmente o que eu escrevi dá a intender para fazer os 2(dois) jeito de uma única vez juntos =) Desculpe o mal entendimento, mas na verdade eu queria e que desse um exemplo de alto acoplamento e outro exemplo de alta coesão, somente para visualizar as principais diferenças * dos 2 princípios. O @Gabriel Heming indicou um artigo aqui neste tópico mesmo (Inversão de Controle e Injeção de Dependência) no post http://forum.imasters.com.br/topic/510435-solid-princpio-da-responsabilidade-nica/?p=2020921 Aqui estão os modelos apresentado lá: public class VendaDeProduto { public void vendeProduto(Produto produto) { //Todo o código para a venda do produto... Log log = new Log("Arquivo.txt"); log.grava(produto); } } Uma coisa que não entendi e que o artigo afirma que a instância de Log não e de responsabilidade da classe "VendaDeProduto" instanciar Então para resolver isso foi criado um atributo log na classe, que servirá para guarda a instancia de Log que vem de um argumento do método "VendaDeProduto" public class VendaDeProduto { private Log log; public void VendaDeProduto(Log logVenda) { this.log = logVenda; } public void vendeProduto(Produto produto) { //Todo o código para a venda do produto... log.grava(produto); } } Quando saber a hora certa de fazer composição num objeto? quando saber que o objeto pai tem a responsabilidade de instanciar um objeto dentro dele? ou sempre usar agregação e nunca usar composição em objetos?? Compartilhar este post Link para o post Compartilhar em outros sites
Enrico Pereira 299 Denunciar post Postado Outubro 28, 2013 Isso cai no conceito de Dependency Injection. Deixo aqui duas perguntas para você: e se eu quiser mudar o logger para mandar logs por email e se eu quiser simplesmente usar um logger falso que não faz log de nada para fazer um teste? Nesse caso, precisaria editar a classe, violando outro princípio S.O.L.I.D., o OCP. No caso de usar DI, podemos até melhorar isso, usando interfaces e tirando benefícios de polimorfismo: public interface Log { public void log(String message); } public class FileLog implements Log { private String file; public FileLog(String file) { this.file = file; } public void log(String message) { // salvar mensagem de log em arquivo.. } } public class MailLog implements Log { // Mailer pode ser uma classe qualquer para enviar email, não é responsabilidade do log isso private Mailer mailer; public MailLog(Mailer mailer) { this.mailer = mailer; } public void log(String message) { // enviar mensagem de log por email.. } } public class FakeLog implements Log { public void log(String message) { // não fazer nada } } E mantendo a implementação que você mostrou no post usando DI, podemos simplesmente usar tudo isso: // através de arquivo Log fileLogger = new FileLog("foobarbaz.txt"); VendaDeProduto venda1 = new VendaDeProduto(fileLogger); venda1.vendeProduto(...); // através de email Log mailLogger = new MailLog(...); VendaDeProduto venda2 = new VendaDeProduto(mailLogger); venda2.vendeProduto(...); // sem fazer log algum Log fakeLogger = new FakeLog(); VendaDeProduto venda3 = new VendaDeProduto(fakeLogger); venda3.vendeProduto(...); Compartilhar este post Link para o post Compartilhar em outros sites
Manoel Barros 1 Denunciar post Postado Outubro 28, 2013 Boa explicativa @Enrico Pereira, agora entendi Mas só reforçando o que você explicou, que fazendo agregação seria bem melhor, no caso do log. Então, sempre prefira Agregação do que Composição ?? Compartilhar este post Link para o post Compartilhar em outros sites
Enrico Pereira 299 Denunciar post Postado Outubro 28, 2013 Depende do caso: http://imasters.com.br/artigo/18901/ Compartilhar este post Link para o post Compartilhar em outros sites
Manoel Barros 1 Denunciar post Postado Outubro 28, 2013 Só mas uma coisinha @Enrico Pereira E se o por exemplo, a classe "VendaDeProduto" precisasse de gravar o produto no arquivo "log" e também enviar o log por "email". Nesse caso teria duas instâncias do objeto log diferente, que a classe "VendaDeProduto" iria usar! Como ia ser feito agora, baseando no exemplo acima?? Compartilhar este post Link para o post Compartilhar em outros sites
Evandro Oliveira 331 Denunciar post Postado Outubro 28, 2013 Só mas uma coisinha @Enrico Pereira E se o por exemplo, a classe "VendaDeProduto" precisasse de gravar o produto no arquivo "log" e também enviar o log por "email". Nesse caso teria duas instâncias do objeto log diferente, que a classe "VendaDeProduto" iria usar! Como ia ser feito agora, baseando no exemplo acima?? Uma das formas é implementar uma Collection. Compartilhar este post Link para o post Compartilhar em outros sites
Manoel Barros 1 Denunciar post Postado Outubro 29, 2013 @Evandro Oliveira pesquisei sobre Collection na web. E eu fiz o seguinte código baseado nos exemplos acima veja: obs.: O exemplos anteriores estavam em JAVA converti para php. Uma nova interface interface iCollectionLog { // Só vai entrar objetos com comportamentos da interface `Log´ nesse método addItem(). public function addItem(Log $obj, $key = null); public function deleteItem($key); public function getItem($key); } Classe que faz a coleção dos objetos Log class CollectionLog implements iCollectionLog { private $items = array(); public function addItem(Log $obj, $key = null) { if ($key == null) { $this->items[] = $obj; } else { if (isset($this->items[$key])) { throw new KeyHasUseException("A Key $key já está em uso."); } else { $this->items[$key] = $obj; } } } public function deleteItem($key) { if (isset($this->items[$key])) { unset($this->items[$key]); } else { throw new KeyInvalidException("Chave inválida $key."); } } public function getItem($key) { if (isset($this->items[$key])) { return $this->items[$key]; } else { throw new KeyInvalidException("Chave inválida $key."); } } } Com a classe collectionLog criada, vamos implementar ela dentro da classe VendaDeProduto class VendaDeProduto { private $logCollection; public function __construct(iCollectionLog $logCollection) { $this->logCollection = $logCollection; } public function vendeProduto(Produto $produto) { //Todo o código para a venda do produto... $this->logCollection->getItem("FileLog")->log($produto);// envia para o arquivo $this->logCollection->getItem("MailLog")->log($produto);// envia para o email } } Regra de negócio $CollectionLog = new CollectionLog(); // Instância o objeto que guarda a coleção de Logs $CollectionLog->addItem(new FileLog("foobarbaz.txt"), "FileLog"); // add o objeto FileLog $CollectionLog->addItem(new MailLog(...), "MailLog"); // add o objeto MailLog $CollectionLog->addItem(new FakeLog(), "FakeLog"); // add o objeto FakeLog $venda = new VendaDeProduto($CollectionLog); $venda->vendeProduto(...); Se algo estiver errado, ou se existir outro maneira melhor, me corrijam. Compartilhar este post Link para o post Compartilhar em outros sites
Evandro Oliveira 331 Denunciar post Postado Outubro 29, 2013 O conceito você pegou perfeitamente! +1 Só pecou em não utilizar alguns recursos que o PHP te fornece de forma nativa como as interfaces SPL Iterator e/ou ArrayAccess. Como, em PHP, o retorno dos métodos não é tipado como seria em Java, você poderia deixar a cargo da sua documentação a informação que obter um item da lista de Loggers retornaria um logger. No caso de ter utilizado ArrayAccess, sua coleção de loggers poderia ser uma herança de ArrayObject para aproveitar as implementações internas já executadas pelo core do PHP. <?php interface Logger { const SEVERITY_DEBUG = 1; const SEVERITY_NOTICE = 2; const SEVEIRTY_WARNING = 4; const SEVERITY_ERROR = 8; /** * Logs the message * * @param string $message The message to log * @param integer [$severity] How critical the message is. */ public function log($message, $severity = NULL); } class LoggerCollection extends ArrayObject { /** * Stores a Logger. * * @param mixed $index Integer key or unique string identifier. * @param Logger $newval The Logger to store. * @throws InvalidArgumentException if $newval isn't a Logger instance. */ public function offsetSet($index, $newval) { if (!($newval instanceof Logger)) { throw new InvalidArgumentException( "The logger should be an instance of Logger." ); } parent::offsetSet($index, $newval); } /** * Retrieve a stored Logger. * * @inheritdoc * @return Logger */ public function offsetGet($index) { return parent::offsetGet($index); } } Compartilhar este post Link para o post Compartilhar em outros sites
Manoel Barros 1 Denunciar post Postado Outubro 29, 2013 Muito bom, valeu pela ajuda @Evandro Oliveira agora não seria melhor fazer assim: public function offsetSet($index, Logger $newval) { parent::offsetSet($index, $newval); } evitando implementações! Compartilhar este post Link para o post Compartilhar em outros sites
Israel Elias 8 Denunciar post Postado Outubro 31, 2013 Como, em PHP, o retorno dos métodos não é tipado como seria em Java, você poderia deixar a cargo da sua documentação a informação que obter um item da lista de Loggers retornaria um logger. Como assim o retorno dos métodos não é tipado em php?? Você citou que a classe ArrayObject garanti que o retorno do método vai retornar como objeto Logger, ela trata isso na entrada do método "offsetSet" ou na saída no método "offsetGet" essa tipagem?? Compartilhar este post Link para o post Compartilhar em outros sites
raonibs 64 Denunciar post Postado Outubro 31, 2013 A responsabilidade da classe é tratar os tipos na entrada do método, a do cliente é tratar os tipos retornados pelo método. Mesmo uma método "Set" pode retornar um tipo Exception ou uma string com erro conforme o caso. O php não induz o tipo retornado, por exemplo a classe offsetGet pode retornar o índice do valor ou um tipo Null. Compartilhar este post Link para o post Compartilhar em outros sites
Jeferson Daniel 11 Denunciar post Postado Outubro 31, 2013 Me parece que quando você começa a usar a classe LoggerCollection na classe VendaDeProduto ela ganha uma responsabilidade a mais. Motivo: Se eu precisar usar um logger a mais nas classes que estão enviando dessa forma, eu precisarei alterar a classe VendaDeProduto. Imaginei uma forma de resolver isso: - Ter a classe LoggerCollection implementando a interface Logger e seu método método Log delegando para todos os logs dentro da coleção. Dessa forma a classe VendaDeProduto nem precisa saber que esse Logger é uma coleção. class VendaDeProduto { private $logCollection; public function __construct(Logger $logCollection) { $this->logCollection = $logCollection; } public function vendeProduto(Produto $produto) { //Todo o código para a venda do produto... $this->logCollection->log($produto);// envia para todos logs pré configurados } } Compartilhar este post Link para o post Compartilhar em outros sites
Bruno Augusto 417 Denunciar post Postado Outubro 31, 2013 Isso não chega a ser uma nova responsabilidade e sim apenas uma nova delegação. A classe de Venda precisa registrar o log de venda de um produto. Ponto! Como não é responsabilidade dela manipular o ambiente de log ou tudo aquilo que o sistema de log precisa para funcionar, ela delega a responsabilidade para aquele de direito invocando o método correspondente do objeto associado. Estava aqui pensando... Pra separar ainda mais, ao invés de usar diretamente o Logger, usar um Observer, assim sua classe de Venda sequer saberia que o produto vendido está sendo registrado em log. Toda ação que fosse executada notificaria o Observer associado de que algo foi feito e o Observer é que faria o log. Compartilhar este post Link para o post Compartilhar em outros sites
Raul Silva 41 Denunciar post Postado Outubro 31, 2013 Lembro-me de um colega implementando o Observer dentro do sistema, mas não lembro muito bem (foi em 2010). Estou um pouco confuso, no site php.net existe a "implementação" desse pattern, SplObserver e SplSubject, o que é o que em algum exemplo prático? Eu criaria um LogObserver para os logs, uma PessoaObserver para a Pessoa e um CozinheiroObserver, para o cozinheiro, todas extendendo a SqlObserver, e a SplSubject seria por exemplo uma "collection" dos observers, iterando através do notify e chamando o método update de cada uma? SplObserver - http://www.php.net/manual/pt_BR/class.splobserver.php SplSubject - http://www.php.net/manual/pt_BR/class.splsubject.php Compartilhar este post Link para o post Compartilhar em outros sites
Bruno Augusto 417 Denunciar post Postado Outubro 31, 2013 Mas é bem por aí porque cada assunto a ser observado (Subject) pode ter virtualmente infinitos observadores. Essa classe de Venda por exemplo, poderia ter além de um LoggerObserver, um StockObserver pra deduzir o produto vendido do estoque da loja. A classe Venda notificaria todos os observadores e cada um faria uma coisa pequena. Compartilhar este post Link para o post Compartilhar em outros sites
Raul Silva 41 Denunciar post Postado Outubro 31, 2013 De quem seria a responsabilidade de definir os observadores? Por exemplo, o StockObserver relataria as vendas dos produtos, mas é a classe de venda que falaria: esses caras vão me observar ou eu definiria na hora de instancia-la ? Porque essa classe pode ter um StockObserver e um LoggerObserver. Estou me confundindo muito? Compartilhar este post Link para o post Compartilhar em outros sites