Clean OOP-Design: Como implementar responsabilidade única e sem programação procedural

5

No momento, estou tentando refatorar uma parte do código C # que é um pouco processualmente escrito. Eu quero tornar o design limpo, orientado a objetos e usando classes com responsabilidades únicas.

O código contém uma classe "Solução", que representa uma solução do Visual Studio e contém apenas dados. Esta classe é usada por outras classes, que têm a responsabilidade de construir a solução (classe SolutionBuilder, interface ISolutionBuilder), atualizar os pacotes NuGet da solução (classe NuGetUpdater, interface INuGetUpdater) e submeter as alterações ao Subversion (classe SubversionClient, interface ISubversionClient) .

Eu acredito que a implementação atual tem as seguintes falhas de design:

  • A classe "Solução" apenas contém dados, não é uma classe OOP real com comportamento
  • As outras classes também não são classes OOP, porque elas possuem apenas um método e são bastante processuais
  • Cheiro de código: a classe SolutionBuilder e NuGetUpdater terminam com "er"

Código de concreto:

public class Solution
{
    public string Name { get; set; }
    public string SolutionFileName { get; set; }
}

public interface ISolutionBuilder
{
    void Build(Solution solution);
}

public interface INuGetUpdater
{
    void Update(Solution solution, string packageSourceUrl, bool safe = true, bool prerelease = false);
}

...

Concordo que está tudo bem se uma classe tiver apenas um método, mas recentemente eu li muitos blogs dizendo que isso é uma OOP ruim e uma codificação procedural. Então, minha primeira ideia foi mover todos esses métodos para a classe de solução, mas depois pensei que isso certamente violaria o princípio de responsabilidade única e também tornaria a classe Solution bastante grande.

Links para as postagens do blog que eu li:
Suas convenções de codificação estão te machucando
Não crie objetos que terminem com 'er'

Eu entendi mal os autores desses posts? Alguém tem uma idéia melhor de como estruturar esse código de uma maneira orientada a objeto? Ou este é um caso em que esta é a melhor maneira de fazer isso?

    
por Ashwani Mehlem 30.06.2016 / 12:29
fonte

1 resposta

7

I agree that it is totally okay if a class has only one method, but recently I have read many blogs saying this is bad OOP and rather procedural coding.

Se uma turma tiver apenas um método , geralmente é Execute() ou algo equivalente. A pergunta que você deve se fazer é: o que você está encapsulando usando uma classe, se você tiver apenas um método? É por isso que esses blogs dizem que você pode querer repensá-lo.

So my first idea really was to move all those methods into the solution class, but then I thought this would certainly violate the single responsibility principle and also make the class Solution become quite large

Uma "responsabilidade" é simplesmente uma "área de especialização". As turmas se especializam em algo. Elas podem fazer mais do que uma coisa com essa coisa. Cabe a você descobrir onde está esse equilíbrio. Bob Martin diz que uma responsabilidade é uma "razão para mudar" e que as classes devem ter apenas um motivo para mudar, em vez de uma responsabilidade.

Did I misunderstand the authors of these blog posts?

Sim, você fez.

O ponto de Suas Convenções de Codificação Estão Ferindo Você é que estamos nos afogando em princípios arquitetônicos, e muito disso é desnecessário.

Claro, você pode jogar em torno de objetos GOF e SOLID e FactoryFactoryFactory durante todo o dia. Seu castelo de cartas será lindo, mas quanto disso realmente funciona?

Ou você pode escrever um código e fazer algo.

Eu vi isso em primeira mão, em mais de uma ocasião. Exércitos de desenvolvedores fazendo tudo certo, seguindo todos os princípios arquiteturais mais recentes, mas escrevendo softwares que são totalmente inflexíveis, difíceis de raciocinar e exigindo resmas de código para evoluir.

Enquanto isso, uma pequena equipe ágil está escrevendo uma nova interface do usuário para um sistema usando uma estrutura de ponta que, embora provavelmente, quebre todos os tipos de regras arquitetônicas, permite flexibilidade no design, alavanca uma tonelada de trabalho feito por outras pessoas, simplifica drasticamente a codificação e reduz o tempo de lançamento no mercado. Ele pode fazer todas essas coisas porque é pragmático, não dogmático.

Steve Yegge ilustra isso melhor do que eu em seu "Reino de Substantivos "artigo :

For the lack of a nail,
    throw new HorseshoeNailNotFoundException("no nails!");

For the lack of a horseshoe,
    EquestrianDoctor.getLocalInstance().getHorseDispatcher().shoot();

For the lack of a horse,
    RidersGuild.getRiderNotificationSubscriberList().getBroadcaster().run(
      new BroadcastMessage(StableFactory.getNullHorseInstance()));

For the lack of a rider,
    MessageDeliverySubsystem.getLogger().logDeliveryFailure(
      MessageFactory.getAbstractMessageInstance(
        new MessageMedium(MessageType.VERBAL),
        new MessageTransport(MessageTransportType.MOUNTED_RIDER),
        new MessageSessionDestination(BattleManager.getRoutingInfo(
                                        BattleLocation.NEAREST))),
      MessageFailureReasonCode.UNKNOWN_RIDER_FAILURE);

For the lack of a message,
    ((BattleNotificationSender)
      BattleResourceMediator.getMediatorInstance().getResource(
        BattleParticipant.PROXY_PARTICIPANT,
        BattleResource.BATTLE_NOTIFICATION_SENDER)).sendNotification(
          ((BattleNotificationBuilder)
            (BattleResourceMediator.getMediatorInstance().getResource(
            BattleOrganizer.getBattleParticipant(Battle.Participant.GOOD_GUYS),
            BattleResource.BATTLE_NOTIFICATION_BUILDER))).buildNotification(
              BattleOrganizer.getBattleState(BattleResult.BATTLE_LOST),
              BattleManager.getChainOfCommand().getCommandChainNotifier()));

For the lack of a battle,
    try {
        synchronized(BattleInformationRouterLock.getLockInstance()) {
          BattleInformationRouterLock.getLockInstance().wait();
        }
    } catch (InterruptedException ix) {
      if (BattleSessionManager.getBattleStatus(
           BattleResource.getLocalizedBattleResource(Locale.getDefault()),
           BattleContext.createContext(
             Kingdom.getMasterBattleCoordinatorInstance(
               new TweedleBeetlePuddlePaddleBattle()).populate(
                 RegionManager.getArmpitProvince(Armpit.LEFTMOST)))) ==
          BattleStatus.LOST) {
        if (LOGGER.isLoggable(Level.TOTALLY_SCREWED)) {
          LOGGER.logScrewage(BattleLogger.createBattleLogMessage(
            BattleStatusFormatter.format(BattleStatus.LOST_WAR,
                                         Locale.getDefault())));
        }
      }
    }

For the lack of a war,
    new ServiceExecutionJoinPoint(
      DistributedQueryAnalyzer.forwardQueryResult(
        NotificationSchemaManager.getAbstractSchemaMapper(
          new PublishSubscribeNotificationSchema()).getSchemaProxy().
            executePublishSubscribeQueryPlan(
              NotificationSchema.ALERT,
              new NotificationSchemaPriority(SchemaPriority.MAX_PRIORITY),
              new PublisherMessage(MessageFactory.getAbstractMessage(
                MessageType.WRITTEN,
                new MessageTransport(MessageTransportType.WOUNDED_SURVIVOR),
                new MessageSessionDestination(
                  DestinationManager.getNullDestinationForQueryPlan()))),
              DistributedWarMachine.getPartyRoleManager().getRegisteredParties(
                PartyRoleManager.PARTY_KING ||
                PartyRoleManager.PARTY_GENERAL ||
                PartyRoleManager.PARTY_AMBASSADOR)).getQueryResult(),
        PriorityMessageDispatcher.getPriorityDispatchInstance())).
      waitForService();

All for the lack of a horseshoe nail.

Se sua cabeça dói olhando para isso, bem ... também dói o meu.

O ponto de Não crie objetos que acabem com 'er' é simplesmente que, se sua classe terminar em er , talvez seja melhor colocar essa funcionalidade no objeto real que sua classe auxiliar está ajudando. Isso é tudo.

Uma nota final: Não existe um caminho certo ou errado. Só existe a maneira que melhor atende aos seus requisitos de software e necessidades comerciais. Se toda essa cerimônia funciona porque você é uma loja Java, o código legado é todo o Reino dos Substantivos e todo mundo entende, então, por todos os meios, continue nesse caminho.

Não é o único caminho, no entanto.

    
por 30.06.2016 / 18:55
fonte