Programmation orientée objet: getters / setters ou noms logiques

12

Je pense actuellement à une interface avec une classe que j'écris. Cette classe contient des styles pour un caractère, par exemple si le caractère est en gras, en italique, souligné, etc. Je discute avec moi-même depuis deux jours si je dois utiliser des getters / setters ou des noms logiques pour les méthodes qui changent les valeurs en ces styles. Bien que j'ai tendance à préférer les noms logiques, cela signifie écrire du code qui n'est pas aussi efficace et pas aussi logique. Laisse moi te donner un exemple.

J'ai une classe CharacterStylesqui a des variables membres bold, italic, underline(et quelques autres, mais je vais les laisser garder les choses simples). La façon la plus simple d'autoriser d'autres parties du programme à accéder à ces variables serait d'écrire des méthodes getter / setter, afin que vous puissiez faire styles.setBold(true)et styles.setItalic(false).

Mais je n'aime pas ça. Non seulement parce que beaucoup de gens disent que les getters / setters cassent l'encapsulation (est-ce vraiment si mauvais?), Mais surtout parce que cela ne me semble pas logique. Je m'attends à styliser un personnage à travers une méthode, styles.format("bold", true)ou quelque chose comme ça, mais pas à travers toutes ces méthodes.

Mais il y a un problème. Étant donné que vous ne pouvez pas accéder à une variable membre d'objet par le contenu d'une chaîne en C ++, je devrais soit écrire un grand conteneur d'instructions if / switch pour tous les styles, soit je devrais stocker les styles dans un tableau associatif ( carte).

Je ne peux pas comprendre quelle est la meilleure façon. Un instant, je pense que je devrais écrire les getters / setters, et l'instant suivant, je me penche dans l'autre sens. Ma question est: que feriez-vous? Et pourquoi feriez-vous ça?

Grenouille
la source
La classe CharacterStyles fait-elle autre chose que regrouper trois booléens? Comment est-il consommé?
Mark Canlas
Oui, car CharacterStyles devrait pouvoir hériter des autres CharacterStyles. Cela signifie que si j'ai un CharacterStyles avec boldset to trueet que les autres variables ne sont pas définies, les méthodes getter pour les autres variables doivent renvoyer la valeur du style parent (qui est stocké dans une autre propriété), tandis que le getter de la boldpropriété doit retourner true. Il y a d'autres choses comme un nom et la façon dont il s'affiche également dans l'interface.
Frog
Vous pouvez utiliser une énumération pour définir les différentes options de style, puis utiliser une instruction switch. Btw, comment gérez-vous undefined?
Tyanna
@ Tyanna: En effet, j'ai aussi pensé à utiliser une énumération, mais ce n'est qu'un détail mineur. Je prévoyais de définir des valeurs non définies sur NULL. N'est-ce pas la meilleure façon?
Frog

Réponses:

6

Oui, les getters / setters cassent l'encapsulation - ils ne sont fondamentalement qu'une couche supplémentaire entre l'accès direct au champ sous-jacent. Vous pourriez tout aussi bien y accéder directement.

Maintenant, si vous voulez que des méthodes plus complexes accèdent au champ, c'est valide, mais au lieu d'exposer le champ, vous devez réfléchir aux méthodes que la classe devrait offrir à la place. c'est à dire. au lieu d'une classe Bank avec une valeur Money exposée à l'aide d'une propriété, vous devez réfléchir aux types d'accès qu'un objet Bank devrait offrir (ajouter de l'argent, retirer, obtenir un solde) et les implémenter à la place. Une propriété sur la variable Money est uniquement syntaxiquement différente de l'exposition directe de la variable Money.

DrDobbs a un article qui en dit plus.

Pour votre problème, j'aurais 2 méthodes setStyle et clearStyle (ou autre) qui prennent une énumération des styles possibles. Une instruction switch à l'intérieur de ces méthodes appliquerait alors les valeurs pertinentes aux variables de classe appropriées. De cette façon, vous pouvez changer la représentation interne des styles en quelque chose d'autre si vous décidez plus tard de les stocker sous forme de chaîne (pour une utilisation en HTML par exemple) - quelque chose qui nécessiterait que tous les utilisateurs de votre classe soient également modifiés si vous utilisiez obtenir / définir les propriétés.

Vous pouvez toujours activer des chaînes si vous souhaitez prendre des valeurs arbitraires, soit avoir une grande instruction if-then (s'il y en a quelques-unes), soit une mappe de valeurs de chaîne vers des pointeurs de méthode à l'aide de std :: mem_fun (ou std :: fonction "donc" en gras "serait stocké dans une clé de carte avec sa valeur étant un sts :: mem_fun à une méthode qui définit la variable en gras sur vrai (si les chaînes sont les mêmes que les noms des variables membres, alors vous pouvez également utiliser la macro de chaîne pour réduire la quantité de code que vous devez écrire)

gbjbaanb
la source
Excellente réponse! Surtout la partie sur le stockage des données au format HTML m'a semblé être une bonne raison pour emprunter cette voie. Vous suggéreriez donc de stocker les différents styles dans une énumération, puis de définir des styles comme styles.setStyle(BOLD, true)? Est-ce exact?
Frog
oui, seulement j'aurais 2 méthodes - setStyle (BOLD) et clearstyle (BOLD). Oubliez le 2ème paramètre, je le préfère juste comme ça et vous pourriez alors surcharger clearStyle pour ne prendre aucun paramètre pour effacer tous les drapeaux de style.
gbjbaanb
Cela ne fonctionnerait pas, car certains types (comme la taille de police) ont besoin d'un paramètre. Mais encore merci pour la réponse!
Frog
J'ai essayé d'implémenter cela, mais il y a un problème que je n'ai pas considéré: comment implémentez styles.getStyle(BOLD)-vous lorsque vous avez non seulement des variables membres de type booléen, mais aussi de types entier et chaîne (par exemple:) styles.getStyle(FONTSIZE). Comme vous ne pouvez pas surcharger les types de retour, comment programmez-vous cela? Je sais que vous pourriez utiliser des pointeurs vides, mais cela me semble être un très mauvais moyen. Avez-vous des conseils sur la façon de procéder?
Frog
vous pouvez retourner une union, ou une structure, ou avec la nouvelle norme, vous pouvez surcharger par type de retour. Cela dit, essayez-vous d'implémenter une seule méthode pour définir un style, où le style est un indicateur, une famille de polices et une taille? Peut-être que vous essayez de forcer un peu trop l'abstraction dans ce cas.
gbjbaanb
11

Une idée que vous n'avez peut-être pas envisagée est le motif de décoration . Plutôt que de définir des indicateurs dans un objet, puis d'appliquer ces indicateurs à tout ce que vous écrivez, vous encapsulez la classe qui effectue l'écriture dans Decorators, qui à son tour applique les styles.

Le code appelant n'a pas besoin de savoir combien de ces wrappers vous avez mis autour de votre texte, vous appelez simplement une méthode sur l'objet externe et il appelle la pile.

Pour un exemple de pseudocode:

class TextWriter : TextDrawingInterface {
    public:
        void WriteString(string x) {
            // write some text somewhere somehow
        }
}

class BoldDecorator : TextDrawingInterface {
    public:
        void WriteString(string x) {
            // bold application on
            m_textWriter.WriteString(x);
            // bold application off
        }

        ctor (TextDrawingInterface textWriter) {
            m_textWriter = textWriter;
        }

    private:
        TextWriter m_TextWriter;
}

Et ainsi de suite, pour chaque style de décoration. Dans son utilisation la plus simple, vous pouvez alors dire

TextDrawingInterface GetDecoratedTextWriter() {
    return new BoldDecorator(new ItalicDecorator(new TextWriter()));
}

Et le code qui appelle cette méthode n'a pas besoin de connaître le détail de ce qu'elle reçoit. Tant que c'est QUELQUE CHOSE qui peut dessiner du texte via une méthode WriteString.

pdr
la source
Hmmm, je vais y jeter un œil demain avec un esprit frais et je vous répondrai alors. Merci d'avoir répondu.
Frog
Je suis un grand fan du modèle Decorator. Le fait que ce modèle ressemble à HTML (où l'opération principale consiste à entourer la chaîne d'entrée de balises) atteste que cette approche peut satisfaire tous les besoins de votre utilisateur d'interface. Une chose à garder à l'esprit est qu'une interface qui est bonne et facile à utiliser peut avoir une implémentation sous-jacente qui est techniquement compliquée. Par exemple, si vous implémentez vous-même le rendu de texte, vous constaterez peut-être qu'il TextWriterfaut parler aux deux BoldDecoratoret ItalicDecorator(dans les deux sens) pour faire le travail.
rwong
bien que ce soit une bonne réponse, ne réintroduit-elle pas la question de l'op? "bold applicaton on" -> comment cela va-t-il se faire? en utilisant des setters / getters comme SetBold ()? C'est exactement la question de l'op.
stijn
1
@stijn: Pas vraiment. Il existe plusieurs façons d'éviter cette situation. Par exemple, vous pouvez envelopper cela dans un générateur avec une méthode toggleStyle (argument enum) qui ajoute et supprime des décorateurs d'un tableau, décorant uniquement l'élément final lorsque vous appelez BuildDecoratedTextWriter. Ou vous pouvez faire comme suggéré et compliquer la classe de base. Cela dépend des circonstances et l'OP n'était pas assez précis sur les spécifications globales pour deviner lequel est le mieux pour lui. Il semble assez intelligent pour pouvoir le comprendre une fois qu'il a obtenu le modèle.
pdr
1
J'ai toujours pensé que le modèle de décorateur implique une commande des décorations. Considérez l'exemple de code illustré; comment supprimer le ItalicDecorator? Pourtant, je pense que quelque chose comme Decorator est la voie à suivre. Les caractères gras, italique et souligné ne sont pas les trois seuls styles. Il y a des minces, des semi-gras, des condensés, des noirs, des larges, des italiques avec du swash, des petites majuscules, etc.
Barry Brown
0

Je pencherais pour la deuxième solution. Il semble plus élégant et flexible. Bien qu'il soit difficile d'imaginer d'autres types que gras, italique et souligné (overline?), Il serait difficile d'ajouter de nouveaux types à l'aide de variables membres.

J'ai utilisé cette approche dans l'un de mes derniers projets. J'ai une classe qui peut avoir plusieurs attributs booléens. Le nombre et les noms des attributs peuvent changer dans le temps. Je les stocke dans un dictionnaire. Si un attribut n'est pas présent, je suppose que sa valeur est "false". Je dois également stocker une liste de noms d'attributs disponibles, mais c'est une autre histoire.

Andrzej Bobak
la source
1
En dehors de ces types, j'ajouterais barré, taille de police, famille de polices, exposant, indice et peut-être d'autres plus tard. Mais pourquoi avez-vous choisi cette méthode dans votre projet? Ne pensez-vous pas que c'est du code sale d'avoir à stocker une liste d'attributs autorisés? Je suis vraiment curieux de savoir quelles sont vos réponses à ces questions!
Frog
J'ai dû faire face à une situation où certains attributs pouvaient être définis par les clients lorsque l'application était déjà déployée. Certains autres attributs étaient requis pour certains clients mais obsolètes pour les autres. Je pourrais personnaliser les formulaires, les ensembles de données stockés et résoudre d'autres problèmes en utilisant cette approche. Je ne pense pas que cela génère du code sale. Parfois, vous ne pouvez tout simplement pas prédire le type d'informations dont votre client aura besoin.
Andrzej Bobak
Mais mon client n'aura pas besoin d'ajouter d'attributs personnalisés. Et dans ce cas, je pense que ça a l'air un peu "hacky". Tu n'es pas d'accord?
Frog
Si vous ne rencontrez pas de problème de nombre dynamique d'attributs, vous n'avez pas besoin d'espace flexible pour les stocker. C'est vrai :) Je ne suis pas d'accord cependant avec l'autre partie. Le stockage des attributs dans un dictionnaire / hashmap / etc n'est pas une mauvaise approche à mon avis.
Andrzej Bobak
0

Je pense que vous pensez trop à la question.

styles.setBold(true) et styles.setItalic(false)sont OK

Tulains Córdova
la source