Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. PHP
Code

Princípios SOLID Parte 1 - O Princípio da Responsabilidade Única

by
Difficulty:BeginnerLength:MediumLanguages:
This post is part of a series called The SOLID Principles.
SOLID: Part 2 - The Open/Closed Principle

Portuguese (Português) translation by Erick Patrick (you can also view the original English article)

Responsabilidade Única (SRP - Single Responsability); Fechado para modificações aberto para expansão (Open/Close); Substituição de Liskov (Liskov's Substitution); Segregação de Interface (Interface Segregation); e Inversão de Dependência (Dependency Inversion). Cinco princípios ágeis que deveriam guiar você todas as vezes que fosse programar.

A Definição

Uma classe só deveria ter um único motivo para mudar.

Definido por Robert C. Martin em seu livro Agile Software Development, Principles, Patterns, and Practices e, depois, republicado na versão C# do mesmo livro, Agile Principles, Patterns, and Practices in C#, é um dos princípios ágeis de programação, conhecidos como princípios SOLID. O que ele afirma é bem simples, porém, alcançar essa simplicidade pode ser bem problemático. Uma classe só deveria ter um único motivo para mudar.

Mas, por que? Por que é tão importante ter apenas um único motivo para mudar?

Em linguagens compiladas e fortemente tipadas, há muitos motivos que podem levar a muitas reimplantações indesejadas. Se há dois motivos diferentes para uma mudança, é aceitável que duas equipes diferentes possam trabalhar no mesmo código por motivos diferentes. Cada uma terá de implantar sua solução, o que, no caso de linguagens compiladas (como C++, C# ou Java), pode causar imcompatibilidade de módulos com módulos criados por outras equipes ou de outras partes dessa mesma aplicação.

Mesmo que você não use uma linguagem compilada, talvez tenha de retestar uma mesma classe ou módulo por diferentes motivos. Isso implica em mais tempo, esforço, recursos e trabalho.

A Audiêcia

Determinar a única responsabilidade de uma classe ou módulo, é muito mais complexo que, simplesmente, verificar em uma lista pré-determianda. Por exemplo, uma dica para encontrarmos as razões para mudanças é verificar o audiência da nossa classe. Os usuários da aplicação ou sistema que desenvolvemos é que requisitarão mudanças para ela. Aqueles que usam que pedirão mudanças. Eis alguns módulos e suas possíveis audiências.

  • Módulo de Persistência - Pode incluir os DBAs e arquitetos de software;
  • Módulo de Relatório - Pode incluir os contadores, secretários e administradores;
  • Módulo de Computação de Pagamento para um Sistema de Pagamento - Pode incluir os advogados, administradores e contadores;
  • Módulo de Busca de Livros para um Sistema de Administração de Biblioteca - Pode incluir os bibliotecários e/ou os próprios clientes.

Atores e Papéis

Associar pessoas reais a todas os papéis existentes pode ser difícil. Em uma pequena empresa, uma única pessoa pode ter de satisfazer vários papéis, enquanto em uma empresa maior, pode ter várias pessoas alocadas para um mesmo papel. Assim, faz bastante sentido pensar sobre esses papéis. Mas os papéis, em si, são um pouco difíceis de definir. O que é um papel? Como os encontramos? É muito mais fácil pensar em pessoas realizando determinados papéis e associa-los à nossa audiência.

Assim, se nossa audiência define os motivos para as mudanças, os atores definem a audiência. Isso nos ajuda a reduzir os conceitos de pessoas como "John o arquiteto" em Arquitetura, ou "Maria a administradora" em Administração.

Então, uma responsabilidade é um conjunto de funções que serve a um ator em particular. (Robert C. Martin)

Fonte da Mudança

Seguindo esse raciocínio, os atores tornam-se a fonte da mudança para a família de funções que os servem. De acordo com que suas necessidades mudam, aquela família de funções também deve mudar para adaptar-se às suas necessidades.

O ator de uma responsabilidade é a única fonte de mudança para aquela responsabilidade. (Robert C. Martin)

Exemplos Clássicos

Objetos que Podem "Imprimir" a Si Mesmos

Digamos que temos uma classe Book encapsulando o conceito de um livro e suas funcionalidades.

Essa classe parece bem razoável. Nós temos um livro, ele tem o seu título, autor e também pode passar as páginas. Finalmente, ele também é capaz de imprimir o conteúdo da página atual na tela. Mas, há um pequeno problema. Se pensarmos um pouco nos atores envolvidos na operação de um objeto do tipo Book, quem eles seriam? Nós podemos pensar, facilmente, em dois atores diferentes: Administração de Livros (como o bibliotecário) e o Mecanismo de Apresentação de Dados (como o método que desejamos enviar o conteúdo para o usuário - seja através da tela, de uma interface gráfica, interface textual ou, talvez, impressão). Eles são dois atores bem diferentes.

Misturar lógica de negócios com apresentação é ruim porque vai de encontro o O Princío da Responsabilidade Única. Veja o código a seguir:

Mesmo esse exemplo básico nos mostra como separar a apresentação da lógica de negócios e, respeitar a SRP, nos trás uma grande vantagem: flexibilidade no projeto.

Objetos que Podem "Salvar" a Si Próprios

Um exemplo parecido com o anterior é quando um objeto pode salvar e retornar a si próprio para a camada de apresentação.

Nós podemos, novamente, identificar vários atores, como o Sistema de Administração de Livros e a Persitência de Dados. Toda vez que precisamos mudar a persistência, precisaremos mudar essa classe. Toda vez que precisamos mudar de página, precisaremos mudar essa classe. Há vários focos que permitem mudança.

Movendo a operação de persistência para outra classe, irá, claramente, separar as responsabilidades e estaremos livres para alterar os métodos de persistência sem afetar o código da nossa classe Book. Por exemplo, implementar uma classe DatabasePersistence seria bem simples e a nossa lógica de negócios, criada em relação às operações com os livros, não mudaria.

Uma Visão de Alto Nível

Em meus artigos anteriores, mencionei e apresentei, mais de uma vez, o esquema arquitetural de alto nível que pode ser visto abaixo.

HighLevelDesign

Se analizarmos esse esquema, você pode ver como o Princípio da Responsabilidade Única é respeitado. A criação de objetos está separada na direta, em Fábricas (Factories) e no ponto de entrada principal (Main) de nossa aplicação, um ator uma responsabilidade. A persistência também é levada em conta, na parte de baixo. Um módulo separado para um responsabilidade diferente. Finalmente, na esqueda, nós temos a apresentação ou o mecanismo de entrega, se assim preferir, no padrão MVC ou qualquer outro tipo de interface de usuário. A SRP foi respeitada novamente. Só nos resta descobrir o que fazer na nossa lógica de negócio.

Considerações de Projeto de Software

Quando pensamos sobre o aplicativo que precisamos codificar, podemos analisar diversos aspectos diferentes. Por exemplo, vários requerimentos que afetam uma mesma classe podem ser um foco de mudança. Esses focos de mudanças são dicas para a responsabilidade única. Há uma alta probabilidade que grupos de requerimentos que estão afetando o mesmo grupo de funções serão responsáveis por outras mudanças ou que precisarão ser separados em um grupo próprio.

O valor primário de um aplicativo é a facilidade de alteração, o secundário é a funcionalidade, no sentido de satisfazer tantos requerimentos quanto possível, atendendo as necessidades dos usuários. Entretanto, para alcançar um valor secundário bastante positivo, o valor primário é obrigatório. Para mantermos nosso valor primário alto, devemos ter um projeto que seja fácil de alterar, estender e adicionar novas funcionalidades, além de garantir que a SRP seja respeitada.

Nós podemos pensar em um passo-a-passo:

  1. Alto valor primário leva a um alto valor secundário;
  2. O valor secundário equivale às necessidades dos usuários;
  3. Necessidades dos usuários equivale às necessidades dos atores;
  4. Necessidades dos atores determina as necessidades de mudança desses atores;
  5. Necessidades de mudanças dos atores definem nossas responsabilidades.

Então, quando formos projetar nossos aplicativos, deveríamos:

  1. Buscar e definir atores;
  2. Identificar a responsabilidade própria a esses atores;
  3. Agrupar nossas funções em classes, para que cada uma tenha uma única responsabilidade.

Um Exemplo Menos Óbvio

Isso parecer estar certo. Não temos metodo algum lidando com persistência ou apresentação. Nós temos nossa funcionalidade turnPage() e alguns outros métodos para prover informações diferentes sobre o livro. Porém, nós podemos ter um problema. Para descobrirmos, precisamos analisar nossa aplicação. A função getLocation() pode ser esse problema.

Todos os métodos da classe Book estão relacionados à logica do negócio. Dessa forma, nossa perspectiva deve vir do ponto de vista do negócio. Se nossa aplicação for criada para ser usada por bibliotecários de verdade, que buscarão pelos livros físicos que pedirmos, então, a SRP pode ser violada.

Podemos pensar que o bibliotecário é aquele interessado nos métodos getTitle(), getAuthor() e getLocation(). Talvez o cliente também tenha acesso à aplicação para selecionar um livro e poder ler as primeiras páginas dele para ter uma ideia melhor sobre o mesmo, e decidir se quer levá-lo ou não. Assim, os atores leitores podem estar interessados em todos os métodos, exceto o getLocations(). Um usuário comum não precisa saber onde o livro é mantido na biblioteca. O livro será entregue ao usuário pelo bibliotecário. Então, precisamos, sim, violar a SRP.

Criando a classe BookLocator, o bibliotecário ficará interessado na BookLocator. O cliente estará interessado, apenas, na classe Book. Claro, há inúmeras maneiras de implementar uma classe BookLocator. Ela pode usar o autor e título do livro, ou um objeto livro e obter as informações necessárias que a classe Book provê. Só depende do nosso negócio. O que é importante é que, se a biblioteca mudar e o bibliotecário tiver de encontrar os livros em uma biblioteca organizada de forma totalmente diferente, o objeto Book não será afetado. Da mesma forma, se decidirmos pre-compilar um resumo para os leitores, ao invés de permitir que eles leiam as primeiras páginas do livro, isso não afetará o bibliotecário nem o processo de encontrar as prateleiras nas quais os livros estão.

Porém, se nosso negócio tiver de eliminar o bibliotecário para criar um mecanismo onde o próprio cliente procura o livro na biblioteca, então, podemos voltar a considerar a forma como fizemos primeiro, onde a SRP é respeitada. Os leitores, agora, também são nossos bibliotecários, eles precisam saber onde os livros ficam, para busca-los e locá-los no sistema automatizado. O que devemos sempre lembrar é que devemos pensar com cuidado em nosso negócio antes de tomar qualquer decisão.

Pensamentos Finais

O Princípio da Responsabilidade Única deve sempre ser considerado quando for codificar seus aplicativos. Projetos de classes e módulos são bastante afetados pela SRP, o que leva a um projeto com baixo acoplamento, com dependências menores e em menor quantidade. Mas, como qualquer moeda, ela tem duas caras. É tentador codificarmos nossa aplicação, desde o começo, com a SRP em mente. Também é bastante tentador idenficar tantos atores quanto quisermos ou precisamos - para não esquecermos de qualquer grupo de usuário desde o começo. Aplicar a SRP ao pé da letra pode levar a uma otimização prematura ao invés de um projeto melhor, gerando um projeto difuso, onde as responsabilidade das classes e módulos são difíceis de identificar ou entender.

Assim, toda vez que perceber que um módulo ou classe começaram a mudar por inúmeras razões diferentes, não hesite, tome os passos necessários para respeitar a SRP, entretanto, não se deixe levar por ela, uma vez que otimizações prematuras podem enganar você.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.