Objektorientierte Programmierung: Getter / Setter oder logische Namen

12

Ich denke gerade über eine Schnittstelle zu einer Klasse nach, die ich schreibe. Diese Klasse enthält Stile für ein Zeichen, zum Beispiel, ob das Zeichen fett, kursiv, unterstrichen usw. ist. Ich habe zwei Tage lang mit mir selbst diskutiert, ob ich für die Methoden, die die Werte in ändern, Getter / Setter oder logische Namen verwenden soll diese Stile. Ich bevorzuge zwar logische Namen, aber es bedeutet, Code zu schreiben, der nicht so effizient und nicht so logisch ist. Lassen Sie mich Ihnen ein Beispiel geben.

Ich habe eine Klasse bekam CharacterStylesdie Membervariablen hat bold, italic, underline(und einige andere, aber ich werde sie auslassen es einfach zu halten). Die einfachste Möglichkeit, anderen Programmteilen den Zugriff auf diese Variablen zu ermöglichen, besteht darin, Get- / Setter-Methoden zu schreiben, damit Sie styles.setBold(true)und ausführen können styles.setItalic(false).

Aber das gefällt mir nicht. Nicht nur, weil viele Leute sagen, dass Getter / Setter die Kapselung sprengen (ist das wirklich so schlimm?), Sondern vor allem, weil es mir nicht logisch erscheint. Ich erwarte, einen Charakter mit einer styles.format("bold", true)oder einer ähnlichen Methode zu stylen, aber nicht mit all diesen Methoden.

Es gibt jedoch ein Problem. Da Sie in C ++ nicht über den Inhalt eines Strings auf eine Objektelementvariable zugreifen können, müsste ich entweder einen großen if-Anweisungs- / switch-Container für alle Stile schreiben oder die Stile in einem assoziativen Array speichern ( Karte).

Ich kann nicht herausfinden, was der beste Weg ist. In einem Moment denke ich, ich sollte die Getter / Setter schreiben, und im nächsten Moment beuge ich mich in die andere Richtung. Meine Frage ist: Was würdest du tun? Und warum würdest du das tun?

Frosch
quelle
Tut die CharacterStyles-Klasse tatsächlich etwas anderes als drei Boolesche Werte bündeln? Wie wird es konsumiert?
Mark Canlas
Ja, da CharacterStyles von anderen CharacterStyles erben können sollen. Das heißt, wenn ich CharacterStyles mit boldset to trueund den anderen Variablen undefined habe, sollten die Getter-Methoden für die anderen Variablen den Wert des übergeordneten Stils (der in einer anderen Eigenschaft gespeichert ist) zurückgeben, während der Getter für die boldEigenschaft zurückgeben sollte true. Es gibt noch einige andere Dinge wie einen Namen und die Art und Weise, wie er in der Benutzeroberfläche angezeigt wird.
Frog
Sie könnten eine Aufzählung verwenden, um die verschiedenen Stiloptionen zu definieren, und dann eine switch-Anweisung verwenden. Übrigens, wie gehst du mit undefined um?
Tyanna
@ Tyanna: In der Tat habe ich auch darüber nachgedacht, eine Aufzählung zu verwenden, aber das ist nur ein kleines Detail. Ich wollte gerade undefinierte Werte auf NULL setzen. Ist das nicht der beste Weg?
Frosch

Antworten:

6

Ja, Getter / Setter unterbrechen die Kapselung - sie sind im Grunde genommen nur eine zusätzliche Ebene zwischen dem direkten Zugriff auf das zugrunde liegende Feld. Sie können genauso gut direkt darauf zugreifen.

Wenn Sie nun komplexere Methoden für den Zugriff auf das Feld benötigen, ist dies gültig, aber anstatt das Feld verfügbar zu machen, müssen Sie sich überlegen, welche Methoden die Klasse stattdessen anbieten soll. dh Anstatt einer Bank-Klasse mit einem Money-Wert, der über eine Eigenschaft verfügbar gemacht wird, müssen Sie überlegen, welche Zugriffsarten ein Bank-Objekt bieten soll (Geld hinzufügen, abheben, Guthaben abrufen), und diese stattdessen implementieren. Eine Eigenschaft für die Money-Variable unterscheidet sich nur syntaktisch davon, die Money-Variable direkt verfügbar zu machen.

DrDobbs hat einen Artikel , der mehr sagt.

Für Ihr Problem hätte ich 2 Methoden setStyle und clearStyle (oder was auch immer), die eine Aufzählung der möglichen Stile nehmen. Eine switch-Anweisung innerhalb dieser Methoden würde dann die relevanten Werte auf die entsprechenden Klassenvariablen anwenden. Auf diese Weise können Sie die interne Darstellung der Stile in eine andere Form ändern, wenn Sie sie später als Zeichenfolge speichern (z. B. zur Verwendung in HTML). In diesem Fall müssten auch alle Benutzer Ihrer Klasse geändert werden Eigenschaften holen / setzen.

Sie können weiterhin Strings einschalten, wenn Sie beliebige Werte annehmen möchten, entweder eine große if-then-Anweisung (falls es einige davon gibt) oder eine Zuordnung von String-Werten zu Methodenzeigern mithilfe von std :: mem_fun (oder std :: function ) so wird "fett" in einem Zuordnungsschlüssel gespeichert, dessen Wert sts :: mem_fun für eine Methode ist, die die Variable fett auf true setzt (wenn die Zeichenfolgen mit den Namen der Mitgliedsvariablen identisch sind, können Sie auch verwenden das stringifizierende Makro , um die Menge an Code zu reduzieren, die Sie schreiben müssen)

gbjbaanb
quelle
Hervorragende Antwort! Besonders der Teil über das Speichern der Daten als HTML erschien mir als guter Grund, diesen Weg zu gehen. Sie würden also vorschlagen, die verschiedenen Stile in einer Aufzählung zu speichern und dann Stile wie festzulegen styles.setStyle(BOLD, true)? Ist das korrekt?
Frosch
Ja, ich hätte nur zwei Methoden - setStyle (BOLD) und clearstyle (BOLD). Vergiss den 2. Parameter, ich bevorzuge es einfach so und du könntest dann clearStyle überladen, um keine Parameter zu nehmen, um alle Style-Flags zu löschen.
Gbjbaanb
Das würde nicht funktionieren, da einige Typen (wie Schriftgröße) einen Parameter benötigen. Aber trotzdem danke für die Antwort!
Frosch
Ich habe versucht, dies zu implementieren, aber es gibt ein Problem, an das ich nicht gedacht habe: Wie implementieren Sie, styles.getStyle(BOLD)wenn Sie nicht nur Mitgliedsvariablen vom Typ Boolean, sondern auch vom Typ Integer und String haben (zum Beispiel:) styles.getStyle(FONTSIZE). Wie programmieren Sie das, da Sie Rückgabetypen nicht überladen können? Ich weiß, Sie könnten leere Zeiger verwenden, aber das klingt für mich wirklich schlecht. Haben Sie Hinweise dazu?
Frosch
Sie können eine Union oder eine Struktur zurückgeben oder mit dem neuen Standard eine Überladung nach Rückgabetyp ausführen. Versuchen Sie dennoch, eine einzige Methode zum Festlegen eines Stils zu implementieren, wobei der Stil ein Flag, eine Schriftfamilie und eine Größe ist? Vielleicht versuchen Sie in diesem Fall, die Abstraktion ein wenig zu stark zu forcieren.
Gbjbaanb
11

Eine Idee, die Sie möglicherweise nicht berücksichtigt haben, ist das Decorator-Muster . Anstatt Flags in einem Objekt zu setzen und diese Flags dann auf alles anzuwenden, was Sie schreiben, binden Sie die Klasse, die das Schreiben ausführt, in Decorators ein, wodurch die Stile angewendet werden.

Der aufrufende Code muss nicht wissen, wie viele dieser Wrapper Sie um Ihren Text gelegt haben, sondern ruft lediglich eine Methode für das äußere Objekt auf und ruft den Stapel auf.

Für ein Pseudocode-Beispiel:

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;
}

Und so weiter, für jeden Dekorationsstil. In seiner einfachsten Verwendung kann man dann sagen

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

Und der Code, der diese Methode aufruft, muss nicht genau wissen, was er empfängt. Solange es ETWAS ist, das Text mit einer WriteString-Methode zeichnen kann.

pdr
quelle
Hmmm, ich werde mir das morgen noch einmal genauer ansehen und mich dann bei Ihnen melden. Danke für die Antwort.
Frog
Ich bin ein großer Fan des Decorator-Musters. Die Tatsache, dass dieses Muster HTML ähnelt (wobei die Kernoperation darin besteht, die Eingabezeichenfolge mit Tags zu versehen), ist eine Bestätigung, dass dieser Ansatz alle Anforderungen Ihres Schnittstellenbenutzers erfüllen kann. Beachten Sie, dass eine gute und benutzerfreundliche Benutzeroberfläche möglicherweise eine technisch komplizierte Implementierung aufweist. Wenn Sie beispielsweise den Text-Renderer selbst implementieren, müssen Sie möglicherweise TextWritermit beiden BoldDecoratorund ItalicDecorator(bidirektional) darüber sprechen , um die Aufgabe zu erledigen.
Rwong
Dies ist zwar eine schöne Antwort, wird die Frage der Operation jedoch nicht erneut gestellt? "kühne Bewerbung am" -> wie soll das gehen? Verwenden Sie Setter / Getter wie SetBold ()? Welches ist genau die Frage der op.
Mittwoch,
1
@stijn: Nicht wirklich. Es gibt eine Reihe von Möglichkeiten, diese Situation zu vermeiden. Sie können dies beispielsweise in einem Builder mit einer toggleStyle-Methode (enum-Argument) umschließen, die Dekoratoren zu einem Array hinzufügt und daraus entfernt und nur das letzte Element dekoriert, wenn Sie BuildDecoratedTextWriter aufrufen. Oder Sie können wie vorgeschlagen vorgehen und die Basisklasse komplizieren. Hängt von den Umständen ab und die OP war nicht spezifisch genug, um zu erraten, welche für ihn am besten ist. Er scheint klug genug zu sein, um es herauszufinden, sobald er das Muster hat.
pdr
1
Ich habe immer gedacht, dass das Dekorationsmuster eine Bestellung der Dekorationen impliziert. Betrachten Sie den gezeigten Beispielcode. Wie wird der ItalicDecorator entfernt? Trotzdem denke ich, dass so etwas wie Decorator der richtige Weg ist. Fett, kursiv und unterstrichen sind nicht die einzigen drei Stile. Es gibt dünne, halbfette, kondensierte, schwarze, breite, kursiv gedruckte, Kapitälchen usw. Möglicherweise müssen Sie eine Möglichkeit finden, einen beliebig großen Satz von Stilen auf einen Charakter anzuwenden.
Barry Brown
0

Ich würde mich zur zweiten Lösung neigen. Es sieht eleganter und flexibler aus. Obwohl es schwierig ist, sich andere Typen als fett, kursiv und unterstrichen (überstrichen?) Vorzustellen, wäre es schwierig, neue Typen mithilfe von Elementvariablen hinzuzufügen.

Ich habe diesen Ansatz in einem meiner letzten Projekte verwendet. Ich habe eine Klasse, die mehrere boolesche Attribute haben kann. Die Anzahl und Namen der Attribute können sich mit der Zeit ändern. Ich speichere sie in einem Wörterbuch. Wenn ein Attribut nicht vorhanden ist, gehe ich davon aus, dass sein Wert "false" ist. Ich muss auch eine Liste der verfügbaren Attributnamen speichern, aber das ist eine andere Geschichte.

Andrzej Bobak
quelle
1
Abgesehen von diesen Typen würde ich durchgestrichen, Schriftgröße, Schriftfamilie, hochgestellt, tiefgestellt und vielleicht später weitere hinzufügen. Aber warum haben Sie sich in Ihrem Projekt für diese Methode entschieden? Denken Sie nicht, dass es schmutziger Code ist, eine Liste zulässiger Attribute speichern zu müssen? Ich bin sehr gespannt, wie Ihre Antworten auf diese Fragen lauten!
Frog
Ich musste mich mit Situationen auseinandersetzen, in denen einige Attribute von Clients definiert werden konnten, als die Anwendung bereits implementiert war. Einige andere Attribute waren für einige Clients erforderlich, für die anderen jedoch veraltet. Mit diesem Ansatz konnte ich die Formulare und gespeicherten Datensätze anpassen und andere Probleme beheben. Ich denke nicht, dass es schmutzigen Code erzeugt. Manchmal können Sie einfach nicht vorhersagen, welche Informationen von Ihrem Kunden benötigt werden.
Andrzej Bobak
Mein Kunde muss jedoch keine benutzerdefinierten Attribute hinzufügen. Und in diesem Fall denke ich, dass es ein bisschen "hacky" aussieht. Stimmst du nicht zu
Frosch
Wenn Sie nicht mit der dynamischen Anzahl von Attributen konfrontiert sind, benötigen Sie keinen flexiblen Speicherplatz, um sie zu speichern. Das stimmt :) Dem anderen Teil stimme ich allerdings nicht zu. Das Speichern von Attributen in einem Dictionary / Hashmap / etc ist meiner Meinung nach kein schlechter Ansatz.
Andrzej Bobak
0

Ich denke, Sie überdenken das Thema.

styles.setBold(true) und styles.setItalic(false)sind OK

Tulains Córdova
quelle