¿Viola algún principio de OOP si una función miembro no usa ninguna de las propiedades de clase / variables miembro?

8

Tengo una clase existente que interactúa y que puede abrir, leer o escribir en un archivo. Necesito recuperar una modificación de archivo para ese propósito. Tengo que agregar un nuevo método.

Supongamos que lo siguiente es mi definición de clase donde quiero agregar un nuevo método.

class IO_file
{
 std::string m_file_name;
 public:
 IO();
 IO(std::string file_name);

+ time_t get_mtime(file_name);
+       OR
+ time_t get_mtime();
};

Tengo dos opciones

  1. Cree un objeto vacío y luego pase el nombre_archivo en el argumento del método para el que deseo recuperar la hora de modificación del archivo.

  2. Pase el nombre del archivo en la construcción del objeto y simplemente llame a la función miembro que operará en la variable miembro.

Ambas opciones servirán para el propósito. También creo que el segundo enfoque es mejor que el primero. Pero lo que no entiendo es ¿Cómo ? ¿El primer enfoque es un mal diseño ya que no utiliza una variable miembro? ¿Qué principio de diseño orientado a objetos viola? Si una función miembro no usa la variable miembro, ¿esa función miembro siempre debe hacerse estática?

irsis
fuente
OOP se trata de separar las preocupaciones, por ejemplo, separar el cliente de la implementación a través de una interfaz, pero también separar la elección de implementación del uso de una interfaz, al tiempo que permite múltiples implementaciones que potencialmente coexisten dinámicamente. Usando el ejemplo de estrategia de @ CandiedOrange, el seleccionador de la estrategia y el consumidor de la estrategia pueden separarse; mientras que con los métodos estáticos, a pesar de que las preocupaciones del cliente y la implementación están separadas, existe una estrecha relación del cliente con la elección de la implementación (única).
Erik Eidt
2
Es un mal diseño lo que puedo hacer IO("somefile.txt").get_mtime("someotherfile.txt"). ¿Y eso que significa?
user253751

Respuestas:

8

¿Viola algún principio de OOP si una función miembro no utiliza ninguna de las propiedades de clase / variables miembro?

No.

OOP no le importa si su función miembro usa, o no usa, propiedades de clase o variables miembro. OOP se preocupa por el polimorfismo y no por la implementación de codificación dura. Las funciones estáticas tienen sus usos, pero una función no debería ser estática simplemente porque no depende del estado del objeto. Si eso es lo que piensas bien, pero no culpes a OOP porque esa idea no vino de OOP.

¿Es [es] un mal diseño [para no] hacer uso de las variables miembro?

Si no necesita recordar el estado de una llamada a otra, no hay una buena razón para usar el estado.

¿Qué principio del diseño orientado a objetos viola?

Ninguna.

Si una función miembro no usa la variable miembro, ¿esa función miembro siempre debe hacerse estática?

No. Este pensamiento tiene la flecha de implicación que va en la dirección equivocada.

  • Una función estática no puede acceder al estado de la instancia

  • Si la función no tiene necesidad de acceder al estado de instancia, entonces la función puede ser estática o no estática

Hacer que la función sea estática aquí depende completamente de usted. Pero lo hará más como un global si lo hace. Antes de volverse estático, considere alojar la función en una clase sin estado. Es mas flexible.


Tengo aquí un ejemplo de OOP de una función miembro que no usa propiedades de clase o variables miembro.

La función miembro (y su clase sin estado) :

#include <iostream>

class Strategy
{
public:
     virtual int execute (int a, int b) = 0; // execute() is a so-called pure virtual 
                                             // function. As a consequence, Strategy 
                                             // is a so-called abstract class.
};

 
Tres implementaciones diferentes:

class ConcreteStrategyAdd:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyAdd's execute()\n";
        return a + b;
    }
};

class ConcreteStrategySubstract:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategySubstract's execute()\n";
        return a - b;
    }
};

class ConcreteStrategyMultiply:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyMultiply's execute()\n";
        return a * b;
    }
};

 
Un lugar para almacenar la elección de implementación:

class Context
{
private:
    Strategy* pStrategy;

public:

    Context (Strategy& strategy)
        : pStrategy(&strategy)
    {
    }

    void SetStrategy(Strategy& strategy)
    {
        pStrategy = &strategy;
    }

    int executeStrategy(int a, int b)
    {
        return pStrategy->execute(a,b);
    }
};

 
Un ejemplo de uso

int main()
{
    ConcreteStrategyAdd       concreteStrategyAdd;
    ConcreteStrategySubstract concreteStrategySubstract;
    ConcreteStrategyMultiply  concreteStrategyMultiply;

    Context context(concreteStrategyAdd);
    int resultA = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategySubstract);
    int resultB = context.executeStrategy(3,4);

    context.SetStrategy(concreteStrategyMultiply);
    int resultC = context.executeStrategy(3,4);

    std::cout << "\nresultA: " << resultA 
              << "\nresultB: " << resultB 
              << "\nresultC: " << resultC 
              << "\n";
}

Salidas:

Called ConcreteStrategyAdd's execute()
Called ConcreteStrategySubstract's execute()
Called ConcreteStrategyMultiply's execute()

resultA: 7       
resultB: -1       
resultC: 12

Y todo sin execute()preocuparse por el estado de ningún objeto. La Strategyclase en realidad no tiene estado. Solo el estado está adentro Context. Los objetos sin estado están perfectamente bien en OOP.

Encontré este código aquí .

naranja confitada
fuente
1
+1 Con respecto a la estática "Este pensamiento tiene la flecha de implicación que va en la dirección incorrecta". es perfecto. Solía ​​trabajar en una empresa en la que si una función no necesitaba un estado, siempre sería estática, lo que significaba que aproximadamente el 60% (si no más) de la aplicación terminaba siendo estática. Habla de una pesadilla.
Carson
De Verdad? Hacemos exactamente lo contrario en mi empresa actual (y lo defendí). Cualquier método que pueda hacerse estático es.
cabeza de jardín
@gardenhead Si desea hacer un argumento contrario, hágalo. Decirme que toda una compañía lo está haciendo no significa que sea algo bueno.
candied_orange
1
@candiedorange Estoy de acuerdo en que no es evidencia adecuada, pero no quería entrar en una guerra de llamas. Pero como insiste, aquí está mi contraargumento: la POO es terrible y cuanto menos se use, mejor. ¿Contento?
cabeza de jardín
@gardenhead OOP es terrible, ¿estática es buena? Puedo escribir código funcional puro sin usar una sola vez estática. ¿Estás seguro de que no estás buscando una guerra de llamas?
candied_orange
5

get_mtimetendría más sentido aquí como una función independiente o como una staticfunción, en lugar de como se ha mostrado aquí.

La mtimelectura de un archivo se lee, en la mayoría de los sistemas, desde una llamada lstato similar, y no requiere un descriptor de archivo abierto, por lo que no tiene sentido tenerlo como una función miembro de una clase instanciada.

Greyfade
fuente
Como mtime no requiere abrir el descriptor de archivo, esta es la razón por la que quería hacerlo independiente de la variable miembro m_file_name. Por lo tanto, hace más para hacerlo estático en la clase que entendí esa parte. Pero quiero entender, desde el punto de vista académico / teórico, qué concepto de OPP violo con la primera opción.
irsis
2
@Rahul Realmente no hay una buena respuesta para eso. Quiero decir, supongo que podría decir que viola la abstracción al implicar una dependencia de los datos de la instancia, pero de eso no se trata realmente la abstracción. Honestamente, creo que te estás preocupando demasiado por los porqués. Simplemente tome como regla general que si una función miembro no necesita acceder a una instancia, entonces no debería ser una no staticmiembro.
greyfade
1
+1 pero sí, prácticamente sabía que no era una buena idea, pero quería aclarar mi comprensión "¿por qué"? La respuesta de CandidOrange lo explica.
irsis
2

La razón por la que la segunda opción parece instintivamente mejor (y, IMO, ES mejor) es porque su primera opción le da un objeto que en realidad no representa nada.

Al no especificar el nombre de archivo, su clase IO_file es realmente solo un objeto abstracto que se parece a un archivo concreto. Si está pasando el nombre del archivo cuando llama al método, también puede refactorizar todo el método en una función pura flotante; no hay ningún beneficio real para mantenerlo atado a un objeto que necesitará instanciar solo para usarlo. Es solo una función; la repetitiva de creación de objetos es solo un paso inconveniente agregado.

Por otro lado, una vez que se le da el nombre de archivo, cualquier método que llame a ese objeto se parece más a consultas sobre una instancia específica de una cosa. Eso es mejor OO porque sus objetos tienen un significado real y, por lo tanto, utilidad.

moberemk
fuente
2

Traduzcamos esto a C. Primero, nuestra clase, ahora es una estructura:

struct IO_file {
    char* m_file_name;
};

Definamos, por simplicidad de los fragmentos, una función de constructor (ignorando las pérdidas de memoria por ahora):

struct IO_file* IO_file(char* file_name) {
    struct IO_file* obj = malloc(sizeof(struct IO_file));
    obj.m_file_name = file_name;
    return obj;
}

La opción 2 se ve así:

time_t get_mtime(struct IO_file*);

y usado así:

time_t mtime = get_mtime(IO_file("some-file"));

¿Qué tal la opción # 1? Bueno, se ve así:

time_t get_mtime(struct IO_file* this, char* file_name);

¿Y cómo se usa? Esencialmente, está pidiendo pasar basura como primer parámetro:

time_t mtime = get_mtime((struct IO_file*)1245151325, "some-file");

No tiene mucho sentido, ¿verdad?

El sistema de objetos de C ++ lo oculta, pero el objeto también es un argumento del método, un argumento puntero implícito llamado this. Esto es lo que la opción n. ° 1 es mala: tiene un argumento que no se usa por definición (los argumentos que no se usan, pero pueden usarse en anulaciones están bien). Tales argumentos no aportan nada mientras complican la firma, la definición y el uso de la función y confunden a los lectores del código que se quedan reflexionando sobre lo que se supone que debe hacer el argumento, por qué está bien pasarle basura y si el hecho de que usted es o no pasarle basura / NULL/ objeto vacío es la causa del error que actualmente están tratando de resolver. Spoiler: no lo es, pero todavía van a perder el tiempo explorando esa posibilidad.

El hecho de que el argumento basura sea implícito puede disminuir el efecto, pero todavía está ahí y puede evitarse fácilmente haciendo el método static.

Idan Arye
fuente