OOP Estilo de codificação: inicializa tudo no construtor?

14

Eu ainda me considero um programador aprendiz, então estou sempre procurando aprender uma maneira "melhor" de programação típica. Hoje, meu colega de trabalho argumentou que meu estilo de codificação faz algum trabalho desnecessário e quero ouvir opiniões de outras pessoas. Normalmente, quando eu desenho uma classe na linguagem OOP (geralmente C ++ ou Python), eu separaria a inicialização em duas 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)
    }
}

(ou equivalente em 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!"

Qual é a sua opinião sobre essa abordagem? Devo abster-me de dividir o processo de inicialização? A questão não se limita apenas ao C ++ e Python, e respostas para outras linguagens também são bem-vindas.

    
por Caladbolgll 23.11.2016 / 22:24
fonte

5 respostas

28

Embora às vezes seja problemático, há muitas vantagens em inicializar tudo no construtor:

  1. Se houver um erro, isso acontece o mais rápido possível e é mais fácil diagnosticar. Por exemplo, se null for um valor de argumento inválido, teste e falhe no construtor.
  2. O objeto é sempre em um estado válido. Um colega de trabalho não pode cometer um erro e se esqueça de chamar initMyClass1() porque não está lá . "Os componentes mais baratos, mais rápidos e mais confiáveis são aqueles que não estão lá."
  3. Se isso faz sentido, o objeto pode ser feito href="https://en.wikipedia.org/wiki/Immutable_object"> imutável, que tem muitas vantagens.
por 23.11.2016 / 23:17
fonte
2

Pense na abstração que você está fornecendo aos seus usuários.

Por que dividir algo que poderia ser feito de uma só vez em duas?

A inicialização extra é apenas algo extra para programadores usando sua API para lembrar, e fornece mais para dar errado se eles não fizerem isso certo, mas para qual valor para eles para este fardo extra?

Você deseja fornecer abstrações simples, fáceis de usar e difíceis de serem executadas. Programar é difícil o suficiente sem coisas desnecessárias para lembrar / arcos para pular. Você quer que seus usuários da API (mesmo que você use sua própria API) caiam no poço de sucesso .

    
por 24.11.2016 / 01:07
fonte
1

Inicialize tudo, exceto a área de big data. As ferramentas de análise estática sinalizarão campos não inicializados no construtor. No entanto, a maneira mais produtiva / segura é ter todas as variáveis de membro com construtores padrão e inicializar explicitamente apenas aquelas que requerem inicialização não padrão.

    
por 23.11.2016 / 22:31
fonte
0

Existem casos em que o objeto tem muita inicialização e pode ser dividido em duas categorias:

  1. Atributos que são imutáveis ou não precisam ser redefinidos.

  2. Atributos que talvez precisem reverter para valores originais (ou valores com o modelo) com base em alguma condição após o cumprimento de seu trabalho, como uma reinicialização temporária. por exemplo. conexões em um conjunto de conexões.

Aqui, a segunda parte da inicialização mantida em uma função separada, digamos InitialiseObject (), pode ser chamada no ctor.

A mesma função pode ser chamada mais tarde se uma reinicialização por software for necessária, sem precisar descartar e recriar o objeto.

    
por 25.11.2016 / 11:41
fonte
0

Como outros já disseram, geralmente é uma boa ideia inicializar no construtor.

Existem, no entanto, razões que não podem ou não se aplicar em casos específicos.

Tratamento de erros

Em muitos idiomas, a única maneira de sinalizar um erro em um construtor é gerar uma exceção.

Se a sua inicialização tiver uma chance razoável de gerar um erro, por exemplo, envolve IO ou seus parâmetros podem ser entrada do usuário, então o único mecanismo aberto para você é gerar uma exceção. Em alguns casos, isso pode não ser o que você deseja e pode fazer mais sentido separar o código propenso a erros a uma função de inicialização separada.

Provavelmente, o exemplo mais comum disso é em C ++, se o padrão do projeto / organização for desativar as exceções.

Máquina de estados

Este é o caso em que você está modelando um objeto que tem transições de estado explícitas. Por exemplo, um arquivo ou um soquete que pode ser aberto e fechado.

Nesse caso, é comum que a construção (e exclusão) do objeto lide apenas com os atributos orientados à memória (nome do arquivo, porta, etc.). Haverá então funções para gerir especificamente as transições de estado, e. abrir, fechar, que são efetivamente as funções de inicialização e desativação.

As vantagens estão no tratamento de erros, como acima, mas também pode haver um caso para separar a construção da inicialização (digamos que você construa um vetor de arquivos e os abra de forma assíncrona).

A desvantagem, como outros disseram, é que você agora coloca o ônus do gerenciamento do estado no usuário de suas classes. Se você pudesse gerenciar apenas com construção, você poderia, por exemplo, usar o RAII para fazer isso automaticamente.

    
por 25.11.2016 / 17:58
fonte