Pregunta

Recientemente escribí una clase que representa curvas B-spline. Estas curvas están definidas por varios puntos de control. Originalmente, tenía la intención de usar ocho puntos de control, así que agregué una constante a la clase, así:

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

Ahora quiero extender esta clase para permitir una cantidad arbitraria de puntos de control. Entonces quiero cambiar esto a:

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

La pregunta es si, para empezar, no es mejor almacenar constantes en métodos para facilitar la adaptabilidad. En otras palabras, ¿no es mejor haber comenzado así:

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

La ventaja de esto es que podría haber cambiado un símbolo en el método en cuestión, en lugar de mover las constantes, etc.

¿Es esta una buena práctica o una mala?

¿Fue útil?

Solución

Normalmente, prefiero mantener el menor número posible de acoplamientos manualmente.

El número de puntos de control en la curva es, bueno, el número de puntos de control en la curva. No es una variable independiente que se puede establecer a voluntad.

Por lo tanto, normalmente expondría una referencia de contenedor estándar constante:

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

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

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

Y si desea saber cuántos puntos de control, use curve.getControlPoints (). size () . Sospecho que, en la mayoría de los casos de uso, querría los puntos y el recuento de todos modos, y al exponer un contenedor estándar puede usar los modismos de iterador de la biblioteca estándar y los algoritmos integrados, en lugar de obtener el recuento y llamar una función como getControlPointWithIndex en un bucle.

Si realmente no hay nada más en la clase de curva, incluso podría ir tan lejos como:

typedef std::vector<Point> Curve;

(a menudo una curva no se representará a sí misma, ya que una clase de representación puede tener detalles sobre la canalización de representación, dejando una curva como puramente el artefacto geométrico)

Otros consejos

int getControlPointCount() {return _controlPointCount;}

Este es un descriptor de acceso. Intercambiar una constante estática por un accesorio no es realmente una ganancia, como ha señalado Litb. Lo que realmente necesita para prepararse para el futuro es probablemente un par de descriptor de acceso y mutador.

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

También agregaría un diseño const para el descriptor de acceso y lo haría:

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

y el correspondiente:

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

Ahora, la gran diferencia con un objeto estático es que el recuento de puntos de control ya no es un atributo de nivel de clase sino un nivel de instancia uno. Este es un cambio de diseño . ¿Lo quieres así?

Nit: Su conteo estático a nivel de clase es public y, por lo tanto, no necesita un descriptor de acceso.

Para responder mejor a su pregunta, también se debe saber cómo se establece la variable controlPointCount. ¿Se encuentra fuera de tu clase? En este caso, también debe definir un setter. ¿O la clase Curve es la única responsable de configurarla? ¿Se establece solo en tiempo de compilación o también en tiempo de ejecución?

De todos modos, evite un número mágico incluso de esta forma:

int getControlPointCount() {return 8;}

Esto es mejor:

int getControlPointCount() {return CONTROL_POINT_COUNT;}

Un método tiene la ventaja de que puede modificar la implementación interna (usar un valor constante, leer desde un archivo de configuración, alterar el valor dinámicamente), sin afectar el externo de la clase.

class Curve
{   
    private:
        int _controlPointCount;

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

    public:      
        curve()
        {
            _controlPointCount = 8;
        }

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

Crearé un código como este, con la función set en privado, para que ningún cuerpo pueda jugar con el recuento de puntos de control, hasta que pasemos a la siguiente fase de desarrollo ... donde actualizamos, comenzamos a actualizar el recuento de puntos de control en tiempo de ejecución en ese momento, podemos mover este método establecido de ámbito privado a público.

Mientras entiendo la pregunta, tengo varios problemas conceptuales con el ejemplo:

  • ¿Cuál es el valor de retorno para getControlPointCount () cuando el número de puntos de control no está limitado?
    • ¿Es MAXINT?
    • ¿Es el número actual de puntos de control en la curva (rompiendo así la lógica que dice que este es el mayor número posible de puntos?)
  • ¿Qué sucede cuando realmente intentas crear una curva con puntos MAXINT? Eventualmente se quedará sin memoria.

La interfaz en sí misma me parece problemática. Al igual que otras clases de colección estándar, la clase debería haber encapsulado su limitación en el número de puntos, y su AddControlPoint () debería haber devuelto un error si se ha producido una limitación en el tamaño, la memoria o cualquier otra violación.

En cuanto a la respuesta específica, estoy de acuerdo con kgiannakakis: una función miembro permite más flexibilidad.

Tiendo a usar la configuración + constante (valor predeterminado) para todos los valores 'estables' a través de la ejecución del programa. Con constantes simples para valores que no pueden cambiar (360 grados - > 2 pi radianes, 60 segundos - > 1 minuto) o cuyo cambio rompería el código de ejecución (valores mínimos / máximos para algoritmos que los hacen inestables).

Usted está lidiando con algunos problemas de diseño diferentes. Primero debe saber si el número de puntos de control es un valor de nivel de clase o instancia. Entonces, si es una constante en cualquiera de los dos niveles.

Si todas las curvas deben compartir el mismo número de puntos de control en su aplicación, entonces es un valor de nivel de clase (estático). Si diferentes curvas pueden tener un número diferente de puntos de control, entonces no es un valor de nivel de clase, sino un nivel de instancia uno.

En este caso, si el número de puntos de control será constante durante toda la vida de la curva, entonces es una constante de nivel de instancia, si puede cambiar, entonces tampoco es constante en este nivel.

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

Para el tipo integral que uso habitualmente:

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

Si la constante no necesita ninguna entidad, excepto la implementación de la clase, declaro constantes en el archivo * .cpp.

namespace
{
const int CONTROL_POINT_COUNT = 8;
}

En general, todos sus datos deben ser privados y accesibles a través de getters y setters. De lo contrario, viola la encapsulación. Es decir, si expone los datos subyacentes, se bloquea a sí mismo y a su clase en una representación particular de esos datos subyacentes.

En este caso específico, habría hecho algo como lo siguiente, creo:

class Curve
{

   protected:

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

   private:

      static int _controlPointCount = 0;
};

Las constantes en general no deben definirse dentro de los métodos. El ejemplo que está eligiendo tiene dos características únicas. Primero, es un captador; segundo, el tipo que se devuelve es un int. Pero el punto de definir constantes es usarlas más de una vez y poder hacer referencia a ellas de una manera conveniente. Mecanografía '' 8 '' en oposición a " controlPointCount " puede ahorrarle tiempo y puede parecer que no tiene un costo de mantenimiento, pero esto no suele ser cierto si siempre define constantes dentro de los métodos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top