Ele viola algum princípio de OOP se uma função de membro não usa nenhuma das propriedades de classe / variáveis ​​de membro?

8

Eu tenho uma classe existente que interage que pode abrir, ler ou gravar em um arquivo. Preciso recuperar uma modificação de arquivo para esse fim. Tenho que adicionar um novo método.

Suponha que esta é a minha definição de classe, na qual quero adicionar um novo 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();
};

Eu tenho duas opções -

  1. Crie um objeto vazio e depois passe o nome_do_arquivo no argumento do método para o qual desejo recuperar o tempo de modificação do arquivo.

  2. Passe o nome do arquivo na construção do objeto e simplesmente chame a função membro que operará na variável membro.

Ambas as opções servirão ao propósito. Eu também acredito que a segunda abordagem é melhor que a primeira. Mas o que eu não entendo é como ? A primeira abordagem é um design ruim, pois não utiliza uma variável de membro? Que princípio de design orientado a objetos ele viola? Se uma função de membro não usa a variável de membro, essa função de membro deve sempre ser tornada estática?

irsis
fonte
OOP é sobre separar preocupações, por exemplo, separar cliente da implementação por meio de uma interface - mas também separar a escolha da implementação do uso de uma interface, além de permitir várias implementações que potencialmente coexistem dinamicamente. Usando o exemplo de estratégia de @ CandiedOrange, o selecionador da estratégia e o consumidor da estratégia podem ser separados; enquanto que nos métodos estáticos, mesmo que as preocupações do cliente e da implementação sejam separadas, existe um forte acoplamento do cliente à escolha da (única) implementação.
precisa
2
É um design ruim que eu posso fazer IO("somefile.txt").get_mtime("someotherfile.txt"). Afinal, o que isso quer dizer?
user253751

Respostas:

8

Ele viola qualquer princípio de OOP se uma função de membro não usa nenhuma das propriedades de classe / variáveis ​​de membro?

Não.

OOP não se importa se sua função de membro usa ou não propriedades de classe ou variáveis ​​de membro. OOP se preocupa com o polimorfismo e não com a implementação da codificação. As funções estáticas têm seus usos, mas uma função não deve ser estática simplesmente porque não depende do estado do objeto. Se esse é o seu pensamento, mas não culpe o OOP porque essa ideia não veio do OOP.

O design incorreto [para] não fazer uso de variáveis-membro?

Se você não precisar se lembrar do estado de chamada para chamada, não há uma boa razão para usar o estado.

Qual princípio do design orientado a objetos [viola]?

Nenhum.

Se uma função de membro não usa a variável de membro, essa função de membro sempre deve ser estática?

Não. Esse pensamento tem a seta de implicação indo na direção errada.

  • Uma função estática não pode acessar o estado da instância

  • Se a função não precisar acessar o estado da instância, ela poderá ser estática ou não estática

Tornar a função estática aqui depende inteiramente de você. Mas fará com que pareça mais global, se você o fizer. Antes de ficar estático, considere hospedar a função em uma classe sem estado. É mais flexível.


Eu tenho aqui um exemplo OOP de uma função de membro que não usa propriedades de classe ou variáveis ​​de membro.

A função membro (e sua classe sem 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.
};

 
Três implementações 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;
    }
};

 
Um local para armazenar a escolha da implementação:

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

 
Um exemplo 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";
}

Saídas:

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

resultA: 7       
resultB: -1       
resultC: 12

E tudo sem se execute()preocupar com o estado de qualquer objeto. A Strategyclasse é realmente sem estado. Único estado está em Context. Objetos sem estado são perfeitamente bons no OOP.

Encontrei este código aqui .

candied_orange
fonte
1
+1 Em relação à estática "Esse pensamento tem a seta de implicação indo na direção errada". é perfeito. Eu costumava trabalhar em uma empresa onde, se uma função não precisasse de estado, ela sempre seria estática, o que significava que cerca de 60% (se não mais) do aplicativo acabavam sendo estáticos. Fale sobre um pesadelo.
Carson
Mesmo? Fazemos exatamente o oposto na minha empresa atual (e eu defendi isso). Qualquer método que possa ser feito estático é.
gardenhead
@ Gardenhead Se você deseja fazer um contra-argumento, faça-o. Dizer-me que uma empresa inteira está fazendo isso não significa que é uma coisa boa.
candied_orange
1
@candiedorange Concordo que não é uma evidência adequada, mas não queria entrar em uma guerra de chamas. Mas como você insiste, aqui está o meu contra-argumento: OOP é terrível e quanto menos usado, melhor. Feliz?
gardenhead
@genhenhead OOP é terrível, então a estática é boa? Eu posso escrever código funcional puro sem nunca usar a estática. Tem certeza de que não está procurando uma guerra de chamas?
candied_orange
5

get_mtimefaria mais sentido aqui como uma função autônoma ou como uma staticfunção, do que como você mostrou aqui.

O mtimearquivo de um arquivo é lido, na maioria dos sistemas, de uma chamada para lstatou similar e não requer um descritor de arquivo aberto; portanto, não faz sentido tê-lo como uma função membro de uma classe instanciada.

greyfade
fonte
Como o mtime não precisa abrir o descritor de arquivo, é por isso que eu queria torná-lo independente da variável de membro m_file_name. Portanto, faz mais para torná-lo estático na classe que entendi essa parte. Mas quero entender, do ponto de vista acadêmico / teórico, do qual o conceito de OPPs viole com a 1ª opção.
irsis #
2
@Rahul Realmente não há uma boa resposta para isso. Quero dizer, suponho que eu poderia dizer que viola a abstração ao implicar uma dependência dos dados da instância, mas não é exatamente disso que se trata a abstração. Honestamente, acho que você está se preocupando demais com os porquês. Apenas considere como regra geral que, se uma função de membro não precisar acessar uma instância, ela não deverá ser um não staticmembro.
greyfade
1
+1, mas na prática eu sabia que não era uma boa ideia, mas queria esclarecer meu entendimento "por que"? A resposta de CandidOrange explica.
irsis
2

A razão pela qual a segunda opção parece instintivamente melhor (e, na IMO, é melhor) é porque sua primeira opção fornece um objeto que na verdade não representa nada.

Ao não especificar o nome do arquivo, sua classe IO_file é realmente apenas um objeto abstrato que se assemelha a um arquivo concreto. Se você está passando o nome do arquivo quando chama o método, pode também refatorar todo o método em uma função pura flutuante; não há nenhum benefício real em mantê-lo vinculado a um objeto que você precisará instanciar apenas para usá-lo. É apenas uma função; o clichê de instanciação de objeto é apenas uma etapa adicional inconveniente.

Por outro lado, uma vez que o nome do arquivo recebe os métodos que você chama nesse objeto, são mais como consultas sobre uma instância específica de uma coisa. É melhor OO porque seus objetos têm um significado real e, portanto, utilidade.

moberemk
fonte
2

Vamos traduzir isso para C. Primeiro, nossa classe - agora é uma estrutura:

struct IO_file {
    char* m_file_name;
};

Vamos, pela simplicidade dos trechos, definir uma função construtora (ignorando os vazamentos de memória por enquanto):

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;
}

A opção 2 é assim:

time_t get_mtime(struct IO_file*);

e usado assim:

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

E a opção nº 1? Bem, fica assim:

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

E como é usado? Essencialmente, você está pedindo para passar o lixo eletrônico como o primeiro parâmetro:

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

Não faz muito sentido, faz?

O sistema de objetos do C ++ o oculta, mas o objeto também é um argumento do método - um argumento implícito do ponteiro chamado this. É essa a opção 1 que é ruim - ela possui um argumento que não é usado por definição (argumentos que não são usados, mas que podem ser usados ​​em substituições, estão OK). Tais argumentos não contribuem em nada, complicando a assinatura, a definição e o uso da função e confundindo os leitores do código que ficam pensando sobre o que o argumento deve fazer, por que não há problema em passar lixo para ele e se o fato de você estar ou não passar lixo / NULL/ objeto vazio para ele é a causa do bug que eles estão tentando resolver no momento. Spoiler - não é, mas eles ainda vão perder tempo explorando essa possibilidade.

O fato de o argumento lixo ser implícito pode diminuir o efeito, mas ele ainda existe e pode ser facilmente evitado com a criação do método static.

Idan Arye
fonte