J'étudie la POO en C ++ et, même si je connais les définitions de ces 3 concepts, je ne sais pas vraiment quand ni comment l'utiliser.
Utilisons cette classe pour l'exemple:
class Person{
private:
string name;
int age;
public:
Person(string p1, int p2){this->name=p1; this->age=p2;}
~Person(){}
void set_name (string parameter){this->name=parameter;}
void set_age (int parameter){this->age=parameter;}
string get_name (){return this->name;}
int get_age (){return this->age;}
};
1. Singleton
COMMENT fonctionne la restriction de la classe d'avoir un seul objet?
POUVEZ- VOUS concevoir une classe qui aurait SEULEMENT 2 instances? Ou peut-être 3?
QUAND utilise-t-on un singleton recommandé / nécessaire? Est-ce une bonne pratique?
Autant que je sache, s'il n'y a qu'une seule fonction virtuelle pure, la classe devient abstraite. Donc, en ajoutant
virtual void print ()=0;
le ferait, non?
POURQUOI auriez-vous besoin d'une classe dont l'objet n'est pas requis?
3.Interface
Si une interface est une classe abstraite dans laquelle toutes les méthodes sont de pures fonctions virtuelles, alors
Quelle est la principale différence entre les 2?
Merci d'avance!
Réponses:
1. Singleton
Vous limitez le nombre d'instances car le constructeur sera privé, ce qui signifie que seules les méthodes statiques peuvent créer des instances de cette classe (il existe d'autres astuces pour accomplir cela, mais ne nous laissons pas emporter).
Créer une classe qui n'aura que 2 ou 3 instances est parfaitement réalisable. Vous devez utiliser singleton chaque fois que vous ressentez la nécessité de n'avoir qu'une seule instance de cette classe dans l'ensemble du système. Cela arrive généralement aux classes qui ont un comportement de «gestionnaire».
Si vous voulez en savoir plus sur les singletons, vous pouvez commencer dans Wikipedia et en particulier pour C ++ dans cet article .
Il y a certainement de bonnes et de mauvaises choses à propos de ce modèle, mais cette discussion appartient ailleurs.
2. Classes abstraites
Oui c'est vrai. Une seule méthode virtuelle marquera la classe comme abstraite.
Vous utiliserez ce type de classes lorsque vous avez une hiérarchie de classes plus grande dans laquelle les classes supérieures ne devraient pas vraiment être instanciées.
Supposons que vous définissiez une classe de mammifères et que vous l'héritiez ensuite de Dog and Cat. Si vous y réfléchissez, il n'y a pas beaucoup de sens d'avoir une pure instance de mammifère puisque vous devez d'abord savoir de quel type de mammifère il s'agit vraiment.
Potentiellement, il existe une méthode appelée MakeSound () qui n'a de sens que dans les classes héritées, mais il n'y a pas de son commun que tous les mammifères peuvent faire (c'est juste un exemple n'essayant pas de justifier les sons des mammifères ici).
Cela signifie donc que Mammal devrait être une classe abstraite car il aura un comportement commun implémenté pour tous les mammifères mais il n'est pas vraiment censé être instancié. C'est le concept de base derrière les classes abstraites mais il y a certainement plus à apprendre.
3. Interfaces
Il n'y a pas d'interfaces pures en C ++ dans le même sens que vous en Java ou C #. La seule façon d'en créer une est d'avoir une classe abstraite pure qui imite la plupart des comportements que vous souhaitez d'une interface.
Fondamentalement, le comportement que vous recherchez est de définir un contrat dans lequel d'autres objets peuvent interagir sans se soucier de l'implémentation sous-jacente. Lorsque vous créez une classe purement abstraite, cela signifie que toute implémentation appartient ailleurs, donc le but de cette classe ne concerne que le contrat qu'elle définit. Il s'agit d'un concept très puissant en OO et vous devriez certainement y réfléchir davantage.
Vous pouvez lire sur la spécification d'interface pour C # dans MSDN pour avoir une meilleure idée:
http://msdn.microsoft.com/en-us/library/ms173156.aspx
C ++ fournira le même type de comportement en ayant une pure classe abstraite.
la source
La plupart des gens ont déjà expliqué ce que sont les singletons / classes abstraites. J'espère que je fournirai une perspective légèrement différente et donnerai des exemples pratiques.
Singletons - Lorsque vous souhaitez que tout le code appelant utilise une seule instance de variables, pour quelque raison que ce soit, vous disposez des options suivantes:
De toutes les options mauvaises et mauvaises, si vous avez besoin de données globales, singleton est une bien meilleure approche que l'une des deux précédentes. Il vous permet également de garder vos options ouvertes si demain vous changez d'avis et décidez d'utiliser l' inversion de contrôle au lieu d'avoir des données globales.
Alors, où utiliseriez-vous un singleton? Voici quelques exemples:
Journalisation - si vous voulez que l'ensemble de votre processus ait un seul journal, vous pouvez créer un objet journal et le faire circuler partout. Mais que faire si vous disposez de 100 000 000 lignes de code d'application hérité? les modifier tous? Ou vous pouvez simplement introduire ce qui suit et commencer à l'utiliser où bon vous semble:
Cache de connexion au serveur - C'est quelque chose que j'ai dû introduire dans notre application. Notre base de code, et il y en avait beaucoup, était utilisée pour se connecter aux serveurs quand cela lui plaisait. La plupart du temps, cela allait, sauf s'il y avait une sorte de latence dans le réseau. Nous avions besoin d'une solution et la refonte d'une application vieille de 10 ans n'était pas vraiment sur la table. J'ai écrit un CServerConnectionManager singleton. Ensuite, j'ai parcouru le code et remplacé les appels CoCreateInstanceWithAuth par un appel de signature identique qui a appelé ma classe. Maintenant, après la première tentative, la connexion a été mise en cache et le reste du temps, les tentatives de «connexion» étaient instantanées. Certains disent que les singletons sont mauvais. Je dis qu'ils m'ont sauvé les fesses.
Pour le débogage, nous trouvons souvent la table d'objets globale en cours d'exécution très utile. Nous avons des cours dont nous aimerions faire le suivi. Ils dérivent tous de la même classe de base. Pendant l'instanciation, ils appellent la table objet singleton et s'enregistrent. Lorsqu'ils sont détruits, ils se désinscrivent. Je peux accéder à n'importe quelle machine, m'attacher à un processus et créer une liste d'objets en cours d'exécution. Je suis dans le produit depuis plus d'une demi-décennie et je n'ai jamais pensé que nous ayons jamais eu besoin de 2 tables d'objets "globales".
Nous avons des classes utilitaires d'analyseur de chaînes relativement complexes qui reposent sur des expressions régulières. Les classes d'expressions régulières doivent être initialisées avant de pouvoir effectuer des correspondances. L'initialisation est quelque peu coûteuse car c'est à ce moment qu'un FSM est généré sur la base d'une chaîne d'analyse. Cependant, après cela, la classe d'expression régulière peut être accédée en toute sécurité par 100 threads car une fois construit, FSM ne change jamais. Ces classes d'analyseur utilisent en interne des singletons pour s'assurer que cette initialisation ne se produit qu'une seule fois. Cela a considérablement amélioré les performances et n'a jamais causé de problèmes en raison de "mauvais singletons".
Cela dit, vous devez garder à l'esprit quand et où utiliser les singletons. 9 fois sur 10, il y a une meilleure solution et par tous les moyens, vous devriez l'utiliser à la place. Cependant, il y a des moments où singleton est absolument le bon choix de conception.
Sujet suivant ... interfaces et classes abstraites. D'abord, comme d'autres l'ont mentionné, l'interface EST une classe abstraite, mais elle va au-delà en imposant qu'elle n'a absolument aucune implémentation. Dans certaines langues, le mot-clé d'interface fait partie de la langue. En C ++, nous utilisons simplement des classes abstraites. Microsoft VC ++ a pris une étape pour définir cela quelque part en interne:
... afin que vous puissiez toujours utiliser le mot-clé d'interface (il sera même mis en surbrillance comme un "vrai" mot-clé), mais en ce qui concerne le compilateur réel, c'est juste une structure.
Alors, où utiliseriez-vous cela? Revenons à mon exemple de table d'objets en cours d'exécution. Disons que la classe de base a ...
impression de vide virtuel () = 0;
Voilà votre classe abstraite. Les classes qui utilisent la table d'objets d'exécution dériveront toutes de la même classe de base. La classe de base contient un code commun pour l'inscription / la désinscription. Mais il ne sera jamais instancié par lui-même. Maintenant, je peux avoir des classes dérivées (par exemple des requêtes, des écouteurs, des objets de connexion client ...), chacun implémentera print () de sorte que lorsqu'il sera attaché au processus et lui demandera ce qui est en cours d'exécution, chaque objet rapportera son propre état.
Les exemples de classes / interfaces abstraites sont innombrables et vous les utilisez certainement (ou devriez les utiliser) beaucoup, beaucoup plus fréquemment que vous utiliseriez des singletons. En bref, ils vous permettent d'écrire du code qui fonctionne avec les types de base et n'est pas lié à l'implémentation réelle. Cela vous permet de modifier l'implémentation ultérieurement sans avoir à modifier trop de code.
Voici un autre exemple. Disons que j'ai une classe qui implémente un enregistreur, CLog. Cette classe écrit dans le fichier sur le disque local. Je commence à utiliser cette classe dans mes 100 000 lignes de code héritées. Partout. La vie est belle jusqu'à ce que quelqu'un le dise, écrivons dans la base de données au lieu d'un fichier. Maintenant, je crée une nouvelle classe, appelons-la CDbLog et écrit dans la base de données. Pouvez-vous imaginer les tracas de passer par 100 000 lignes et de tout changer de CLog à CDbLog? Alternativement, je pourrais avoir:
Si tout le code utilisait l'interface ILogger, tout ce que je devrais changer est l'implémentation interne de CLogFactory :: GetLog (). Le reste du code fonctionnerait simplement de manière automatique sans que je doive lever le petit doigt.
Pour plus d'informations sur les interfaces et une bonne conception OO, je recommande fortement les principes, modèles et pratiques agiles d' oncle Bob en C # . Le livre est rempli d'exemples qui utilisent des abstractions et fournit des explications en langage clair de tout.
la source
Jamais. Pire que cela, ils sont une chienne absolue dont il faut se débarrasser, alors faire cette erreur une fois peut vous hanter pendant de très nombreuses années.
La différence entre les classes abstraites et les interfaces n'est absolument rien en C ++. Vous avez généralement des interfaces pour spécifier un certain comportement de la classe dérivée, mais sans avoir à tout spécifier. Cela rend votre code plus flexible, car vous pouvez échanger n'importe quelle classe qui répond aux spécifications les plus limitées. Les interfaces d'exécution sont utilisées lorsque vous avez besoin d'une abstraction au moment de l'exécution.
la source
Singleton est utile lorsque vous ne voulez pas plusieurs copies d'un objet particulier, il ne doit y avoir qu'une seule instance de cette classe - il est utilisé pour les objets qui conservent l'état global, doivent traiter le code non réentrant d'une manière ou d'une autre, etc.
Un singleton qui a un nombre fixe de 2 instances ou plus est un multiton , pensez au pool de connexions de base de données, etc.
L'interface spécifie une API bien définie qui aide à modéliser l'interaction entre les objets. Dans certains cas, vous pouvez avoir un groupe de classes qui ont des fonctionnalités communes - si c'est le cas, au lieu de les dupliquer dans les implémentations, vous pouvez ajouter des définitions de méthode à l'interface en les transformant en une classe abstraite .
Vous pouvez même avoir une classe abstraite dans laquelle toutes les méthodes sont implémentées, mais vous la marquez comme abstraite pour indiquer qu'elle ne doit pas être utilisée telle quelle sans sous-classement.
Remarque: L' interface et la classe abstraite ne sont pas très différentes dans le monde C ++ avec l'héritage multiple, etc., mais ont des significations différentes dans Java et al.
la source
Si vous arrêtez d'y penser, tout tourne autour du polymorphisme. Vous voulez être en mesure d'écrire un morceau de code une fois qui peut faire plus que ce que l'on pense en fonction de ce que vous passez.
Disons que nous avons une fonction comme le code Python suivant:
La bonne chose à propos de cette fonction est qu'elle sera capable de gérer n'importe quelle liste d'objets, tant que ces objets implémentent une méthode "printToScreen". Vous pouvez lui passer une liste de widgets heureux, une liste de widgets tristes ou même une liste qui en a un mélange et la fonction foo sera toujours en mesure de faire correctement son travail.
Nous nous référons à ce type de restriction d'avoir besoin d'avoir un ensemble de méthodes implémentées (dans ce cas, printToScreen) comme interface et les objets qui implémentent toutes les méthodes sont implémenter l'interface.
Si nous parlions d'un langage dynamique de type canard comme Python, nous serions fondamentalement terminés maintenant. Cependant, le système de type statique de C ++ exige que nous donnions une classe aux objecs dans notre fonction et il ne pourra travailler qu'avec des sous-classes de cette classe initiale.
Dans notre cas, la seule raison pour laquelle la classe Printable existe est de donner une place à la méthode printToScreen. Puisqu'il n'y a pas d'implémentation partagée entre les classes qui implémentent la méthode printToScreen, il est logique de faire de Printable une classe abstraite qui n'est utilisée que pour regrouper des classes similaires dans une hiérarchie commune.
En C ++, la classe absctract et les concepts d'interface sont un peu flous. Si vous voulez mieux les définir, les classes abstraites sont ce à quoi vous pensez, tandis que les interfaces signifient généralement l'idée plus générale et multilingue de l'ensemble des méthodes visibles qu'un objet expose. (Bien que certains langages, comme Java, utilisent le terme d'interface pour faire référence à quelque chose de plus directement comme une classe de base abstraite)
Fondamentalement, les classes concrètes spécifient comment les objets sont implémentés, tandis que les classes abstraites spécifient comment elles s'interfacent avec le reste du code. Pour rendre vos fonctions plus polymorphes, vous devriez essayer de recevoir un pointeur vers la superclasse abstraite chaque fois que cela aurait du sens de le faire.
Quant aux singletons, ils sont vraiment inutiles, car ils peuvent souvent être remplacés par un groupe de méthodes statiques ou de vieilles fonctions simples. Cependant, parfois vous avez une sorte de restriction qui vous oblige à utiliser un objet, même si vous ne voudriez pas vraiment en utiliser un, donc le motif singleton est approprié.
BTW, certaines personnes pourraient avoir commenté que le mot "interface" a une signification particulière dans le langage Java. Je pense cependant qu'il vaut mieux s'en tenir à la définition plus générale pour l'instant.
la source
Interfaces
Il est difficile de comprendre le but d'un outil qui résout un problème que vous n'avez jamais eu. Je n'ai pas compris les interfaces pendant un certain temps après avoir commencé la programmation. Nous aurons compris ce qu'ils ont fait, mais je ne savais pas pourquoi vous vouliez en utiliser un.
Voici le problème - vous savez ce que vous voulez faire, mais vous avez plusieurs façons de le faire, ou vous pouvez changer la façon dont vous le faites plus tard. Ce serait bien si vous pouviez jouer le rôle du manager désemparé - aboyez quelques commandes et obtenez les résultats que vous souhaitez sans vous soucier de la façon dont cela est fait.
Supposons que vous ayez un tout petit site Web et que vous enregistriez toutes les informations de vos utilisateurs dans un fichier csv. Ce n'est pas la solution la plus sophistiquée, mais elle fonctionne assez bien pour stocker les détails de l'utilisateur de votre maman. Plus tard, votre site décolle et vous avez 10 000 utilisateurs. Il est peut-être temps d'utiliser une base de données appropriée.
Si vous aviez été intelligent au début, vous auriez vu cela arriver et vous n'auriez pas fait les appels pour enregistrer directement sur csv. Au lieu de cela, vous pensez à ce dont vous avez besoin, quelle que soit la façon dont il a été mis en œuvre. Disons
store()
etretrieve()
. Vous créez unePersister
interface avec des méthodes abstraites pourstore()
etretrieve()
et créez uneCsvPersister
sous-classe qui implémente réellement ces méthodes.Plus tard, vous pouvez créer un
DbPersister
qui implémente le stockage et la récupération réels des données complètement différemment de la façon dont votre classe csv l'a fait.La grande chose est que tout ce que vous avez à faire maintenant est de changer
à
et puis vous avez terminé. Vos appels à
prst.store()
etprst.retrieve()
fonctionneront toujours, ils sont juste traités différemment "en coulisses".Maintenant, vous deviez encore créer les implémentations cvs et db, vous n'avez donc pas encore expérimenté le luxe d'être le patron. Les vrais avantages sont évidents lorsque vous utilisez des interfaces créées par quelqu'un d'autre. Si quelqu'un d'autre a eu la gentillesse d'en créer un
CsvPersister()
etDbPersister()
déjà, alors il vous suffit d'en choisir un et d'appeler les méthodes nécessaires. Si vous décidez d'utiliser l'autre plus tard, ou dans un autre projet, vous savez déjà comment cela fonctionne.Je suis vraiment rouillé sur mon C ++, donc je vais juste utiliser des exemples de programmation génériques. Les conteneurs sont un excellent exemple de la façon dont les interfaces vous facilitent la vie.
Vous pouvez avoir
Array
,LinkedList
,BinaryTree
, etc. toutes les sous - classes de ceContainer
qui a des méthodes telles queinsert()
,find()
,delete()
.Maintenant, lorsque vous ajoutez quelque chose au milieu d'une liste chaînée, vous n'avez même pas besoin de savoir ce qu'est une liste chaînée. Vous venez d'appeler
myLinkedList->insert(4)
et il parcourt comme par magie la liste et la colle dedans. Même si vous savez comment fonctionne une liste chaînée (ce que vous devriez vraiment faire), vous n'avez pas besoin de rechercher ses fonctions spécifiques, car vous savez probablement déjà ce qu'elles sont en utilisant une autreContainer
auparavant.Classes abstraites
Les classes abstraites sont assez similaires aux interfaces (eh bien, techniquement, les interfaces sont des classes abstraites, mais ici je veux dire des classes de base qui ont certaines de leurs méthodes étoffées.
Supposons que vous créez un jeu et que vous devez détecter les ennemis à proximité du joueur. Vous pouvez créer une classe de base
Enemy
qui a une méthodeinRange()
. Bien qu'il y ait beaucoup de choses différentes sur les ennemis, la méthode utilisée pour vérifier leur portée est cohérente. Par conséquent, votreEnemy
classe aura une méthode étoffée pour vérifier la portée, mais des méthodes virtuelles pures pour d'autres choses qui ne partagent aucune similitude entre les types ennemis.La bonne chose à ce sujet est que si vous gâchez le code de détection de plage ou si vous souhaitez le modifier, vous n'avez qu'à le changer en un seul endroit.
Bien sûr, il existe de nombreuses autres raisons pour les interfaces et les classes de base abstraites, mais ce sont quelques raisons pour lesquelles vous pouvez les utiliser.
Singletons
Je les utilise occasionnellement et je n'ai jamais été brûlé par eux. Cela ne veut pas dire qu'ils ne ruineront pas ma vie à un moment donné, sur la base des expériences des autres.
Voici une bonne discussion sur l'état mondial de la part de gens plus expérimentés et méfiants: pourquoi l'état mondial est-il si mal?
la source
Dans le règne animal, il existe divers animaux qui sont des mammifères. Ici, les mammifères sont une classe de base et divers animaux en dérivent.
Avez-vous déjà vu un mammifère passer? Oui, plusieurs fois je suis sûr - mais ils étaient tous de types de mammifères, non?
Vous n'avez jamais vu quelque chose qui n'était littéralement qu'un mammifère. Ils étaient tous des types de mammifères.
Le mammifère de classe est requis pour définir diverses caractéristiques et groupes mais il n'existe pas en tant qu'entité physique.
C'est donc une classe de base abstraite.
Comment se déplacent les mammifères? Marchent-ils, nagent-ils, volent, etc.?
Il n'y a aucun moyen de savoir au niveau des mammifères mais tous les mammifères doivent se déplacer d'une manière ou d'une autre (disons que c'est une loi biologique pour faciliter l'exemple).
Par conséquent, MoveAround () est une fonction virtuelle, car chaque mammifère qui dérive de cette classe doit pouvoir l'implémenter différemment.
Cependant, étant donné que chaque mammifère DOIT définir MoveAround car tous les mammifères doivent se déplacer et il est impossible de le faire au niveau des mammifères. Il doit être implémenté par toutes les classes enfants mais là, il n'a aucun sens dans la classe de base.
MoveAround est donc une fonction virtuelle pure.
Si vous avez une classe entière qui autorise l'activité mais n'est pas en mesure de définir au niveau supérieur comment cela doit être fait, alors toutes les fonctions sont purement virtuelles et il s'agit d'une interface.
Par exemple - si nous avons un jeu où vous allez coder un robot et me le soumettre pour combattre sur un champ de bataille, j'ai besoin de connaître les noms de fonction et les prototypes à appeler. Je me fiche de la façon dont vous l'implémentez de votre côté tant que l '«interface» est claire. Par conséquent, je peux vous fournir une classe d'interface dont vous dériverez pour écrire votre robot tueur.
la source