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.