¿Es mejor permitir que una función lance o lance el constructor?

Creo que es más fácil explicarlo con un ejemplo. Tomemos una clase que modele la velocidad de un automóvil de Fórmula 1, la interfaz puede verse así:

class SpeedF1
{
public:
  explicit SpeedF1(double speed);
  double getSpeed() const;
  void setSpeed(double newSpeed);
  //other stuff as unit 
private:
  double speed_;
};

Ahora bien, las velocidades negativas no son significativas en este caso particular y tampoco valores superiores a 500 km / h. En este caso, el constructor y la función setSpeed ​​pueden generar excepciones si el valor proporcionado no está dentro de un rango lógico.

Puedo introducir una capa adicional de abstracción e insertar un objeto adicional en lugar de duplicar. El nuevo objeto será un envoltorio alrededor del doble y se construirá y nunca se modificará. La interfaz de la clase se cambiará a:

class ReasonableSpeed
{
public:
  explicit ReasonableSpeed(double speed); //may throw a logic error
  double getSpeed() const;
  //no setter are provide
private:
  double speed_;
};

class SpeedF1
{
public:
  explicit SpeedF1(const ReasonableSpeed& speed);
  ReasonableSpeed getSpeed() const;
  void setSpeed(const ReasonableSpeed& newSpeed);
  //other stuff as unit 
private:
  ReasonableSpeed speed_;
};

Con este diseño, SpeedF1 no puede lanzar, sin embargo, necesito pagar más a un constructor de objetos cada vez que quiero restablecer la velocidad.

Para las clases en las que un conjunto limitado de valores es razonable (por ejemplo, los meses en un calendario), normalmente hago que el constructor sea privado y proporciono un conjunto completo de funciones estáticas. En este caso es imposible, otra posibilidad es implementar un patrón de objeto nulo pero no estoy seguro de que sea superior simplemente lanzar una excepción.

Finalmente, mi pregunta es:

¿Cuál es la mejor práctica para resolver este tipo de problema?

preguntado el 27 de agosto de 11 a las 15:08

¿Una clase que represente la velocidad del automóvil? ¿Qué intentas encapsular? Además, no aplique cosas que no sean física / matemáticamente imposibles, como speed < 500km/halguien va a construir construido un coche supersónico y te presentará un error. -

la clase fue solo un ejemplo, como dije en la primera línea, piense en cualquier clase que acepte un número real entre dos valores. El porcentaje es otro ejemplo, es un número real entre 0 y 100. -

5 Respuestas

En primer lugar, no sobreestime el costo del constructor adicional. De hecho, este costo debería ser exactamente el costo de inicializar un double más el costo de la verificación de validez. En otras palabras, es probable igual a usar un crudo double.

En segundo lugar, pierde al armador. Los setters y, en menor grado, los getters, casi siempre son anti-patrones. Si necesita establecer una nueva velocidad (máxima), es probable que realmente desee un automóvil nuevo.

Ahora, sobre el problema real: un constructor de lanzamiento está completamente bien en principio. No escriba código complicado para evitar tal construcción.

Por otro lado, también me gusta la idea de los tipos de autocomprobación. Esto hace el mejor uso del sistema de tipos C ++ y estoy a favor de eso.

Ambas alternativas tienen sus ventajas. Cuál es el mejor realmente depende de la situación exacta. En general, trato de explotar el sistema de tipos y la verificación de tipos estáticos tanto como sea posible. En su caso, esto significaría tener un tipo adicional para la velocidad.

Respondido 28 ago 11, 12:08

"pierde el setter ... es probable que realmente quieras un auto nuevo" Ese es un enfoque bastante funcional para ver las cosas, pero no estoy seguro de si el uso liberal de este estilo en C ++ realmente no tiene efectos adversos en el rendimiento. - kizzx2

@Konrad Rudolph "pierde al armador. Los armadores casi siempre son anti-patrones". Me gustaría aprender más sobre esto, ¿tiene algún buen enlace (o libro) sobre este tema? - Alessandro Teruzzi

@Alessandro Felleisen tiene una charla sobre la enseñanza de este tipo de diseño ccs.neu.edu/home/matthias/Presentations/FDPE2005/htdch.pdf y un borrador de libro. ccs.neu.edu/home/matthias/htdc.html - Antonakos

"Si necesita establecer una nueva velocidad, es probable que realmente desee un automóvil nuevo". ¿Seriamente? Probablemente soy el programador más "funcional" de mi oficina, e incluso creo que eso suena loco. Puedo cambiar de velocidad en mi auto actual sin comprar uno nuevo; ¿Por qué debería ser diferente el objeto que representa mi automóvil? - Nemo

@Nemo Estoy de acuerdo, "lo más probable es que quieras un coche nuevo" es ridículo. Para agregar un poco a la respuesta, también diría que si las personas ven algo en el constructor, saben que construyeron mal el objeto. Pero si se produce alguna otra función miembro, no está claro si la construyeron mal porque es posible que simplemente hayan usado la función que arrojó mal. - Seth Carnegie

Voto firmemente por la segunda opción. Esta es solo mi opinión personal sin mucho respaldo académico. Mi experiencia es que configurar un sistema "puro" que opere solo con datos válidos hace que su código sea mucho más limpio. Esto se puede lograr utilizando su segundo enfoque, que garantiza que solo ingresen datos válidos al sistema.

Si su sistema crece, puede encontrar que ReasonableSpeed se usa en muchos lugares (use su discreción, pero es probable que las cosas se reutilicen bastante). El segundo enfoque le ahorrará muchos códigos de verificación de errores a largo plazo.

Respondido 27 ago 11, 20:08

Estoy de acuerdo con el argumento general (especialmente la parte del "sistema puro"). Por otro lado, tener una entidad representa velocidad pueden ser exagerado. - Konrad Rudolph

Si solo una clase hereda de ReasonableSpeed, entonces parece un poco exagerado.

Si muchas clases heredan o usan ReasonableSpeed, entonces es inteligente.

Respondido 27 ago 11, 20:08

Ambos diseños producen el mismo resultado cuando se utiliza un valor no válido como velocidad, es decir, ambos generan una excepción. Aplicando Principio de la navaja de Occamo Regla de la rareza de Unix:

Regla de la parsimonia: escriba un gran programa solo cuando quede claro mediante la demostración que nada más servirá.

'Grande' aquí tiene el sentido tanto de gran volumen de código como de complejidad interna. Permitir que los programas crezcan mucho perjudica la mantenibilidad. Debido a que la gente se muestra reacia a desechar el producto visible de mucho trabajo, los grandes programas invitan a una inversión excesiva en enfoques fallidos o subóptimos.

es posible que desee elegir el primer enfoque más simple. A menos que desee reutilizar ReasonableSpeed clase.

Respondido el 20 de junio de 20 a las 12:06

Recomendaría hacer esto en su lugar:

class SpeedF1
{
public:
  explicit SpeedF1(double maxSpeed);
  double getSpeed() const;
  void accelerate();
  void decelerate();
protected:
  void setSpeed(double speed);
  //other stuff as unit 
private:
  double maxSpeed_;
  double curSpeed_;
};

SpeedF1::SpeedF1(double maxSpeed) maxSpeed_(maxSpeed), curSpeed_(0.0) { }

double SpeedF1::getSpeed() const { return curSpeed_; }

void SpeedF1::setSpeed(double speed) {
    if(speed < 0.0) speed = 0.0;
    if(speed > maxSpeed_) speed = maxSpeed_;
    curSpeed = speed;
}

void SpeedF1::accelerate() {
    setSpeed(curSpeed_ + SOME_CONSTANT_VELOCITY);
}


void SpeedF1::decelerate() {
    setSpeed(curSpeed_ - SOME_CONSTANT_VELOCITY);
}

Respondido 27 ago 11, 20:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.