Estilo de codificación OOP: ¿inicializar todo en el constructor?

14

Todavía me considero un programador aprendiz, por lo que siempre estoy buscando aprender una "mejor" forma de programación típica. Hoy, mi compañero de trabajo ha argumentado que mi estilo de codificación hace un trabajo innecesario, y quiero escuchar las opiniones de los demás. Por lo general, cuando diseño una clase en lenguaje OOP (generalmente C ++ o Python), separaría la inicialización en dos partes diferentes:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(o equivalente a Python)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

¿Cuál es su opinión sobre este enfoque? ¿Debo abstenerme de dividir el proceso de inicialización? La pregunta no solo se limita a C ++ y Python, y también se agradecen las respuestas para otros lenguajes.

Caladbolgll
fuente
1
relacionado: stackoverflow.com/questions/3127454/…
Doc Brown
2
ver también: stackoverflow.com/questions/33124542/…
Doc Brown el
1
Y para Python: stackoverflow.com/questions/20661448/…
Doc Brown
¿Por qué lo haces normalmente? ¿Hábito? ¿Alguna vez te dieron una razón para hacerlo?
JeffO
@JeffO Obtuve este hábito cuando estaba trabajando para hacer GUI con la biblioteca MFC. La mayoría de las clases relacionadas con la interfaz de usuario, como CApp, CWindow, CDlg, etc., tienen funciones OnInit () con las que puede sobrescribir, que responden a sus mensajes correspondientes.
Caladbolgll

Respuestas:

28

Aunque a veces es problemático, hay muchas ventajas en inicializar todo en el constructor:

  1. Si va a haber un error, ocurre lo más rápido posible y es más fácil de diagnosticar. Por ejemplo, si nulo es un valor de argumento no válido, pruebe y falle en el constructor.
  2. El objeto siempre está en un estado válido. Un compañero de trabajo no puede cometer un error y olvida llamar initMyClass1()porque no está allí . "Los componentes más baratos, rápidos y confiables son aquellos que no están allí".
  3. Si tiene sentido, el objeto puede hacerse inmutable, lo que tiene muchas ventajas.
user949300
fuente
2

Piensa en la abstracción que estás proporcionando a tus usuarios.

¿Por qué dividir algo que se puede hacer de una vez en dos?

La inicialización adicional es algo extra para los programadores que usan su API para recordar, y proporciona más para salir mal si no lo hacen bien, pero ¿qué valor tienen para ellos esta carga adicional?

Desea proporcionar abstracciones simples, fáciles de usar y difíciles de equivocar. La programación es bastante difícil sin cosas gratuitas para recordar / aros para saltar. Desea que los usuarios de su API (incluso si solo está usando su propia API) caigan en el pozo del éxito .

Erik Eidt
fuente
1

Inicialice todo excepto el área de big data. Las herramientas de análisis estático marcarán los campos no inicializados en el constructor. Sin embargo, la forma más productiva / segura es tener todas las variables miembro con constructores predeterminados e inicializar explícitamente solo aquellas que requieren una inicialización no predeterminada.

zzz777
fuente
0

Hay casos en los que el objeto tiene mucha inicialización que se puede dividir en dos categorías:

  1. Atributos que son inmutables o que no necesitan restablecerse.

  2. Atributos que podrían necesitar volver a los valores originales (o valores de plantilla) en función de alguna condición después de cumplir con su trabajo, una especie de restablecimiento parcial. por ejemplo, conexiones en un grupo de conexiones.

Aquí la segunda parte de la inicialización que se mantiene en una función separada, digamos InitialiseObject (), se puede llamar en el ctor.

Se puede llamar a la misma función más adelante si se requiere un reinicio por software, sin tener que descartar y volver a crear el objeto.

Ramakant
fuente
0

Como han dicho otros, generalmente es una buena idea inicializar en el constructor.

Sin embargo, existen razones para que esto no se aplique o no en casos específicos.

Manejo de errores

En muchos idiomas, la única forma de señalar un error en un constructor es generar una excepción.

Si su inicialización tiene una posibilidad razonable de generar un error, por ejemplo, involucra IO o sus parámetros podrían ser ingresados ​​por el usuario, entonces el único mecanismo abierto para usted es generar una excepción. En algunos casos, esto puede no ser lo que desea y puede tener más sentido separar el código propenso a errores en una función de inicialización separada.

Probablemente el ejemplo más común de esto es en C ++ si el proyecto / estándar organizacional es desactivar las excepciones.

Máquina estatal

Este es el caso en el que está modelando un objeto que tiene transiciones de estado explícitas. Por ejemplo, un archivo o un socket que se puede abrir y cerrar.

En este caso, es común que la construcción del objeto (y su eliminación) solo trate con atributos orientados a la memoria (nombre de archivo, puerto, etc.). Luego habrá funciones para gestionar específicamente las transiciones de estado, por ejemplo, abrir, cerrar, que son efectivamente funciones de inicialización y desmantelamiento.

Las ventajas están en el manejo de errores, como anteriormente, pero también puede haber un caso para separar la construcción de la inicialización (digamos que construye un vector de archivos y los abre de forma asincrónica).

La desventaja, como han dicho otros, es que ahora usted pone la carga de la administración del estado sobre el usuario de sus clases. Si pudieras administrar solo la construcción, entonces podrías, por ejemplo, usar RAII para hacer esto automáticamente.

Alex
fuente