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

14

Eu ainda me considero um programador aprendiz, por isso estou sempre procurando aprender uma maneira "melhor" para a 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 desenvolvo uma classe na linguagem OOP (geralmente C ++ ou Python), separava 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 me abster de dividir o processo de inicialização? A questão não se limita apenas a C ++ e Python, e respostas para outras linguagens também são apreciadas.

Caladbolgll
fonte
2
veja também: stackoverflow.com/questions/33124542/…
Doc Brown
1
E para Python: stackoverflow.com/questions/20661448/…
Doc Brown
Por que você costuma fazer isso? Hábito? Você já teve um motivo para fazer isso?
JeffO 08/12/16
@ Jeffie Eu adquiri esse hábito quando estava trabalhando para criar GUI com a biblioteca MFC. A maioria das classes relacionadas à interface do usuário, como CApp, CWindow, CDlg e assim por diante, possui funções OnInit () com as quais você pode sobrescrever, o que responde às mensagens correspondentes.
Caladbolgll

Respostas:

28

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

  1. Se ocorrer um erro, isso acontece o mais rápido possível e é mais fácil de diagnosticar. Por exemplo, se null for um valor de argumento inválido, teste e falhe no construtor.
  2. O objeto está sempre em um estado válido. Um colega de trabalho não pode cometer um erro e esquecer de ligar initMyClass1()porque não está lá . "Os componentes mais baratos, rápidos e confiáveis ​​são aqueles que não existem".
  3. Se fizer sentido, o objeto pode ser imutável, o que tem muitas vantagens.
user949300
fonte
2

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

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

A inicialização extra é apenas algo a mais para que os programadores que usam sua API se lembrem e fornece mais para dar errado se não fizerem o que é certo, mas qual é o valor para esse encargo extra?

Você deseja fornecer abstrações simples, fáceis de usar e difíceis de dar errado. Programar já é bastante difícil, sem coisas gratuitas para lembrar / bastidores para pular. Você deseja que os usuários da API (mesmo que você esteja usando sua própria API) caiam no poço do sucesso .

Erik Eidt
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.

zzz777
fonte
0

Há casos em que o objeto tem muita inicialização que pode ser dividida em duas categorias:

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

  2. Atributos que podem precisar reverter para valores originais (ou valores com modelos) com base em alguma condição após a conclusão do trabalho, como uma redefinição suave. por exemplo, conexões em um conjunto de conexões.

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

A mesma função pode ser chamada posteriormente se uma redefinição suave for necessária, sem a necessidade de descartar e recriar o objeto.

Ramakant
fonte
0

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

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

Manipulação 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 E / S ou seus parâmetros podem ser entrada do usuário, o único mecanismo aberto a você é gerar uma exceção. Em alguns casos, pode não ser o que você deseja e pode fazer mais sentido separar o código propenso a erros em 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 estado

Este é o caso em que você está modelando um objeto que possui 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 do objeto (e exclusão) lide apenas com atributos orientados à memória (nome do arquivo, porta, etc.). Haverá funções para gerenciar especificamente as transições de estado, por exemplo, abrir, fechar, que são efetivamente funções de inicialização e desmontagem.

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 (por exemplo, você constrói um vetor de arquivos e os abre de forma assíncrona).

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

Alex
fonte