Pour rendre votre code faiblement couplé, voici quelques points simples à retenir:
Partie 1:
Techniquement connu sous le nom de «séparation des préoccupations». Chaque classe a un rôle spécifique, elle doit gérer la logique métier ou la logique applicative. Essayez d'éviter la classe qui combine les deux responsabilités. Par exemple, une classe qui gère des données (à long terme) est une logique d'application tandis qu'une classe qui utilise des données est une logique métier.
Personnellement, je me réfère à cela (dans mon propre petit monde) comme create it or use it
. Une classe doit créer un objet ou utiliser un objet qu'elle ne doit jamais faire les deux.
Partie 2:
Comment mettre en œuvre la séparation des préoccupations.
Comme point de départ, il existe deux techniques simples:
Remarque: les modèles de conception ne sont pas absolus.
Ils sont censés être adaptés à la situation mais ont un thème sous-jacent similaire à toutes les applications. Alors ne regardez pas les exemples ci-dessous et dites que je dois suivre cela de manière rigide; ce ne sont que des exemples (et un peu artificiels à cela).
Injection de dépendance :
C'est là que vous passez un objet qu'une classe utilise. L'objet que vous transmettez basé sur une interface pour que votre classe sache quoi en faire mais n'a pas besoin de connaître l'implémentation réelle.
class Tokenizer
{
public:
Tokenizer(std::istream& s)
: stream(s)
{}
std::string nextToken() { std::string token; stream >> token;return token;}
private:
std::istream& stream;
};
Ici, nous injectons le flux dans un Tokenizer. Le tokenizer ne sait pas de quel type est le flux tant qu'il implémente l'interface de std :: istream.
Modèle de localisateur de service :
Le modèle de localisateur de service est une légère variation de l'injection de dépendance. Plutôt que de donner un objet qu'il peut utiliser, vous lui passez un objet qui sait localiser (créer) l'objet que vous souhaitez utiliser.
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;
};
Ici, nous passons l'objet d'application un objet persistant. Lorsque vous effectuez une action de sauvegarde / chargement, il utilise la persistance pour créer un objet qui sait réellement comment effectuer l'action. Remarque: Encore une fois, la persistance est une interface et vous pouvez fournir différentes implémentations en fonction de la situation.
Ceci est utile lorsqu'un potentially
objet unique est requis chaque fois que vous instanciez une action.
Personnellement, je trouve cela particulièrement utile pour écrire des tests unitaires.
Remarque sur les modèles:
Les modèles de conception sont un énorme sujet en soi. Il ne s'agit en aucun cas d'une liste exclusive de modèles que vous pouvez utiliser pour faciliter le couplage lâche; ce n'est qu'un point de départ commun.
Avec l'expérience, vous vous rendrez compte que vous utilisez déjà ces modèles, c'est juste que vous n'avez pas utilisé leurs noms formels. En normalisant leurs noms (et en faisant en sorte que tout le monde les apprenne), nous constatons qu'il est facile et plus rapide de communiquer des idées.