Question

, le motif de getter / setter Si je ne me trompe à fond est un modèle couramment utilisé pour deux choses:

  1. Pour faire une variable privée afin qu'il puisse être utilisé, mais jamais modifié, en ne fournissant une méthode getVariable (ou, plus rarement, seulement modifiable, en ne fournissant une méthode de setVariable).
  2. Pour vous assurer que, dans l'avenir, si vous arrive d'avoir un problème auquel une bonne solution serait tout simplement de traiter la variable avant qu'elle va dans et / ou hors de la classe, vous pouvez traiter la variable en utilisant une mise en œuvre effective sur les méthodes getter et setter au lieu de simplement revenir ou le réglage des valeurs. De cette façon, le changement ne se propage pas au reste du code.

Question n ° 1: Est-ce que je manque de toute utilisation accesseurs ou sont l'un de mes hypothèses incorrectes? Je ne sais pas si je ne me trompe pas sur ceux-ci.

Question n ° 2: Y at-il une sorte de bonté modèle qui peut me empêcher d'avoir à écrire les accesseurs pour mes variables membres? Je ne trouve pas.

Question 3: Est-ce que le modèle de classe suivante est un bon moyen de mettre en œuvre un getter sans avoir à écrire réellement le accesor

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

Il semble juste pour moi, et je ne pouvais même réimplémenter la logique de get en utilisant une autre supercherie de spécialisation de modèle partiel. La même chose peut être appliqué à une sorte de poseur ou même des modèles de classe GetterSetter.

Que pensez-vous?

Était-ce utile?

La solution

Alors que la solution est soignée du point de vue de la mise en œuvre, architectually, il est seulement à mi-chemin. Le point de getter / setter modèle est de donner les clas contrôle sur les données de elle et de diminuer le couplage (à savoir les autres classes connaissant comment les données sont stockées). Cette solution permet d'obtenir le premier, mais pas tout à fait celle-ci.

En fait, l'autre classe doit maintenant savoir deux choses - le nom de la variable et la méthode sur le getter (à savoir .get()) au lieu d'un - par exemple getWidth(). Cela provoque un couplage accru.

Cela dit, il est fractionnement des poils architecturaux proverbiales. Peu importe tant que ça à la fin de la journée.

EDIT OK, maintenant chie et fou rire, voici une version du getter à l'aide des opérateurs, de sorte que vous ne devez pas faire .value ou .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
}

EDIT fixe opérateur d'affectation hardcoded. Cela devrait fonctionner assez bien si le type lui-même un opérateur d'affectation. Par défaut struct ont ceux donc pour les simples, il devrait fonctionner hors de la boîte.

Pour les classes plus complexes, vous aurez besoin de mettre en œuvre un opérateur d'affectation qui est assez juste. Avec RVO et Copier sur optimisations Ecrire , cela devrait être raisonnablement efficace au moment de l'exécution.

Autres conseils

FWIW ici sont mes opinions sur vos questions:

  1. En général, le fait est qu'il existe une logique commerciale ou d'autres contraintes appliquées dans le setter. Vous pouvez également avoir calculé ou des variables virtuelles par le découplage de la variable d'instance avec des méthodes accesseurs.
  2. Pas que je sache. Les projets que j'ai travaillé ont eu une famille de macros C pour éradiquer de telles méthodes
  3. Oui; Je pense que c'est assez propre. Je crains que ça ne vaut pas la peine, il vous reste plus qu'à embrouiller les autres développeurs (un concept plus dont ils ont besoin pour tenir dans leur tête) et ne sauve pas beaucoup sur l'abattage sanitaire telles méthodes manuellement.

Depuis Igor Zevaka a publié une version de cela, je posterai un j'ai écrit il y a longtemps. Ce chiffre est légèrement différent - j'ai observé au moment où plus utilisation réelle de paires get / set (qui fait quoi que ce soit a fait) était d'appliquer la valeur d'un séjour variable dans une plage prédéterminée. Ceci est un peu plus vaste, comme l'ajout des opérateurs d'E / S, où l'extracteur applique encore la plage définie. Il a également un peu de code test / d'exercice pour montrer l'idée générale de ce qu'il fait et comment il le fait:

#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. Vous pouvez également utiliser une méthode de type getter ou setter pour obtenir ou définir des valeurs calculables, bien les propriétés sont utilisés à d'autres langues comme C #

  2. Je ne peux pas penser de façon raisonnable de faire abstraction du cadre et d'obtenir un nombre inconnu de valeurs / propriétés.

  3. Je ne suis pas assez familier avec le standard C ++ bœuf pour commenter.

Cela peut être exagéré dans ce cas, mais vous devriez vérifier l'idiome avocat / client pour une utilisation judicieuse de l'amitié. Avant de trouver cet idiome, j'évitais amitié tout à fait.

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

Et maintenant la question, et si vous avez besoin d'un setter ainsi.

Je ne sais pas pour vous, mais je tendance à avoir (à peu près) deux types de classes:

  • classe pour la logique
  • blobs

Les blobs sont des collections tout en vrac de toutes les propriétés d'un objet métier. Par exemple, un Person aura un surname, firstname, plusieurs adresses, plusieurs professions ... donc Person ne peut pas avoir une logique.

Pour les blobs, je tends à utiliser l'attribut privé canonique + getter + setter, car il fait abstraction de la mise en œuvre effective du client.

Cependant, bien que votre modèle (et son évolution par Igor Zeveka) sont vraiment gentils, ils ne traitent pas le problème de réglage et ils ne traitent pas compatibilité binaire problèmes.

Je suppose que je serais probablement recourir à des macros ...

Quelque chose comme:

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

Ce qui serait utilisé comme:

// 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

Avec un peu de magie préprocesseur cela fonctionnerait très bien!

#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)

D'accord, pas de type sûr, et tout, mais:

  • il est un ensemble raisonnable de macro je pense
  • il est facile à utiliser, l'utilisateur ne jamais avoir à vous soucier de 2 macros après tout, comme si les modèles erreurs pourraient obtenir poilue
  • utilisation de boost.call_traits pour l'efficacité (const & / choix de valeur)
  • il y a plus de fonctionnalités il: getter / setter duo

  • il est, malheureusement, un ensemble de macros ... et ne se plaindra pas si jamais vous

  • il fait des ravages sur les accesseurs (public, protégé, privé) il est donc préférable de ne pas le INTERSPED dans toute la classe

Voici l'exemple canonique alors:

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:

};

Vous résoudre le mauvais problème. Dans une application bien conçue, accesseurs doivent être rare , et non automatisé. Une classe significative fournit une sorte de abstraction . Il est non seulement une collection de membres, il modélise un concept qui est plus que la somme de ses variables membres. Et il ne généralement même pas de sens d'exposer les membres individuels.

Une classe doit exposer les opérations qui ont du sens sur le concept modélise. La plupart des variables membres sont là pour maintenir cette abstraction, pour stocker l'état que vous avez besoin. Mais il faut généralement pas accessible directement. Voilà pourquoi il est membre privé de la classe en premier lieu .

Au lieu de trouver des moyens plus faciles à écrire car.getFrontLeftWheel(), demandez-vous pourquoi l'utilisateur de la classe aurait besoin la roue avant gauche en premier lieu. Avez-vous habituellement manipuler cette roue directement lors de la conduite? La voiture est censé pour vous de prendre soin de toutes les affaires roue de filature, est-ce pas?

C'est là que je pense #defines sont encore utiles.

La version du modèle est complexe et difficile à comprendre - définir la version est évidente

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

class Window
{
    Getter(int, height);
}

Je suis sûr que j'ai la syntaxe un peu mal - mais vous obtenez le point

.

S'il y avait un ensemble bien connu des modèles, disons, stimuler alors je les utiliser. Mais je ne serais pas écrire mon propre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top