Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Na regra Single responsibility principle dos princípios SOLID, fala que a classe tem que ter somente uma responsabilidade. Afinal se uma classe tem que ter uma única responsabilidade, ela terá somente um método?? não entendi muito bem esse conceito.
1 - Quando eu vejo tutorias sendo vídeo ou artigos , neles explicam que devemos prever todos os comportamentos de um objeto, mas 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??
3 - caso algum método da classe não estiver no conceito de responsabilidade da classe, logo ele terá que sair dessa classe não é, mas se a classe precisar desse método como fica isso?
Por favor me deem um exemplo: de como "fazer" e "não fazer", dentro dessa regra de única responsabilidade da classe.
1) Não, no caso da hipotética classe pessoa você deve separar os comportamentos entre diferentes classes. Você pode ter por exemplo as classes: PessoaExibe, Pessoa e PessoaAcao. Veja o pseudo código abaixo.
//Responsabilidade de gerenciar os dados da Pessoa.
class Pessoa {
$nome;
$idade;
// Metodos sets e Gets
}
//Responsabilidade de Exibir os dados da Pessoa
class PessoaExibe{
$pessoa;
__construct (Pessoa $pessoa){
$this->pessoa=$pessoa;
}
function exibePessoa(){
return "Esta pessoa tem a idade de $this->pessoa->getIdade() anos e o nome de $this->pessoa->getName()";
}
}
//Responsabilida de realizar ações da Pessoa
PessoaAcao{
$pessoa;
function __construct (Pessoa $pessoa){
$this->pessoa=$pessoa;
}
function andar(){
//Código de implementação desta função }
function correr(){
//Código de implementação desta função }
}
Se a classe de ações ficar muito grande você pode separar por categorias ou abstrai-la em diferentes classes.
Ex.:
classe PessoaActionMovimento - correr, adar,
classe PessoActionBoca - comer, falar,
etc...
2) Isso é um pouco intuitivo, partir da pergunta "O que essa classe faz?" é um bom principio, se a resposta for com muitos "es" ou com muios "ous" isso sugere que a classe esta fazendo muita coisa. Ex.: "Esta classe valida coisas e as imprime na tela". Estudar Taxonomia também ajuda separar melhor as classes.
3)Você pode fazer como nas classes do exemplo acima, as classes PessoaExibe e PessoaAcao precisam dos métodos da classe pessoa, para usa-los elas recebem um objeto pessoa durante a instanciação.
Ficaria assim:
$pessoa = New Pessoa();
$pessoa->setNome("João");
$pessoa->setIdade(25);
$exibePessoa = New PessoaExibe($pessoa);
echo $exibePessoa -> exibePessoa(); //Esta pessoa tem a idade de 25 anos e o nome de João.Gabriel Heming, eu também estou com uma dúvida sobre DAO.
Vamos supor que eu tenho PessoaDAO e lá teria os métodos save, update, delete etc. Isso não daria mais de uma responsabilidade para a classe?
> Vamos supor que eu tenho PessoaDAO e lá teria os métodos save, update, delete etc. Isso não daria mais de uma responsabilidade para a classe?
Não, save update e delete lidão com a mesma coisa (banco de dados), fazem coisas diferentes mais segue a responsabilidade de uma classe DAO...Não. É (muito) difícil definir responsabilidade. Save, update, delete, etc. são ações, todas relacionadas com o acesso.
Uma forma de detectar god objects e violação de princípios S.O.L.I.D. é através dos testes unitários. Quando o teste começa a "feder", é um bom sinal de que o código está com problemas de design, geralmente.
>
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??
O único comportamento exclusivo de uma pessoa ai é falar.
Cachorros andam, gatos caem, cavalos comem....
ai já se começa entrar em abstração.
Um exemplo que acho bem fácil de entender é um objeto que é responsável por upar um arquivo para o servidor.
esse objeto tem apenas como responsabilidade pegar o arquivo e jogar em um lugar X mas ele não tem obrigação nenhuma de saber QUAL ARQUIVO é esse, se ele esta no formato certo ou não, se o tamanho dele é maior que o permitido...
isso tudo não importa por que o objeto já tem que receber mastigado e fazer o trabalho dele passar de um lugar para o outro.
se você fizer um objeto que recebe, trata o arquivo, da um nome novo para ele, e depois joga no servidor você tem um god class.
Obrigada gente pela ajuda :)
Só umas dúvidas:
O princípio de responsabilidade única define que ela, a classe, não pode ter mais de um motivo para ser modificada. Se ela tiver mais de um motivo, não tem uma única responsabilidade.
O que seria esse "a classe, não pode ter mais de um motivo para ser modificada":
Um outro objeto, que seja do contexto do método, poderá ser criado (caso não exista) e se encarregará da responsabilidade daquele método. Utilizando o mesmo exemplo da pessoa, uma pessoa não "se salva" no SGBD, mas uma classe de persistência sim, como um DAO, por exemplo.
>
O que seria esse "a classe, não pode ter mais de um motivo para ser modificada":
Não para ambos os questionamentos.
Seria a classe possuir contextos diferentes que precisam ser modificados por razões diferentes. Como já mencionamos, não é fácil definir responsabilidades, tem que se olhar o contexto por todos os lados.
Vou tentar ser um pouco mais didático, utilizando a resposta a sua pergunta seguinte (que está lá em baixo).
A classe PessoaDAO (nossa classe de persistência fictícia no momento) possui como única responsabilidade a persistência dos dados do objeto Pessoa. A única razão pela qual a classe PessoaDAO pode ser modificada é quando a própria classe Pessoa mudar (novo campo adicionado/removido, etc).
Caso você tiver que mudar a classe PessoaDAO por outros motivos que não sejam diretamente ligados a classe Pessoa (conexão com o SGBD mudou, o objeto pessoa deverá ser armazenado/obtido em sessão), significa que ela possui mais de uma razão para ser modificada.
A classe PessoaDAO não precisa saber (especificadamente) aonde deverá persistir os dados. Somente que deve persistir os dados de Pessoa em algum lugar, conforme sua dependência: pode ser uma abstração de um Storage, por exemplo, aonde PessoaDAO espera persistir os dados no objeto Storage. O Storage, em específico, é irrelevante para classe PessoaDAO.
>
Quando eu me refiro a classe de persistência, estou me referindo a classe responsável pela persistência do objeto Pessoa. Classe, a qual, faz parte da camada de persistência.
Qualquer dúvida, não deixe de perguntar/questionar.
Adendo (leituras para esclarecer certas dúvidas sobre alguns exemplos):
excelente explicação do Gabriel, agora ficou fácil.
Nossa agora clareou muita coisa!
Mas deixo eu ver se entendi, a 'classe PessoaDAO' só existe para tratar dados da 'classe pessoa' no banco, como (salvar, criar, deletar, alterar) registro no bd.
Resumindo: a classe pessoaDAO é a persistência da classe Pessoa ou seja a classe pessoaDAO é responsável pela gerencia de dados da classe pessoa
Correto??
Queridos, a responsabilidade e o motivo para modificações pode ser facilmente entendido como um "assunto".
Um exemplo um pouco mais palpável seria descrever um cozinheiro.
Um cozinheiro faz pratos: [inline]fazerOmelete()[/inline], [inline]fazerSanduiche()[/inline], [inline]fazerFiletDeFrangoAoMolhoCarbonara()[/inline].
Quantas responsabilidades o cozinheiro tem? Uma. fazer pratos. E ele tem apenas um único motivo para alterar seu modus operandi: Alterar a quantidade de pratos que serão disponibilizados.
Entendam que, assim como responsabilidade única não significa fazer apenas uma coisa, ter um único motivo para ser modificado não significa alterar o conteúdo de seus métodos, mas sim de sua interface. Os métodos devem ser alterados o tempo todo. O nome disso é refatoração e pertence a outra temática.
Uma godclass possui métodos como [inline]fazerOmelete()[/inline] mas também implementa métodos como [inline]recolherPratosDaMesa()[/inline] e [inline]levarOTrocoDaConta()[/inline].
Esta abordagem aumenta os motivos pelo qual a classe será modificada: Se formos aceitar pagamentos com cartão, se formos parar de recolher os pratos e passaremos a pedir que os clientes levem às bandejas às lixeiras, se formos começar oferecer serviço de Vallet para estacionar os carros...
Sim, baseando-se no nosso exemplo. DAO é apenas um de inúmeros padrões existentes, alguns muitos similares, outros extremamente diferentes.
@evandro
baseando se no seu exemplo sobre o cozinheiro ficaria mais ou menos assim??
class cozinheiro
{
public function fazerPratos( Cardapio $cardapio )
{
if( $cardapio->pratosDisponiveis() ) {
echo 'prato feito';
} else {
echo 'desculpe esse prato não está disponivel';
}
}
}>
@evandro
baseando se no seu exemplo sobre o cozinheiro ficaria mais ou menos assim??
class cozinheiro
{
public function fazerPratos( Cardapio $cardapio )
{
if( $cardapio->pratosDisponiveis() ) {
echo 'prato feito';
} else {
echo 'desculpe esse prato não está disponivel';
}
}Na verdade, o exemplo do cozinheiro geraria um pseudocódigo similar a isto aqui:
class cozinheiro {
public function paellaDeFrutosDoMar() {
// um grande chef nunca revela suas receitas ;)
return new PaellaDeFrutosDoMar;
}
public function pastaAoPomodoro() {
return new PastaAoPomodoro;
}
public function lagartoAoMolhoMadeira() {
return new LagartoAoMolhoMadeira;
}
}É muito mais fácil de explicar este princípio quando se tem o domínio do conceito de interfaces. Eu poderia simplesmente dizer que o princípio de responsabilidade única prega que uma Entidade deve ter um único motivo para alterar sua interface.
No caso do cozinheiro, alteramos sua interface por um único motivo: alterar a quantidade de pratos disponíveis. Seja para mais ou para menos.
Só salientando aqui que só quando paramos pra fazer esse tipo de análise nos damos conta da importância de planejar antes de sair escrevendo código.
Através da UML fica mais fácil entender quando nossos objetos possuem muitas responsabilidades.
Não sei se já foi falado, mas responsabilidade única não tem nenhum efeito sobre o tamanho que a classe deve ter. Desde que a coesão seja alta, a classe pode ter quantos métodos e atributos forem necessários.
Exatamente, o SRP pode ser violado numa classe com um método, assim como pode ser seguido numa classe com 30 métodos.
Um exemplo do mundo real de violação de SRP é quando há implementação de ActiveRecord junto com validação/filtro/eventos/logs, as famigeradas "Models" de alguns frameworks.
1 - Aproveitando o tópico, alguém poderia dar um exemplo na prática de alto acoplamento e alta coesão ?
2 - uma classe que tem uma alta coesão, e por que ela está seguindo a regra da responsabilidade única? correto?
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.
2 - uma classe que tem uma alta coesão, e por que ela está seguindo a regra da responsabilidade única? correto?
Sim. O SRP influi tanto na coesão (aumentando-a) quanto no acoplamento (diminuindo-o).
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.
>
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.
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.
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.
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.
>
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);
}
}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(...);
Ter uma responsabilidade e ter apenas um método não é a mesma coisa. Fazer muita coisa não é o mesmo que ter muitas responsabilidades.
O princípio de responsabilidade única define que ela, a classe, não pode ter mais de um motivo para ser modificada. Se ela tiver mais de um motivo, não tem uma única responsabilidade.
1 - Não, isso não é god class. A god class ou god object é uma classe/objeto que possui muitas responsabilidades, comumente associada a frase "knows too much or does too much". Normalmente, o god object, possui um acoplamento e dependência tão fortes, que qualquer modificação em sua estrutura pode afetar todo uma aplicação. É aquela classe que trata uma requisição, se conecta ao banco de dados, retorna as informações e "printa" o HTML (tudo ao mesmo tempo).
2 - Não existe uma fórmula mágica para saber, existem alguns meios. Robert "UncleBob" Martin define, no livro Clean Code, algumas pequenas dicas, não são leis absolutas, mas ajudam. Por exemplo, métodos devem ser definidos conforme o escopo de uma classe, se a classe for uma Pessoa, todos os métodos devem ser de responsabilidade da pessoa. Por exemplo: correr, andar, comer são métodos atrelados a uma pessoa. Já o método salvar não é de responsabilidade da pessoa, e sim de um objeto de persistência e/ou outro objeto.
Outros detalhes mencionados, dentro de um método, é a quantidade de níveis que um método tem. Se tiver mais de um (if, else, while) a estrutura tem fortes chances de ser repensada.
Mas isso tudo não são regras/leis, são apenas dicas. Existem diversas que eu desconheço. Com prática, isso ficará instintivo e você nem saberá/perceberá o porque tomou tal decisão (olhando pelo ponto de vista da abstração que desejou).
O livro Agile Software Development: Principles, Patterns and Practices trata mais desse assunto (no final do post tem um link do capítulo), eu não cheguei a le-lo (está complicado de conseguir um exemplar), por isso não posso dar outros exemplos do livro.
3 - Um outro objeto, que seja do contexto do método, poderá ser criado (caso não exista) e se encarregará da responsabilidade daquele método. Utilizando o mesmo exemplo da pessoa, uma pessoa não "se salva" no SGBD, mas uma classe de persistência sim, como um DAO, por exemplo. O DAO é responsável pelo acesso aos dados do objeto. Logo, um PessoaDAO, tem a responsabilidade de realizar a persistência do objeto Pessoa. O método salvar, utiliza um objeto pessoa, não é de responsabilidade da pessoa e sim de responsabilidade do objeto de Persistência.
http://www.objectmentor.com/resources/articles/srp.pdf