Per rendere il tuo codice liberamente accoppiato qui ci sono alcune cose semplici da ricordare:
Parte 1:
Tecnicamente noto come "Separazione della preoccupazione". Ogni classe ha un ruolo specifico, dovrebbe gestire la logica aziendale o la logica dell'applicazione. Cerca di evitare la classe che combina entrambe le responsabilità. vale a dire una classe che gestisce i dati (a lungo termine) è la logica dell'applicazione mentre una classe che utilizza i dati è la logica aziendale.
Personalmente mi riferisco a questo (nel mio piccolo mondo) come create it or use it
. Una classe dovrebbe creare un oggetto o usare un oggetto che non dovrebbe mai fare entrambi.
Parte 2:
Come implementare la separazione delle preoccupazioni.
Come punto di partenza ci sono due semplici tecniche:
Nota: i motivi di progettazione non sono assoluti.
Dovrebbero essere personalizzati in base alla situazione ma hanno un tema sottostante simile a tutte le applicazioni. Quindi non guardare gli esempi qui sotto e dire che devo seguirlo rigidamente; questi sono solo esempi (e sono leggermente inventati).
Iniezione delle dipendenze :
Qui è dove passi un oggetto che usa una classe. L'oggetto che passi in base a un'interfaccia in modo che la tua classe sappia cosa farne ma non ha bisogno di conoscere l'implementazione effettiva.
class Tokenizer
{
public:
Tokenizer(std::istream& s)
: stream(s)
{}
std::string nextToken() { std::string token; stream >> token;return token;}
private:
std::istream& stream;
};
Qui iniettiamo il flusso in un tokenizer. Il tokenizer non sa di che tipo è lo stream purché implementi l'interfaccia di std :: istream.
Modello di localizzazione di servizio :
Il modello del localizzatore di servizio è una leggera variazione sull'iniezione di dipendenza. Invece di dare un oggetto che può usare, gli passi un oggetto che sa come localizzare (creare) l'oggetto che vuoi usare.
class Application
{
public:
Application(Persister& p)
: persistor(p)
{}
void save()
{
std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
saveDialog.DoSaveAction();
}
void load()
{
std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
loadDialog.DoLoadAction();
}
private:
Persister& persistor;
};
Qui passiamo all'oggetto applicazione un oggetto persistenza. Quando si esegue un'azione di salvataggio / caricamento, utilizza il persistor per creare un oggetto che sappia effettivamente come eseguire l'azione. Nota: ancora una volta il persistor è un'interfaccia e puoi fornire implementazioni diverse a seconda della situazione.
Ciò è utile quando potentially
è richiesto un oggetto unico ogni volta che si crea un'istanza di un'azione.
Personalmente trovo che ciò sia particolarmente utile nella scrittura di unit test.
Nota dei motivi:
I modelli di progettazione sono un argomento enorme per sé. Questo non è affatto un elenco esclusivo di modelli che è possibile utilizzare per facilitare l'accoppiamento; questo è solo un punto di partenza comune.
Con l'esperienza ti renderai conto che stai già usando questi schemi è solo che non hai usato i loro nomi formali. Standardizzando i loro nomi (e convincendo tutti a impararli) scopriamo che è facile e veloce comunicare idee.