Est-il préférable de stocker des constantes de classe dans des membres de données ou dans des méthodes?

StackOverflow https://stackoverflow.com/questions/647648

  •  22-07-2019
  •  | 
  •  

Question

J'ai récemment écrit une classe qui rend les courbes B-spline. Ces courbes sont définies par un certain nombre de points de contrôle. À l'origine, j'avais l'intention d'utiliser huit points de contrôle. J'ai donc ajouté une constante à la classe, comme suit:

class Curve
{
   public:
      static const int CONTROL_POINT_COUNT = 8;
};

Je souhaite maintenant étendre cette classe pour autoriser une quantité arbitraire de points de contrôle. Donc, je veux changer ceci en:

class Curve
{
   public:
      int getControlPointCount() {return _controlPointCount;}
};

La question est de savoir s'il n'est pas préférable de stocker des constantes dans les méthodes pour faciliter l'adaptabilité. En d’autres termes, n’est-il pas préférable d’avoir commencé ainsi:

class Curve
{
   public:
      int getControlPointCount() {return 8;}
};

L'avantage de ceci est que j'aurais pu changer un symbole dans la méthode en question, au lieu de me déplacer entre constantes, etc.

Est-ce une bonne ou une mauvaise pratique?

Était-ce utile?

La solution

Je préfère généralement maintenir le moins de couplages possible manuellement.

Le nombre de points de contrôle dans la courbe est, ainsi, le nombre de points de contrôle dans la courbe. Ce n'est pas une variable indépendante qui peut être définie à volonté.

J'exposais donc généralement une référence de conteneur standard const:

class Curve
{   
    private:
        std::vector<Point>& _controlPoints;

    public:      
        Curve ( const std::vector<Point>& controlPoints) : _controlPoints(controlPoints)
        {
        }

        const std::vector<Point>& getControlPoints ()
        {
            return _controlPoints;
        }
};

Et si vous voulez savoir combien de points de contrôle, utilisez curve.getControlPoints (). size () . Je soupçonne que dans la plupart des cas d'utilisation, vous voudriez quand même avoir les points et le nombre. En exposant un conteneur standard, vous pouvez utiliser les idiomes d'itérateur et les algorithmes intégrés de la bibliothèque standard, plutôt que d'obtenir le nombre et l'appel. une fonction comme getControlPointWithIndex dans une boucle.

S'il n'y a vraiment rien d'autre dans la classe de courbe, je pourrais même aller aussi loin que:

typedef std::vector<Point> Curve;

(souvent une courbe ne se rendra pas d'elle-même, car une classe de rendu peut avoir des détails sur le pipeline de rendu, laissant ainsi une courbe purement un artefact géométrique)

Autres conseils

int getControlPointCount() {return _controlPointCount;}

Ceci est un accesseur. L'échange d'une constante statique contre un accesseur n'est pas vraiment un gain, comme l'a souligné litb. Ce dont vous avez vraiment besoin pour être à l’avenir , c’est probablement une paire d’accesseurs et de mutateurs.

int getControlPointCount() {return _controlPointCount;} // accessor

Je jetterais également un design-const pour l'accesseur et le ferais:

int getControlPointCount() const {return _controlPointCount;} // accessor

et le correspondant:

void setControlPointCount(int cpc) { _controlPointCount = cpc;} //mutator

Maintenant, la grande différence avec un objet statique est que le nombre de points de contrôle n'est plus un attribut de niveau classe, mais un niveau d'instance un. Ceci est une modification de conception . Le voulez-vous de cette façon?

Nit: Votre compte statique au niveau classe est public et n'a donc pas besoin d'un accesseur.

Pour mieux répondre à votre question, vous devez également savoir comment la variable controlPointCount est définie. Est-ce que ça se passe à l'extérieur de votre classe? Dans ce cas, vous devez également définir un setter. Ou la classe Curve est-elle la seule responsable de son paramétrage? Est-il défini uniquement au moment de la compilation ou également à l'exécution?

Quoi qu’il en soit, évitez les nombres magiques même sous cette forme:

int getControlPointCount() {return 8;}

C'est mieux:

int getControlPointCount() {return CONTROL_POINT_COUNT;}

Une méthode présente l’avantage de pouvoir modifier l’implémentation interne (utiliser une valeur constante, lire dans un fichier de configuration, modifier la valeur de manière dynamique), sans affecter l’externe de la classe.

class Curve
{   
    private:
        int _controlPointCount;

        void setControlPointCount(int cpc_arg)
        {
            _controlPointCount = cpc_arg;
        }

    public:      
        curve()
        {
            _controlPointCount = 8;
        }

        int getControlPointCount() const
        {
            return _controlPointCount;
        }
};

Je vais créer un code comme celui-ci, avec la fonction set en privé, de sorte qu'aucun corps ne puisse jouer avec le nombre de points de contrôle, jusqu'à ce que nous passions à la phase suivante du développement .. où nous mettons à jour le point de mise à jour runtime. à ce moment, nous pouvons déplacer cette méthode set du domaine privé au domaine public.

Tout en comprenant la question, l’exemple me pose un certain nombre de problèmes conceptuels:

  • Quelle est la valeur de retour pour getControlPointCount () lorsque le nombre de points de contrôle n'est pas limité?
    • Est-ce MAXINT?
    • Est-ce le nombre actuel de points de contrôle sur la courbe (brisant ainsi la logique qui dit qu'il s'agit du plus grand nombre possible de points?)
  • Que se passe-t-il lorsque vous essayez de créer une courbe avec des points MAXINT? Vous allez finir par manquer de mémoire.

L’interface elle-même me semble problématique. Comme d'autres classes de collection standard, la classe devrait avoir encapsulé sa limitation sur le nombre de points et son AddControlPoint () aurait dû renvoyer une erreur si une limitation de taille, de mémoire ou toute autre violation s'est produite.

En ce qui concerne la réponse spécifique, je suis d’accord avec kgiannakakis: une fonction membre permet plus de flexibilité.

J'ai tendance à utiliser configuration + constante (valeur par défaut) pour toutes les valeurs "stables" lors de l'exécution du programme. Avec des constantes simples pour les valeurs qui ne peuvent pas changer (360 degrés - 2 pi radians, 60 secondes -> 1 minute) ou dont la modification romprait le code en cours d'exécution (valeurs minimales / maximales pour les algorithmes les rendant instables).

Vous avez affaire à différents problèmes de conception. Vous devez d’abord savoir si le nombre de points de contrôle est une valeur de niveau classe ou instance. Puis si c'est une constante à l'un des deux niveaux.

Si toutes les courbes doivent partager le même nombre de points de contrôle dans votre application, il s'agit d'une valeur de niveau classe (statique). Si différentes courbes peuvent avoir un nombre différent de points de contrôle, il ne s'agit pas d'une valeur de niveau de classe, mais d'une instance de niveau un.

Dans ce cas, si le nombre de points de contrôle est constant pendant toute la durée de vie de la courbe, il s'agit d'une constante de niveau d'instance. S'il peut changer, il n'est pas constant à ce niveau.

// Assuming that different curves can have different 
// number of control points, but that the value cannot 
// change dynamically for a curve.
class Curve
{
public:
   explicit Curve( int control_points )
      : control_points_( control_points )
   {}
   // ...
private:
   const int control_points_;
};

namespace constant
{
   const int spline_control_points = 8;
}
class Config
{
public:
   Config();
   void readFile( std::string const & file );

   // returns the configured value for SplineControlPoints or
   // constant::spline_control_points if the option does not 
   // appear in config.
   int getSplineControlPoints() const;
};

int main()
{
   Config config;
   config.readFile( "~/.configuration" ); // read config

   Curve c( config.getSplineControlPoints() );
}

Pour le type intégral, j'utilise habituellement:

class Curve
{
   public:
      enum 
      {
          CONTROL_POINT_COUNT = 8
      };
};

Si constant ne nécessite aucune entité, à l'exception de l'implémentation de la classe, je déclare les constantes dans le fichier * .cpp.

namespace
{
const int CONTROL_POINT_COUNT = 8;
}

En général, toutes vos données doivent être privées et accessibles via des accesseurs et des setters. Sinon, vous violez l'encapsulation. En d'autres termes, si vous exposez les données sous-jacentes, vous vous enfermerez, vous et votre classe, dans une représentation particulière de ces données.

Dans ce cas précis, j'aurais fait quelque chose comme ce qui suit, je pense:

class Curve
{

   protected:

      int getControlPointCount() {return _controlPointCount;}
      int setControlPointCount(int c) { _controlPointCount = c; }

   private:

      static int _controlPointCount = 0;
};

Les constantes en général ne doivent pas être définies dans les méthodes. L'exemple que vous choisissez présente deux caractéristiques uniques. D'abord, c'est un getter; Deuxièmement, le type retourné est un int. Mais le but de la définition des constantes est de les utiliser plus d'une fois et de pouvoir s'y référer facilement. Dactylographie " 8 " par opposition à " controlPointCount " Cela peut vous faire gagner du temps et ne pas entraîner de coût de maintenance, mais cela ne sera généralement pas vrai si vous définissez toujours des constantes dans des méthodes.

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