Clean OOP-Design: come implementare una singola responsabilità e nessuna programmazione procedurale

5

Attualmente sto cercando di refactoring un pezzo di codice C # che è in qualche modo scritto proceduralmente. Voglio rendere il design pulito, orientato agli oggetti e utilizzare le classi con responsabilità singole.

Il codice contiene una classe "Soluzione", che rappresenta una soluzione di Visual Studio e contiene solo dati. Questa classe viene utilizzata da altre classi, che hanno la responsabilità di creare la soluzione (classe SolutionBuilder, interfaccia ISolutionBuilder), aggiornare i pacchetti NuGet della soluzione (classe NuGetUpdater, interfaccia INuGetUpdater) e confermare le modifiche a Subversion (classe SubversionClient, interface ISubversionClient) .

Credo che l'attuale implementazione abbia i seguenti difetti di progettazione:

  • La classe "Soluzione" contiene solo dati, non è una vera classe OOP con un comportamento
  • Anche le altre classi non sono realmente classi OOP, perché hanno solo un metodo e sono piuttosto procedurali
  • Odore di codice: Classe SolutionBuilder e NuGetUpdater terminano con "er"

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

...

Sono d'accordo che è del tutto normale che una classe abbia un solo metodo, ma di recente ho letto molti blog che dicono che si tratta di una cattiva OOP e piuttosto di una codifica procedurale. Quindi la mia prima idea è stata quella di spostare tutti quei metodi nella classe della soluzione, ma poi ho pensato che questo avrebbe sicuramente violato il principio della singola responsabilità e anche rendere la classe Solution piuttosto grande.

Link ai post del blog che ho letto:
Le convenzioni di codifica ti fanno male
Non creare oggetti che finiscono con" er "

Ho frainteso gli autori di questi post del blog? Qualcuno ha un'idea migliore su come strutturare questo codice in modo orientato agli oggetti? O è questo un caso in cui questo è il modo migliore per farlo?

    
posta Ashwani Mehlem 30.06.2016 - 12:29
fonte

1 risposta

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 una classe ha un solo metodo , di solito è Execute() o qualcosa di equivalente. La domanda che devi porci è: che cosa stai incapsulando usando una classe, se hai un solo metodo? Ecco perché quei blog dicono che potresti volerlo ripensare.

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

Una "responsabilità" è semplicemente una "area di competenza". Le classi sono specializzate in qualcosa. Potrebbero fare più di una cosa con quel qualcosa. Spetta a te capire dove è questo equilibrio. Bob Martin afferma che una responsabilità è una "ragione per cambiare" e che le classi dovrebbero avere una sola ragione per cambiare, ergo una sola responsabilità.

Did I misunderstand the authors of these blog posts?

Sì, l'hai fatto.

Il punto Le convenzioni di codifica ti fanno male è che stiamo affogando nei principi architettonici, e in gran parte non è necessario.

Certo, puoi gettare oggetti GOF e SOLID e FactoryFactoryFactory tutto il giorno. Il tuo castello di carte sarà bello, ma quanto effettivamente funziona?

Oppure puoi scrivere un po 'di codice e fare qualcosa.

L'ho visto in prima persona, in più di un'occasione. Eserciti di sviluppatori che fanno tutto bene, seguendo tutti i più recenti principi architettonici, ma scrivendo software che è assolutamente inflessibile, difficile da ragionare e che richiede l'utilizzo di risme di codice per evolversi.

Nel frattempo, un piccolo team agile sta scrivendo una nuova interfaccia utente per un sistema che utilizza un framework all'avanguardia che, sebbene probabilmente rompa tutti i tipi delle regole architettoniche, consente flessibilità nella progettazione, fa leva su una tonnellata di lavoro fatto da altri, semplifica drasticamente la codifica e riduce il time to market. Può fare tutte queste cose perché è pragmatico, non dogmatico.

Steve Yegge lo illustra meglio di me nel suo "Regno di "Nouns" article :

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 la tua testa fa male a guardarlo, beh ... fa male anche al mio.

Il punto di non creare oggetti che finiscono con 'er' è semplicemente che, se la tua classe termina in er , è meglio mettere quella funzionalità nell'oggetto reale che la tua classe helper sta aiutando. Questo è tutto.

Un'ultima nota: Non esiste un modo giusto o sbagliato. Esiste solo il modo in cui meglio soddisfa i requisiti software e le esigenze aziendali. Se tutta la cerimonia funziona perché sei un negozio Java, il codice legacy è tutto Kingdom of Nouns e tutti lo comprendono, quindi con tutti i mezzi, continua lungo quel percorso.

Non è l'unico percorso, però.

    
risposta data 30.06.2016 - 18:55
fonte