Frage

Wenn ich nicht gründlich irre, die Getter / Setter-Muster ist ein gemeinsames Muster für zwei Dinge verwendet:

  1. Um eine private Variable zu machen, so dass es verwendet werden kann, aber niemals verändert, indem nur ein getVariable Verfahren bereitgestellt wird (oder, seltener, nur veränderbar, indem nur ein setVariable Verfahren bereitgestellt wird).
  2. Um sicherzustellen, dass in der Zukunft, wenn Sie geschehen, ein Problem zu haben, auf das eine gute Lösung einfach sein würde, um die Variable zu behandeln, bevor es in und / oder aus der Klasse geht, können Sie die Variable behandeln, indem Sie eine tatsächliche Implementierung auf dem Getter und Setter Methoden anstatt einfach zurückkehren oder die Werte zu setzen. Auf diese Weise wird die Änderung auf den Rest des Codes nicht ausbreiten.

Frage 1: Fehle ich jegliche Verwendung von Accessoren oder sind alle meine Annahmen falsch? Ich bin mir nicht sicher, ob ich auf diejenigen richtig bin.

Frage 2: Gibt es irgendeine Art von Vorlage Güte, die mich von mit schreiben, um die Zugriffsmethoden für meine Membervariablen halten können? Ich habe nicht finden.

Frage 3: Wird die folgende Klasse-Vorlage einen guten Weg, um einen Getter umzusetzen, ohne tatsächlich die Accesor schreiben

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    template <typename ... Args>
    Getter(Args args) : value(args ...) {} // Uses C++0x

    T get() { return value; }

protected:
    T value;
};

class Window
{
public:
    Getter<uint32_t,Window> width;
    Getter<uint32_t,Window> height;

    void resize(uint32_t width,uint32_t height)
    {
        // do actual window resizing logic

        width.value = width; // access permitted: Getter befriends Window
        height.value = height; // same here
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method

    // This works: Getter::get() is public
    std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";

    // This doesn't work: Getter::value is private
    win.width.value = 640;
    win.height.value = 480;
}

Es scheint mir fair, und ich konnte sogar unter Verwendung eines anderen Teil Template-Spezialisierung Tricks die get Logik neu implementieren. Das gleiche kann auf eine Art Setter oder auch GetterSetter Klassenvorlagen angewendet werden.

Was denken Sie?

War es hilfreich?

Lösung

Während die Lösung aus der Umsetzung Sicht ordentlich ist, architektonisch, es ist nur auf halbem Weg. Der Punkt des Getters / Setter Muster ist das clas über die sie Daten steuern zu geben und (zu wissen, das heißt andere Klasse Wie Daten gespeichert) Kopplung zu verringern. Diese Lösung löst die früheren, aber nicht ganz das letztere.

In der Tat jetzt die andere Klasse hat zwei Dinge zu wissen - den Namen der Variablen und das Verfahren auf dem Getter (d .get()) anstelle einen - z getWidth(). Dies bewirkt eine erhöhte Kopplung.

Nachdem alles gesagt, dass ist diese Spaltung sprichwörtlich Architektur Haare. Es spielt keine Rolle, dass alle viel am Ende des Tages.

Bearbeiten OK, jetzt scheißt und kichert, hier ist eine Version der Getter Operatoren, also müssen Sie nicht tun .value oder .get()

template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
    typedef T Type;
};

template <typename T,class Owner>
class Getter
{
public:
    friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter

    operator T()
    {
        return value;
    }

protected:
    T value;

    T& operator=( T other )
    {
       value = other;
       return value;  
    }


};

class Window
{
public:
    Getter<int,Window> _width;
    Getter<int,Window> _height;

    void resize(int width,int height)
    {
        // do actual window resizing logic
        _width = width; //using the operator
        _height = height; //using the operator
    }
};

void someExternalFunction()
{
    Window win;

    win.resize(640,480); // Ok: public method
    int w2 = win._width; //using the operator
    //win._height = 480; //KABOOM
}

Bearbeiten Fixed hartkodierte Zuweisungsoperator. Dies sollte recht gut funktionieren, wenn der Typ selbst einen Zuweisungsoperator hat. Standardmäßig structs haben diejenigen so für einfache diejenigen sollte es aus dem Kasten heraus arbeiten.

Für komplexere Klassen benötigen Sie einen Zuweisungsoperator zu implementieren, die fair genug ist. Mit RVO und Copy on Write Optimierungen, sollte dies einigermaßen effizient zur Laufzeit sein.

Andere Tipps

FWIW hier sind meine Meinungen auf Ihre Fragen:

  1. Typischerweise ist der Punkt ist, dass es Geschäftslogik oder andere Einschränkungen in dem Setter erzwungen ist. Sie können auch oder virtuelle Variablen berechnet durch Entkopplung der Instanzvariable mit Accessormethoden.
  2. Nicht, dass ich wüsste. Projekte, die ich gearbeitet habe haben eine Familie von C-Makros hatte auszumerzen solche Methoden
  3. Ja; Ich denke, das ist ziemlich ordentlich. Ich mache mir Sorgen nur, dass es nicht der Mühe wert, es wird nur andere Entwickler verwirren (ein weiteres Konzept, das sie in ihrem Kopf passen müssen) und spart nicht viel über manuell solche Methoden auszumerzen.

Seit Igor Zevaka veröffentlicht eine Version davon, ich werde ein Post schrieb ich vor langer Zeit. Das ist etwas anders - ich zu der Zeit beobachtet, dass die meisten real Verwendung von get / set-Paare (das tatsächlich tat nichts) war der Wert einer Variablen Bleiben innerhalb eines vorbestimmten Bereichs zu erzwingen. Dies ist ein etwas umfangreicher, wie das Hinzufügen von E / A-Operatoren, wo Extraktor noch den definierten Bereich erzwingt. Es hat auch ein bisschen Test / Übungs Code die allgemeine Vorstellung von dem zu zeigen, was sie tut und wie sie es tut:

#include <exception>
#include <iostream>
#include <functional>

template <class T, class less=std::less<T> >
class bounded {
    const T lower_, upper_;
    T val_;

    bool check(T const &value) {
        return less()(value, lower_) || less()(upper_, value);
    }

    void assign(T const &value) {
        if (check(value))
            throw std::domain_error("Out of Range");
        val_ = value;
    }

public:
    bounded(T const &lower, T const &upper) 
        : lower_(lower), upper_(upper) {}

    bounded(bounded const &init) 
        : lower_(init.lower), upper_(init.upper)
    { 
        assign(init); 
    }

    bounded &operator=(T const &v) { assign(v);  return *this; }

    operator T() const { return val_; }

    friend std::istream &operator>>(std::istream &is, bounded &b) {
        T temp;
        is >> temp;

        if (b.check(temp))
            is.setstate(std::ios::failbit);
        else
            b.val_ = temp;
        return is;
    }
};


#ifdef TEST

#include <iostream>
#include <sstream>

int main() {
    bounded<int> x(0, 512);

    try {
        x = 21;
        std::cout << x << std::endl;

        x = 1024;
        std::cout << x << std::endl;
    }

    catch(std::domain_error &e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    std::stringstream input("1 2048");
    while (input>>x)
        std::cout << x << std::endl; 

    return 0;
}

#endif
  1. Sie können auch einen Getter oder Setter-Typ-Methode verwenden, zu erhalten oder setzen berechenbare Werte, sehr die Art und Weise Eigenschaften in anderen Sprachen wie C # verwendet werden

  2. ich nicht angemessener Weise zu abstrakt denken kann, die immer und Einstellung einer unbekannten Anzahl von Werten / Eigenschaften.

  3. Ich bin nicht vertraut genug mit der C ++ ox Standard Stellung zu nehmen.

Dies kann in diesem Fall zu viel des Guten, aber Sie sollten den Anwalt / Client-Idiom für vernünftige Freundschaft Nutzung angeboten werden. Bevor dieses Idiom zu finden, vermied ich Freundschaft zusammen.

http://www.ddj.com/cpp/184402053/

Und nun die Frage, und was, wenn Sie auch einen setter benötigen.

Ich weiß nicht, über Sie, aber ich neige (grob) zwei Arten von Klassen haben:

  • Klasse für die Logik
  • Blobs

Die Blobs sind nur lose Sammlungen aller Eigenschaften eines Business-Objekt. Zum Beispiel kann ein Person eine surname, firstname, mehrere Adressen, mehrere Berufe haben ... so kann Person nicht Logik haben.

Für die Blobs, neige ich dazu, das kanonische private Attribut + Getter + Setter zu verwenden, da sie die tatsächliche Umsetzung des Client abstrahiert.

Doch obwohl die Vorlage (und seine Entwicklung von Igor Zeveka) wirklich schön sind, sie befassen sich nicht mit der Einstellung Problem und sie befassen sich nicht mit Binärkompatibilität Fragen.

Ich glaube, ich würde wahrscheinlich zu Makros zurückgreifen ...

So etwas wie:

// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**

// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
   public: boost::call_traits<Type>::const_reference Name() const

#define DEFINE_VALUE_GETTER(Object, Name)\
   boost::call_traits<Name##_type>::const_reference Object::Name ()const\
   { return m_##Name; }

#define DECLARE_VALUE_SETTER(Object, Type, Name)\
   public: Type& Name();\
   public: Object& Name(boost::call_traits<Type>::param_type i);

#define DEFINE_VALUE_SETTER(Object, Name)\
   Name##_type& Object::Name() { return m_##Name; }\
   Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
   { m_##Name = i; return *this; }

Welche verwendet werden würde, wie:

// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));

// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic

Window& Window::width(int i) // Always seems a waste not to return anything!
{ 
  if (i < 0) throw std::logic_error();
  m_width = i;
  return *this;
} // Window::width

Mit etwas Präprozessor Magie wäre es ganz gut funktionieren!

#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>

#define DECLARE_VALUE_ITER(r, data, elem)\
  DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )

#define DEFINE_VALUE_ITER(r, data, elem)\
  DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )

#define DECLARE_VALUE(Object, Type, Name, Seq)\
   public: typedef Type Name##_type;\
   private: Type m_##Name;\
   BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)

#define DEFINE_VALUE(Object, Name, Seq)\
   BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)

Okay, nicht geben Sie sicher, und alles, aber:

  • es ist eine vernünftige Reihe von Makro Ich denke
  • es ist einfach zu bedienen, der Benutzer immer nur hat etwa 2 Makros nach kümmern, obwohl wie Vorlagen könnten die Fehler bekommen behaart
  • Verwendung von boost.call_traits für Effizienz (const & / Wert Wahl)
  • gibt es mehr Funktionalität gibt: Getter / Setter Duo

  • Es ist leider eine Reihe von Makros ... und wird sich nicht beschweren, wenn Sie jemals

  • es verheerende Auswirkungen auf den Zugriffs- (öffentlich, geschützt, privat), so ist es am besten nicht in der gesamten Klasse
  • Intersped

Hier ist das kanonische Beispiel dann:

class Window
{
  // Best get done with it
  DECLARE_VALUE(Window, int, width, (GETTER));
  DECLARE_VALUE(Window, int, height, (GETTER));

// don't know which is the current access level, so better define it
public:

};

Sie lösen das falsche Problem. In einem gut gestalteten Anwendung, Getter und Setter sollte selten , nicht automatisiert. Eine sinnvolle Klasse stellt eine Art von Abstraktion . Es ist nicht einfach eine Sammlung von Mitgliedern, es Modelle ein Konzept, das mehr als nur die Summe seiner Mitgliedsvariablen ist. Und es ist normalerweise nicht einmal Sinn machen, die einzelnen Mitglieder zu belichten.

Es sollte eine Klasse, um die Operationen aussetzen, die sinnvoll auf dem Konzept, dass es Modelle. Die meisten Mitgliedsvariablen gibt es diese Abstraktion zu halten, um den Zustand zu speichern, die Sie benötigen. Aber es soll in der Regel nicht direkt zugegriffen werden. Deshalb ist es ein privates Mitglied der Klasse in erster Linie .

Anstatt einfache Wege zu finden, car.getFrontLeftWheel() zu schreiben, fragen Sie sich, warum der Benutzer der Klasse immer das vordere linke Rad in erster Linie brauchen würde. Sie manipulieren Sie in der Regel direkt das Rad während der Fahrt? Das Auto soll kümmern sich um alle Rad-Spinnen Geschäft für Sie, nicht wahr?

Dies ist, wo ich denke, #defines noch nützlich sind.

Die Template-Version ist kompliziert und schwer zu verstehen - die definiert Version liegt auf der Hand

#define Getter(t, n)\
     t n;\
     t get_##n() { return n; }

class Window
{
    Getter(int, height);
}

Ich bin sicher, ich habe die Syntax etwas falsch - aber Sie erhalten den Punkt

.

Wenn es eine bekannte Reihe von Vorlagen in war, sagen wir, steigern, dann würde ich sie nutzen. Aber ich würde meine eigenen nicht schreiben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top