Accoppiamento semantico contro classe grande

3

Ho l'hardware con cui comunico via TCP. Questo hardware accetta ~ 40 diversi comandi / richieste con circa 20 risposte diverse. Ho creato una classe HardwareProxy che ha un TcpClient per inviare e ricevere dati. Non mi piaceva l'idea di avere 40 metodi diversi per inviare comandi / richieste, così ho iniziato il percorso di avere un singolo metodo SendCommand che accetta un ICommand e restituisce una risposta IR, ciò comporta 40 diverse classi SpecificCommand. Il problema è che richiede l'accoppiamento semantico, ovvero il metodo che invoca SendCommand riceve una IResponse che deve downcast a SpecificResponse, io uso una mappa futura che credo garantisca la risposta specifica appropriata, ma ho l'impressione che questo codice abbia un odore. Oltre all'accoppiamento semantico, ICommand e IResponse sono essenzialmente classi astratte vuote (Marker Interfaces) e questo mi sembra sospetto.

Se vado con i 40 metodi, non credo di aver infranto il principio della responabilità, poiché la responsabilità della classe HardwareProxy è quella di agire come l'hardware, che ha tutti questi comandi. Questo percorso è solo brutto, inoltre mi piacerebbe avere versioni asincrone, quindi ci sarebbero circa 80 metodi.

È meglio mordere il proiettile e avere una classe di grandi dimensioni, accettare l'accoppiamento e MarkerInterfaces per una soultuion più piccola, o mi sto perdendo in un modo migliore?

Grazie.

Soluzione possibile

Sulla base della risposta di DXM questo è ciò che mi è venuto in mente:

struct Proxy
{
   template<class C, class R>
   ICommand<C, R>* CreateCommand()
   {
      return new C(this);
   }

   void Send(std::string s)
   {
      //tcp send here for now print command name
      cout << s << endl;
   }
};

template<class Derived>
struct IResponse
{};

struct DerivedResponse1 : IResponse<DerivedResponse1>
{
   std::string GetValue()
   {
      return "DerivedResponse1";
   }
};

template<class Derived, class Response>
struct ICommand
{
protected:
   Proxy* pProxy;
public:
   Response* Send()
   {
      std::string msg = "Sent: " + static_cast<Derived*> (this)->GetName();
      pProxy->Send(msg);
      //wait for future here
      Response* pResponse = new Response; //replace with future value
      return pResponse;
   }
};

struct DerivedCommand1 : public ICommand<DerivedCommand1, DerivedResponse1>
{
   DerivedCommand1(Proxy* pProxy)
   {
      this->pProxy = pProxy;
   }

   std::string GetName()
   {
       return "DerivedCommand1";
   }
};

int main()
{
   Proxy proxy;

   ICommand<DerivedCommand1, DerivedResponse1>* pDerivedCommand = 
           proxy.CreateCommand<DerivedCommand1, DerivedResponse1>();
   DerivedResponse1* pDerivedResponse = pDerivedCommand->Send();
   cout << "Received :" << pDerivedResponse->GetValue() << endl;

   return 0;
}

Come ti sembra? Sembra che potrei anche non aver bisogno dell'interfaccia IResponse, dovremo vedere quando la impugno. Mi rendo conto che mancano molte cose importanti, come il futuro, ma volevo prima scaricare il modello. Cosa ne pensi di passare il proxy al comando? Inoltre, che dire di avere l'invio nell'ICommand anziché scriverlo per ogni comando? Sto violando altri principi OO? Grazie.

    
posta zrp 31.10.2013 - 21:25
fonte

2 risposte

2

Penso che il tuo passaggio da 40 metodi in una classe a 40 classi di comando sia stato fatto nella giusta direzione. Continuerò a muovermi ancora di più in quella direzione ...

Invece di avere un punto di ingresso di invio nella tua classe HardwareProxy principale, fai in modo che ognuna delle tue classi concrete implementa send () che restituisca un oggetto di risposta concreto. Sotto il cofano le classi di comando possono derivare dalla comune classe di base dei comandi, che conterrebbe un riferimento a HardwareProxy (o forse TcpClient è sufficiente).

Un altro aspetto positivo di c ++ è la possibilità di definire un'interfaccia pubblica pulita e sicura al tipo con l'aiuto di modelli:

proxy = new HardwareProxy();
proxy->Connect(...);

SpecificCommand* command = proxy.CreateCommand<SpecificCommand>();
SpecificResponse* response = command->send(...);

Aggiornamento: La tua ultima idea non è esattamente quella che avevo in mente, come probabilmente potresti dire se confronti l'esempio del mio cliente sopra con la tua funzione principale

Non penso che ci sia alcun valore nella definizione della classe generica di ICommand, specialmente dal momento in cui si crea un ICommand < > modello, perdi comunque il polimorfismo. Il tuo codice cliente dovrebbe essere in grado di lavorare direttamente con le tue classi SpecificCommand / SpecificResponse. Presumibilmente ogni comando / risposta avrebbe dati diversi in entrata e in uscita, quindi non c'è motivo di mettere quella parte in un'interfaccia generica.

Tuttavia, se si dispone di 40 diversi comandi / risposte univoci, essi avranno qualcosa in comune, il modo in cui impacchettano / serializzano / deserializzano i dati per prepararli alle comunicazioni TCP. Quindi quello che vorrei fare è avere una classe BaseCommand / BaseResponse (ancora non modelli) che gestisca tutto il lavoro comune e sia potenzialmente responsabile di parlare con l'oggetto TCPClient. Le classi di base consentono inoltre al proxy di trattare tutti i comandi in modo uniforme nel caso in cui sia necessario tenere traccia di quali comandi sono in sospeso o essere in grado di scorrere tutti e di pulirli con garbo quando la connessione TCP cade.

Anche se adoro i template c ++ e li uso abbastanza liberamente, un consiglio che ti darò è di evitarli a meno che tu non ne abbia effettivamente bisogno. Nel tuo esempio, stai utilizzando un parametro template per eseguire un cast del puntatore per accedere alla funzione membro della classe derivata. Si potrebbe facilmente dichiarare GetName () come virtuale. Il motivo per cui ho suggerito la funzione di template CreateCommand < > () è dovuto al fatto che il codice apparirebbe più pulito perché altrimenti sarebbe necessario restituire il tipo BaseCommand e il codice client dovrebbe eseguire ancora un altro cast di puntatori. Ma il vero design sottostante che sto pensando potrebbe facilmente essere fatto senza template.

    
risposta data 31.10.2013 - 22:06
fonte
1

Personalmente, sto bene con le 40 funzioni. Non sembra che tu voglia un sottoinsieme / interfaccia del proxy, e la comunicazione tcp non è un ottimo candidato per il test delle unità.

Non tutto deve essere OOP.

Detto questo, penso che sia sciocco cercare di legare quella volontà dei comandi in un'unica interfaccia. Ammettiamolo: non tutti i comandi hanno gli stessi parametri, e gli utenti non vorranno iterare attraverso comandi arbitrari ed eseguirli. Richiederanno un comando specifico (probabilmente tramite un'interfaccia di comando specifica) che contiene implicitamente il suo carico utile e la sua risposta.

    
risposta data 01.11.2013 - 01:27
fonte

Leggi altre domande sui tag