Ao usar o Princípio da Responsabilidade Única, o que constitui uma "responsabilidade"?

187

Parece bastante claro que "Princípio da Responsabilidade Única" não significa "só faz uma coisa". É para isso que servem os métodos.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin diz que "as turmas devem ter apenas um motivo para mudar". Mas é difícil envolver sua mente se você for um programador novo no SOLID.

Eu escrevi uma resposta para outra pergunta , na qual sugeri que as responsabilidades são como cargos e dançavam o assunto usando uma metáfora de restaurante para ilustrar o meu ponto. Mas isso ainda não articula um conjunto de princípios que alguém poderia usar para definir as responsabilidades de suas classes.

Então, como você faz isso? Como você determina quais responsabilidades cada classe deve ter, e como você define uma responsabilidade no contexto do SRP?

    
por Robert Harvey 27.03.2017 / 21:32
fonte

16 respostas

109

Uma maneira de entender isso é imaginar possíveis mudanças de requisitos em projetos futuros e se perguntar o que você precisará fazer para que isso aconteça.

Por exemplo:

New business requirement: Users located in California get a special discount.

Example of "good" change: I need to modify code in a class that computes discounts.

Example of bad changes: I need to modify code in the User class, and that change will have a cascading effect on other classes that use the User class, including classes that have nothing to do with discounts, e.g. enrollment, enumeration, and management.

Ou:

New nonfunctional requirement: We'll start using Oracle instead of SQL Server

Example of good change: Just need to modify a single class in the data access layer that determines how to persist the data in the DTOs.

Bad change: I need to modify all of my business layer classes because they contain SQL Server-specific logic.

A ideia é minimizar a pegada de futuras mudanças potenciais, restringindo modificações de código a uma área de código por área de mudança.

No mínimo, suas aulas devem separar preocupações lógicas de preocupações físicas. Um grande conjunto de exemplos pode ser encontrado no namespace System.IO : lá podemos encontrar vários tipos de fluxos físicos (por exemplo, FileStream , MemoryStream ou NetworkStream ) e vários leitores e gravadores ( BinaryWriter , TextWriter ) que funcionam em um nível lógico. Ao separá-los dessa maneira, evitamos explosão combinatória: em vez de precisar de FileStreamTextWriter , FileStreamBinaryWriter , NetworkStreamTextWriter , NetworkStreamBinaryWriter , MemoryStreamTextWriter e MemoryStreamBinaryWriter , basta conectar o gravador e o fluxo e você pode ter o que você quer. Então, mais tarde, podemos adicionar, digamos, um XmlWriter , sem precisar reimplementá-lo para memória, arquivo e rede separadamente.

    
por 27.03.2017 / 21:51
fonte
73

Em termos práticos, as responsabilidades são limitadas pelas coisas que são prováveis de mudar. Portanto, não há nenhuma maneira científica ou estereotipada de chegar ao que constitui uma responsabilidade, infelizmente. É um julgamento.

É sobre o que, na sua experiência , é provável que mude.

Nós tendemos a aplicar a linguagem do princípio em uma raiva hiperbólica, literal e zelosa. Nós tendemos a dividir as classes porque elas poderiam mudar, ou ao longo de linhas que simplesmente nos ajudam a quebrar os problemas. (A última razão não é inerentemente ruim.) Mas o SRP não existe por si só; está em serviço para criar software de manutenção.

Então, novamente, se as divisões não forem orientadas por alterações prováveis , elas não serão verdade em serviço para o SRP 1 se YAGNI é mais aplicável. Ambos atendem ao mesmo objetivo final. E ambos são questões de julgamento - espero que o julgamento experiente .

Quando o tio Bob escreve sobre isso, ele sugere que pensamos em "responsabilidade" em termos de "quem está pedindo a mudança". Em outras palavras, não queremos que a Parte A perca seus empregos porque a Parte B pediu uma mudança.

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function. (Uncle Bob - The Single Responsibility Principle )

Desenvolvedores bons e experientes terão uma noção de quais mudanças são prováveis. E essa lista mental irá variar um pouco pela indústria e organização.

O que constitui uma responsabilidade na sua aplicação específica, na sua organização em particular, é, em última instância, uma questão de julgamento experiente . É sobre o que é provável que mude. E, de certo modo, é sobre quem possui a lógica interna do módulo.

1. Para ser claro, isso não significa que eles sejam divisões ruins. Eles podem ser divisões excelentes que melhoram dramaticamente a legibilidade do código. Significa apenas que não são dirigidos pelo SRP.

    
por 27.03.2017 / 23:00
fonte
27

Eu sigo "classes devem ter apenas um motivo para mudar".

Para mim, isso significa pensar em esquemas desatenciosos que o proprietário do meu produto pode criar ("Precisamos dar suporte ao celular!", "Precisamos ir para a nuvem!", "Precisamos dar suporte ao chinês!") . Os bons projetos limitarão o impacto desses esquemas em áreas menores e os tornarão relativamente fáceis de realizar. Desenhos ruins significam muitos códigos e mudanças arriscadas.

Experiência é a única coisa que eu encontrei para avaliar corretamente a probabilidade desses esquemas malucos - porque fazer um fácil pode tornar os outros dois mais difíceis - e avaliar a qualidade de um projeto. Programadores experientes podem imaginar o que eles precisam fazer para mudar o código, o que está por aí para mordê-los no rabo, e quais truques facilitam as coisas. Programadores experientes têm um bom pressentimento de como eles são feridos quando o dono do produto pede coisas malucas.

Praticamente, acho que os testes unitários ajudam aqui. Se o seu código for inflexível, será difícil testá-lo. Se você não puder injetar dados ou outros dados de teste, provavelmente não conseguirá injetar o código SupportChinese .

Outra métrica aproximada é o passo do elevador. Os tradicionais lances de elevador são "se você estivesse em um elevador com um investidor, poderia vendê-lo com uma ideia?". Startups precisam ter descrições simples e curtas do que estão fazendo - qual é o foco deles. Da mesma forma, classes (e funções) devem ter uma descrição simples do que fazem . Não "esta classe implementa algum fubar de tal forma que você pode usá-lo nestes cenários específicos". Algo que você pode dizer a outro desenvolvedor: "Esta classe cria usuários". Se você não puder comunicar isso a outros desenvolvedores, você irá para obter bugs.

    
por 27.03.2017 / 22:23
fonte
23

Ninguém sabe. Ou, pelo menos, somos incapazes de concordar com uma definição. É isso que faz com que o SPR (e outros princípios do SOLID) seja bastante controverso.

Eu diria que ser capaz de descobrir o que é ou não é uma responsabilidade é uma das habilidades que o desenvolvedor de software precisa aprender ao longo de sua carreira. Quanto mais código você escrever e revisar, mais experiência você terá para determinar se algo é uma ou várias responsabilidades. Ou se a responsabilidade única for fraturada em partes separadas do código.

Eu diria que o objetivo principal do SRP não é ser uma regra rígida. É para nos lembrar de estar atentos à coesão no código e sempre nos esforçarmos ao máximo para determinar qual código é coeso e o que não é.

    
por 27.03.2017 / 22:24
fonte
5

Eu acho que o termo "responsabilidade" é útil como uma metáfora porque nos permite usar o software para investigar o quão bem o software é organizado. Em particular, eu me concentraria em dois princípios:

  • A responsabilidade é proporcional à autoridade.
  • Nenhuma entidade deve ser responsável pela mesma coisa.

Esses dois princípios nos permitem distribuir responsabilidades de maneira significativa porque eles se desempenham um ao outro. Se você está capacitando um pedaço de código para fazer algo por você, ele precisa ter uma responsabilidade sobre o que faz. Isso faz com que a responsabilidade que uma classe possa ter de crescer, expandindo-se, seja "um motivo para mudar" para escopos mais amplos e mais amplos. No entanto, à medida que você amplia as coisas, você naturalmente começa a se deparar com situações em que várias entidades são responsáveis pela mesma coisa. Isso é repleto de problemas na responsabilidade da vida real, então certamente é um problema na codificação também. Como resultado, esse princípio faz com que os escopos diminuam, à medida que você subdivide a responsabilidade em parcelas não duplicadas.

Além desses dois, um terceiro princípio parece razoável:

  • A responsabilidade pode ser delegada

Considere um programa recém-criado ... um quadro em branco. No início, você só tem uma entidade, que é o programa como um todo. É responsável por ... tudo. Naturalmente, em algum momento, você começará a delegar responsabilidade a funções ou classes. Neste ponto, as duas primeiras regras entram em jogo forçando você a equilibrar essa responsabilidade. O programa de nível superior ainda é responsável pelo resultado geral, assim como um gerente é responsável pela produtividade de sua equipe, mas cada sub-entidade tem responsabilidade delegada e, com isso, a autoridade para assumir essa responsabilidade.

Como um bônus adicional, isso torna o SOLID particularmente compatível com qualquer desenvolvimento de software corporativo que possa ser necessário. Todas as empresas do planeta têm algum conceito de como delegar responsabilidades e nem todas concordam. Se você delegar responsabilidade dentro do seu software de uma maneira que seja remanescente da delegação da sua própria empresa, será muito mais fácil para os futuros desenvolvedores descobrirem como você faz as coisas nessa empresa.

    
por 28.03.2017 / 23:17
fonte
5

Em esta conferência em Yale, Tio Bob apresenta este exemplo engraçado :

EledizqueEmployeetemtrêsrazõesparamudar,trêsfontesderequisitosdemudançaedáaessabem-humoradaeirônica,mas,noentanto,umaexplicaçãoilustrativa:

  • IftheCalcPay()methodhasanerrorandcoststhecompanymillionsofUS$,theCFOwillfireyou.

  • IftheReportHours()methodhasanerrorandcoststhecompanymillionsofUS$,theCOOwillfireyou.

  • IftheWriteEmmployee()methodhasanerrorthatcausestheerasureofalotofdataandcoststhecompanymillionsofUS$,theCTOwillfireyou.

SohavingthreedifferentClevelexecspotentiallyfiringyouforcostlyerrorsinthethesameclassmeanstheclasshastoomanyresponsibilities.

EleforneceessasoluçãoqueresolveaviolaçãodoSRP,masaindaprecisaresolveraviolaçãodoDIPquenãoémostradanovídeo.

    
por 29.03.2017 / 01:47
fonte
3

Acho que uma maneira melhor de subdividir as coisas do que as "razões para mudar" é começar pensando em se faria sentido exigir que o código que precisa executar duas (ou mais) ações precise manter um referência de objeto separado para cada ação, e se seria útil ter um objeto público que poderia fazer uma ação, mas não a outra.

Se as respostas para ambas as perguntas forem sim, isso sugeriria que as ações devem ser feitas por classes separadas. Se as respostas a ambas as perguntas forem não, isso sugeriria que, do ponto de vista público, deveria haver uma classe; se o código para isso for complicado, ele pode ser subdividido internamente em classes privadas. Se a resposta à primeira questão for não, mas a segunda for sim, deve haver uma classe separada para cada ação mais uma classe composta que inclua referências a instâncias das outras.

Se alguém tiver classes separadas para o teclado do caixa, bipe, leitura numérica, impressora de recibos e gaveta de dinheiro, e nenhuma classe composta para uma caixa registradora completa, então o código que deve processar uma transação pode acabar ficando acidentalmente Chamado de uma maneira que recebe entrada do teclado de uma máquina, produz ruído do bipe de uma segunda máquina, mostra números no visor de uma terceira máquina, imprime um recibo na impressora de uma quarta máquina e abre a gaveta de dinheiro de uma quinta máquina. Cada uma dessas subfunções pode ser manipulada por uma classe separada, mas também deve haver uma classe composta que as une. A classe composta deve delegar o máximo de lógica possível às classes constituintes, mas deve, quando possível, envolver funções de seus componentes constituintes, em vez de exigir que o código do cliente acesse diretamente os componentes.

Pode-se dizer que a "responsabilidade" de cada classe é incorporar alguma lógica real ou fornecer um ponto de conexão comum para várias outras classes que o fazem, mas o importante é focar primeiro e principalmente em como o código do cliente deve exibir uma aula. Se fizer sentido para o código do cliente ver algo como um único objeto, o código do cliente deverá vê-lo como um único objeto.

    
por 28.03.2017 / 01:01
fonte
3

O SRP é difícil de acertar. É principalmente uma questão de atribuir 'tarefas' ao seu código e garantir que cada parte tenha responsabilidades claras. Como na vida real, em alguns casos, dividir o trabalho entre as pessoas pode ser bastante natural, mas em outros casos pode ser realmente complicado, especialmente se você não os conhece (ou o trabalho).

Eu sempre recomendo que você apenas escreva um código simples que funcione primeiro e, em seguida, refatore um pouco: Você tenderá a ver como o código se agrupa naturalmente depois de um tempo. Acho que é um erro forçar as responsabilidades antes que você conheça o código (ou pessoas) e o trabalho a ser feito.

Uma coisa que você notará é quando o módulo começa a fazer muito e é difícil de depurar / manter. Este é o momento de refatorar; qual deve ser o trabalho principal e quais tarefas podem ser atribuídas a outro módulo? Por exemplo, ele deve lidar com as verificações de segurança e o outro trabalho, ou você deve fazer verificações de segurança em outros lugares primeiro, ou isso tornará o código mais complexo?

Use muitas indiretas e isso se torna uma bagunça de novo ... como para outros princípios, este estará em conflito com outros, como KISS, YAGNI, etc. Tudo é uma questão de equilíbrio.

    
por 28.03.2017 / 10:35
fonte
3

"Princípio da responsabilidade única" talvez seja um nome confuso. "Apenas uma razão para mudar" é uma descrição melhor do princípio, mas ainda é fácil entender mal. Nós não estamos falando sobre o que faz com que objetos mudem de estado em tempo de execução. Estamos estudando o que poderia levar os desenvolvedores a alterar o código no futuro.

A menos que estejamos corrigindo um bug, a mudança será devido a um requisito comercial novo ou alterado. Você terá que pensar fora do próprio código e imaginar quais fatores externos podem fazer com que os requisitos mudem independentemente . Diga:

  • As taxas de imposto mudam devido a uma decisão política.
  • O marketing decide alterar nomes de todos os produtos
  • a interface do usuário precisa ser reprojetada para ser acessível
  • O banco de dados está congestionado, então você precisa fazer algumas otimizações
  • Você precisa acomodar um aplicativo para dispositivos móveis
  • e assim por diante ...

Idealmente, você quer que fatores independentes afetem diferentes classes. Por exemplo. Como as taxas de imposto são alteradas independentemente dos nomes dos produtos, as alterações não devem afetar as mesmas classes. Caso contrário, você corre o risco de uma introdução à mudança de imposto, um erro na nomenclatura do produto, que é o tipo de acoplamento firme que você deseja evitar com um sistema modular.

Portanto, não se concentre apenas no que poderia mudar - qualquer coisa poderia mudar no futuro. Concentre-se no que pode mudar . As alterações são geralmente independentes se forem causadas por diferentes atores.

O seu exemplo com títulos de emprego está no caminho certo, mas você deve levá-lo mais literalmente! Se o marketing pode causar alterações no código e finanças pode causar outras alterações, essas alterações não devem afetar o mesmo código, uma vez que estes são literalmente títulos de cargos diferentes e, portanto, as mudanças acontecerão de forma independente.

Para citar o tio Bob que inventou o termo:

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function.

Então, para resumir: uma "responsabilidade" é atender a uma única função de negócios. Se mais de um ator puder fazer com que você tenha que mudar de classe, a classe provavelmente quebra esse princípio.

    
por 28.03.2017 / 17:04
fonte
2

Um bom artigo que explica os princípios de programação SOLID e dá exemplos de código que seguem e não seguem esses princípios é link .

No exemplo relacionado ao SRP, ele dá um exemplo de algumas classes de forma (círculo e quadrado) e uma classe projetada para calcular a área total de várias formas.

Em seu primeiro exemplo, ele cria a classe calculadora de área e retorna sua saída como HTML. Mais tarde, ele decide que deseja exibi-lo como JSON e precisa alterar sua classe de cálculo de área.

O problema com este exemplo é que sua classe de cálculo de área é responsável por calcular a área de formas E exibir essa área. Ele então passa por uma maneira melhor de fazer isso usando outra classe projetada especificamente para exibir áreas.

Este é um exemplo simples (e mais fácil de entender lendo o artigo, pois tem trechos de código), mas demonstra a ideia central do SRP.

    
por 27.03.2017 / 22:21
fonte
0

Primeiro, o que você tem é, na verdade, dois problemas separados : o problema de quais métodos colocar em suas classes e o problema de inchaço na interface.

Interfaces

Você tem essa interface:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Presumível, você tem várias classes que estão em conformidade com a interface CustomerCRUD (caso contrário, uma interface é desnecessária) e alguma função do_crud(customer: CustomerCRUD) que recebe um objeto em conformidade. Mas você já quebrou o SRP: vinculou essas quatro operações distintas.

Digamos que, mais tarde, você possa operar em exibições de banco de dados. Uma visualização de banco de dados tem somente o método Read disponível para ele. Mas você quer escrever uma função do_query_stuff(customer: ???) que os operadores transparentemente em tabelas ou visualizações completas; só usa o método Read , afinal.

Portanto, crie uma interface

Interface pública CustomerReader   {     Leitura pública do cliente (customerID: int)   }

e fator sua interface CustomerCrud como:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Mas não há fim à vista. Pode haver objetos que podemos criar, mas não atualizar, etc. Esse buraco de coelho é muito profundo. A única maneira sensata de aderir ao princípio da responsabilidade única é fazer com que todas as suas interfaces contenham exatamente um método . Vai realmente segue esta metodologia do que eu vi, com a grande maioria das interfaces contendo uma única função; Se você quiser especificar uma interface que contenha duas funções, crie uma nova interface que combina as duas. Você logo terá uma explosão combinatória de interfaces.

A saída dessa bagunça é usar a subtipagem estrutural (implementada, por exemplo, no OCaml) em vez de interfaces (que são uma forma de subtipagem nominal). Nós não definimos interfaces; em vez disso, podemos simplesmente escrever uma função

let do_customer_stuff customer = customer.read ... customer.update ...

que chama os métodos que quisermos. O OCaml usará inferência de tipos para determinar se podemos transmitir qualquer objeto que implemente esses métodos. Neste exemplo, seria determinado que customer tem tipo <read: int -> unit, update: int -> unit, ...> .

Classes

Isso resolve a confusão interface ; mas ainda temos que implementar classes que contenham vários métodos. Por exemplo, devemos criar duas classes diferentes, CustomerReader e CustomerWriter ? E se quisermos alterar a forma como as tabelas são lidas (por exemplo, agora armazenamos em cache nossas respostas em redis antes depois de buscar os dados), mas agora como elas são escritas? Se você seguir esta cadeia de raciocínio até sua conclusão lógica, você será levado inextricavelmente à programação funcional:)

    
por 28.03.2017 / 00:14
fonte
0

Na minha opinião, a coisa mais próxima de um SRP que me vem à mente é um fluxo de uso. Se você não tem um fluxo de uso claro para qualquer aula, é provável que sua classe tenha um cheiro de design.

Um fluxo de uso seria uma determinada sucessão de chamada de método que forneceria um resultado esperado (assim testável). Você basicamente define uma classe com os casos de uso que tem IMHO, é por isso que toda a metodologia do programa se concentra nas interfaces sobre a implementação.

    
por 28.03.2017 / 23:43
fonte
0

É para conseguir que múltiplas mudanças de requisitos, não requeiram que seu componente mude .

Mas boa sorte entendendo que, à primeira vista, quando você ouve falar do SOLID.

Vejo muitos comentários dizendo que SRP e YAGNI podem contradizer um ao outro, mas YAGN eu apliquei por TDD (GOOS, London School) me ensinou a pensar e projetar meus componentes a partir da perspectiva de um cliente. Eu comecei a projetar minhas interfaces por qual é o mínimo que qualquer cliente gostaria que ele fizesse, isso é o quão pouco deveria ser . E esse exercício pode ser feito sem qualquer conhecimento de TDD.

Eu gosto da técnica descrita pelo Tio Bob (não me lembro de onde, infelizmente), algo como:

Ask yourself, what does this class do?

Did your answer contain either of And or Or

If so, extract that part of the answer, it a responsibility of its own

Esta técnica é absoluta e, como disse o @svidgen, o SRP é um julgamento, mas quando aprendemos algo novo, os absolutos são os melhores, é mais fácil sempre faça algo. Certifique-se de que a razão pela qual você não se separa é; uma estimativa educada e não porque você não sabe como. Esta é a arte e é preciso experiência.

Acho que muitas das respostas parecem ser um argumento para desvincular quando se fala em SRP .

SRP é não para garantir que uma alteração não se propague no gráfico de dependência.

Teoricamente, sem SRP , você não teria dependências ...

Uma alteração não deve causar uma alteração em muitos lugares do aplicativo, mas temos outros princípios para isso. No entanto, o SRP melhora o Princípio Fechado Aberto . Este princípio é mais sobre abstração, no entanto, abstrações menores são mais fáceis de reimplementar .

Portanto, ao ensinar o SOLID como um todo, tenha o cuidado de ensinar que o SRP permite alterar menos código quando os requisitos mudam, quando, na verdade, ele permite que você escreva menos novo código.

    
por 29.03.2017 / 16:29
fonte
0

Não há uma resposta clara para isso. Embora a questão seja restrita, as explicações não são.

Para mim, é algo como a Navalha de Occam, se você quiser. É um ideal onde tento medir o meu código atual. É difícil dizê-lo em palavras simples e simples. Outra metáfora seria "um tópico" que é tão abstrato, isto é, difícil de entender, quanto "responsabilidade única". Uma terceira descrição seria "lidar com um nível de abstração".

O que isso significa praticamente?

Ultimamente eu uso um estilo de codificação que consiste principalmente em duas fases:

A fase I é melhor descrita como um caos criativo. Nesta fase eu escrevo o código enquanto os pensamentos estão fluindo - isto é, cru e feio.

A fase II é o oposto completo. É como limpar depois de um furacão. Isso leva mais trabalho e disciplina. E então eu olho para o código da perspectiva de um designer.

Eu estou trabalhando principalmente em Python agora, o que me permite pensar em objetos e classes mais tarde. Primeira Fase I - escrevo apenas funções e distribuo-as quase aleatoriamente em diferentes módulos. Na Fase II , depois que comecei as coisas, observei mais de perto qual módulo lida com qual parte da solução. E enquanto percorremos os módulos, os tópicos são emergentes para mim. Algumas funções estão relacionadas tematicamente. Estes são bons candidatos para classes . E depois eu transformei funções em classes - o que é quase feito com recuo e adicionando self à lista de parâmetros em python;) - Eu uso SRP como o Occam's Razor para remover funcionalidades de outros módulos e classes.

Um exemplo atual pode estar escrevendo pequenas funcionalidades de exportação no outro dia.

Havia a necessidade de csv , excel e combinados folhas de excel em um zip.

A funcionalidade simples foi feita em três vistas (= funções). Cada função usava um método comum para determinar filtros e um segundo método para recuperar os dados. Em seguida, em cada função, a preparação da exportação ocorreu e foi entregue como uma Resposta do servidor.

Havia muitos níveis de abstração misturados:

I) lidando com solicitação / resposta de entrada / saída

II) determinando filtros

III) recuperando dados

IV) transformação de dados

O passo fácil foi usar uma abstração ( exporter ) para lidar com as camadas II-IV em um primeiro passo.

O único remanescente foi o tópico lidando com solicitações / respostas . No mesmo nível de abstração está extraindo parâmetros de requisição , o que é bom. Então eu tive para isso ver uma "responsabilidade".

Em segundo lugar, tive que dividir o exportador, que, como vimos, consistia em pelo menos três outras camadas de abstração.

Determinar os critérios de filtro e retrival estão quase no mesmo nível de abstração (os filtros são necessários para obter o subconjunto correto dos dados). Esses níveis foram colocados em algo como uma camada de acesso a dados .

Na etapa seguinte, dividi os mecanismos de exportação propriamente ditos: onde era necessário escrever para um arquivo temporal, eu dividi isso em duas "responsabilidades": uma para a gravação real dos dados no disco e outra parte que lidava com a formato real.

Ao longo da formação das aulas e módulos, as coisas ficaram mais claras, o que pertencia aonde. E sempre a questão latente, se a classe faz muito .

How do you determine which responsibilities each class should have, and how do you define a responsibility in the context of SRP?

É difícil dar uma receita para seguir. É claro que eu poderia repetir o enigmático "nível de abstração" - regra se isso ajuda.

Principalmente para mim, é uma espécie de "intuição artística" que leva ao design atual; Eu modelo código como um artista pode esculpir argila ou pintar.

Imagine-me como um Coding Bob Ross ;)

    
por 30.03.2017 / 21:50
fonte
0

O que eu tento fazer para escrever código que segue o SRP:

  • Escolha um problema específico que você precisa resolver;
  • Escreva o código que resolve, escreva tudo em um método (por exemplo: main);
  • Analise cuidadosamente o código e, com base no negócio, tente definir as responsabilidades visíveis em todas as operações que estão sendo executadas (essa é a parte subjetiva que também depende do negócio / projeto / cliente);
  • Por favor, note que toda a funcionalidade já está implementada; o que vem a seguir é apenas a organização do código (nenhum recurso ou mecanismo adicional será implementado a partir de agora nesta abordagem);
  • Com base nas responsabilidades que você definiu nas etapas anteriores (que são definidas com base no negócio e na ideia de "uma razão para mudar"), extraia uma classe ou um método separado para cada uma delas;
  • Por favor, note que esta abordagem só se preocupa com o SPR; o ideal é que haja etapas adicionais aqui tentando aderir aos outros princípios também.

Exemplo:

Problema: obtenha dois números do usuário, calcule sua soma e envie o resultado para o usuário:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Em seguida, tente definir responsabilidades com base nas tarefas que precisam ser executadas. A partir disso, extraia as classes apropriadas:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Em seguida, o programa refatorado se torna:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Nota: este exemplo muito simples leva em consideração apenas o princípio SRP. O uso de outros princípios (por exemplo: o código "L" deve depender de abstrações e não de concreções) proporcionaria mais benefícios ao código e o tornaria mais sustentável para mudanças de negócios.

    
por 03.10.2017 / 21:07
fonte
0

De Robert C. Martins livro Arquitetura Limpa: Um Guia do Artesão para a Estrutura do Software e Design , publicado em 10 de setembro de 2017, Robert escreve na página 62 o seguinte:

Historically, the SRP has been described this way:

A module should have one, and only one, reason to change

Software systems are changed to satisfy users and stakeholders; those users and stakeholders are the "reason to change". that the principle is talking about. Indeed, we can rephrase the principle to say this:

A module should be responsible to one, and only one, user or stakeholder

Unfortunately, the word "user" and "stakeholder" aren't really the right word to use here. There will likely be more than one user or stakeholder who wants the system changed in the sane way. Instead we're really referring to a group - one or more people who require that change. We'll refer to that group as an actor.

Thus the final version of the SRP is:

A module should be responsible to one, and only one, actor.

Então, isso não é sobre código. O SRP trata de controlar o fluxo de requisitos e necessidades de negócios, que só podem vir de uma só pessoa.

    
por 23.07.2018 / 22:10
fonte