É uma boa ideia dividir um construtor em múltiplas funções?

5

Aqui está o fluxo de trabalho de uma aula do meu programa:

Instanciação de classe

Servidor - > Criando soquete - > Soquete de ligação para addr: port - > Ouvindo - > Lidando com clientes

Devo colocar todas essas coisas no construtor ou em funções privadas separadas?

    
por Nurrl 05.09.2017 / 00:38
fonte

7 respostas

9

Como regra, você deve minimizar a quantidade de lógica de negócios nos construtores ... Assim, dependendo da função / responsabilidade da sua classe, você pode incluir a criação de soquetes e inserir o soquete em um endereço no construtor, mas provavelmente ouvir e manusear deve ser feito em métodos separados.

    
por 05.09.2017 / 01:31
fonte
1

O construtor deve fazer apenas uma coisa e fazê-lo bem: construir o objeto (aqui uma instância de Server ).

Não, não faça tudo isso no construtor! Aqui está porque

Se você adicionar mais coisas, como criar um soquete e vinculá-lo a um endereço de porta, terá vários problemas. Por exemplo:

  • e se a ligação da porta estiver errada: o construtor deve falhar? Ou deveria retornar a instância, mas em um estado de erro que poderia permitir a recuperação posterior?
  • e se houver alternativas diferentes para fazer a mesma coisa, por exemplo, vincular o servidor a uma porta IPv4 ou vincular o servidor a um endereço IPv6?

Além disso, se o construtor iniciar operações que não terminam, como escutar e tratar solicitações de clientes, todo o seu código será executado em um objeto inacabado (ou seja, antes que o construtor seja concluído).

Por fim, alguns idiomas não permitem derivar um novo tipo ServerXXX e substituir alguns de seus métodos. Em C ++, por exemplo, o construtor Server seria executado no objeto Server base de ServerXXX e não estaria ciente dos métodos ServerXXX substituídos. Somente quando o construtor Server for concluído, ele será transferido para o construtor ServerXXX

Como fazer melhor?

Existem várias alternativas. Sem saber todos os detalhes, posso imaginar usar um padrão de construtor (GoF) :

  • Um construtor abstrato definiria a interface geral para construir seu servidor, construindo as diferentes partes necessárias.
  • Um construtor de concreto implementaria essa interface para uma determinada classe de servidor e forneceria a cola para montar as peças.
  • Um "cliente" criaria o construtor de concreto apropriado e o injeta no diretor
  • Um diretor invocaria os métodos do contrete buider na ordem apropriada
  • O cliente chamaria o construtor de concreto para obter o final pronto para usar o Servidor.

Esse tipo de abordagem não é ideal para executar negócios em execução, ou seja, solicitações de atendimento e manipulação. Isso definitivamente não faz parte do processo de construção. Além disso, isso pode exigir multithreading, um thread fazendo a escuta e vários outros threads manipulando as solicitações para os clientes conectados.

Para esta parte, sugiro que você escolha um padrão de método de modelo que implemente o evento loop para o servidor. O código principal seria algo como:

ServerBuilder builder; 
ServerDirector director(builder); 
director.construct();                  // creates the server and its components
Server server = director.get_result(); // server ready to use 
server.mainloop();                     
    
por 05.09.2017 / 22:54
fonte
0

Se o seu construtor for grande o suficiente para pensar em dividi-lo em várias partes, eu tender a pensar se isso pode não indicar que a sua classe (e sua área de responsabilidade) é maior do que deveria ser, então você pode querer dividir a classe em várias partes.

No caso específico de um servidor de soquete, acho que há realmente duas (quase) partes completamente separadas. Um apenas escuta as conexões de entrada. É bastante genérico - quase qualquer servidor de socket faz praticamente as mesmas coisas: criar um socket, ligá-lo a um endereço, escutar conexões de entrada. Uma vez que aceita uma conexão, ela a transfere para outra classe que tenha toda a lógica de negócios para lidar com as interações com o cliente.

Essa segunda classe é um manipulador de sessão que apenas recebe um soquete aberto que já foi aceito e se comunica com o cliente por meio do soquete. É mais difícil dizer exatamente o que acontece aqui, porque a maior parte depende dessa interação entre o cliente e o servidor.

Quando escrevo código como esse, nenhuma classe teve um construtor tão grande ou complexo que eu vi muito (qualquer, realmente) motivo para dividi-lo em partes. O ouvinte basicamente faz socket() , bind() e listen() . Algo como uma dúzia de linhas de código - e três quartos disso é o tratamento de erros e tal (embora se você precisar lidar com algo como SSL / TLS, a complexidade cresce um pouco).

    
por 05.09.2017 / 21:57
fonte
0

Eu prefiro os construtores que retornam instâncias working da classe dada. (Analogia do mundo real: se você pedir um carro, você espera que ele seja funcional no sentido de que você pode sair com ele, e é isso que eu espero de um construtor).

Naturalmente, a definição de "trabalho" é específica do aplicativo. Então a questão é, o que você espera de um Server ?

  • Instanciação de classe: você certamente precisa fazer isso, caso contrário, não há nada que você possa chamar de Server .
  • Criando soquete, soquete de ligação para addr: port, escutando: é um Server se não escutar em nenhuma porta?
  • Gerenciando clientes: é um Server se não lidar com solicitações de clientes?

Normalmente, eu respondo a todas essas perguntas com um YES e faço o construtor fazer todas essas coisas (não criar um construtor de 100 linhas, mas dividir as ações em métodos individuais, é claro). Mas depois que o servidor foi construído, ele serve.

Talvez suas respostas sejam diferentes, ou talvez o Server seja uma classe base abstrata - então o construtor deve ser diferente, é claro.

    
por 06.09.2017 / 21:13
fonte
0

Outras respostas cobrem mais bem. Uma coisa não mencionada é como as interfaces IDisposable (.NET) e AutoClosable and Closable (Java) funcionam.

É uma boa prática no .NET usar a instrução using e tentar-com-recursos sempre que possível.

Se colocarmos a lógica no construtor, isso não seria possível. Então, isso é mais um "con" para os prós e contras da lista.

    
por 07.09.2017 / 20:13
fonte
-1

Supondo que você tenha uma classe que, entre outras coisas, manipule a ligação inicial da porta e configure a manipulação de eventos como uma obrigação (também deve ser executada antes que qualquer outra operação possa ser executada), uma maneira de fazê-lo é para criar um método público init que deve ser chamado antes de qualquer outro. ... Mas isso é feio. Mais precisamente, requer que o chamador leve em conta considerações que deveriam ser obscurecidas pela própria classe geral.

Uma solução melhor seria inicializar lentamente a configuração de soquete e manipulação de eventos. Ou seja, você tem uma instância de Server com um membro booleano privado isInitialized inicialmente configurado como false. Antes de qualquer método ser chamado, você executa essa verificação e executa a chamada necessária para o método particular init , depois definindo isInitialized como true. Se você tem poucos métodos públicos, isso é ideal e suja muito pouco sua implementação real. Se você tiver muitos métodos públicos, você deve considerar a criação de uma segunda classe que lida com o âmago da conexão, que você usa para executar as ações de baixo nível, incluindo o quando a conexão é formada (tudo o que seria obscurecido do chamador para Server ).

Embora seja verdade que você não criará uma instância de Server sem usá-la (para que o benefício secundário de carregamento ocioso não se aplique), ela ainda fornece um meio de remover o código potencialmente explosivo seu construtor. O erro também seria mais jogado em um local mais intuitivo, em um método chamado init dentro de seu método send , que provavelmente é um lugar que será coberto por blocos try catch de qualquer maneira.

    
por 05.09.2017 / 09:26
fonte
-2

Eu certamente prefiro quebrar grandes construtores, com uma ressalva:

Eu só usaria métodos static , preferencialmente puros, para o trabalho. Isso alivia a necessidade de se preocupar com a chamada de métodos de instância em uma instância inconsistente.

Por exemplo, posso ter algo como:

class MyClass {
private:
    Socket socket;
public:
    MyClass(Address address, Port port) {
        this->socket = MyClass::createSocket();
        MyClass::bindSocket(this->socket, address, port);
        // etc
    }

    Socket createSocket() const {
        return Socket(); // hypothetical. Add socket construction logic here
    }

    void bindSocket(Socket socket, Address address, Port port) {
        socket.bind(address, port); //hypothetical
    }
};
    
por 05.09.2017 / 06:14
fonte

Tags