Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

lorena85

SOLID – Princípio da Responsabilidade Única

Recommended Posts

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

 

Por favor me deem um exemplo: de como "fazer" e "não fazer", dentro dessa regra de única responsabilidade da classe.

 

http://www.objectmentor.com/resources/articles/srp.pdf

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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?

Compartilhar este post


Link para o post
Compartilhar em outros sites

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

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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":
- Seria alterar mais de 2 atributos (valor ou nome do atributo) da classe?
- Seria alterar mais de 2 implementação especifica de algum método ou mais de um método da classe?

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 uma classe de persistência?
- Camada de persistência é a mesma coisa de classe de persistência??

Compartilhar este post


Link para o post
Compartilhar em outros sites

O que seria esse "a classe, não pode ter mais de um motivo para ser modificada":

- Seria alterar mais de 2 atributos (valor ou nome do atributo) da classe?

- Seria alterar mais de 2 implementação especifica de algum método ou mais de um método da classe?

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.

 

- O que seria uma classe de persistência?

- Camada de persistência é a mesma coisa de classe de persistência??

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

- Refatoração S.R.P. - ContactsDisplay;

- Inversão de Controle x Injeção de Dependência.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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??

Compartilhar este post


Link para o post
Compartilhar em outros sites

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

Compartilhar este post


Link para o post
Compartilhar em outros sites

Sim, baseando-se no nosso exemplo. DAO é apenas um de inúmeros padrões existentes, alguns muitos similares, outros extremamente diferentes.

Compartilhar este post


Link para o post
Compartilhar em outros sites

@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';
		}
	}
}

se algo estiver errado corrija-me

Compartilhar este post


Link para o post
Compartilhar em outros sites

@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';
		}
	}
}
se algo estiver errado corrija-me

 

 

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;
    }
}

Quando digo sobre aumentar ou diminuir a quantidade de pratos disponíveis, não queria dizer a respeito do cardápio, me referia exatamente à quantidade de métodos que a classe possui.

 

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

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

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?

Compartilhar este post


Link para o post
Compartilhar em outros sites

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

Compartilhar este post


Link para o post
Compartilhar em outros sites

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.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.