Podemos viver sem construtores?

25

Digamos que, por alguma razão, todos os objetos sejam criados dessa maneira $ obj = CLASS :: getInstance (). Em seguida, injetamos dependências usando setters e executamos a inicialização usando $ obj-> initInstance (); Existem problemas ou situações reais que não podem ser resolvidos se não usarmos construtores?

Ps a razão para criar um objeto desta maneira, é que podemos substituir a classe dentro de getInstance () de acordo com algumas regras.

Estou trabalhando em PHP, se isso importa

Axel Foley
fonte
qual linguagem de programação?
mosquito
2
PHP (por que isso importa?)
Axel Foley
8
Parece que o que você deseja fazer pode ser alcançado usando o padrão de fábrica.
SuperM
1
Apenas como uma observação: o padrão de fábrica @superM mencionado é uma maneira de implementar a Inversão de Controle (Injeção de Dependência).
Joachim Sauer
Não é isso que o javascript faz com objetos?
Thijser

Respostas:

44

Eu diria que isso dificulta bastante o seu espaço de design.

Os construtores são um ótimo local para inicializar e validar os parâmetros passados. Se você não puder mais usá-los para isso, a inicialização, o tratamento de estado (ou simplesmente negar o construtor de objetos "quebrados") se tornará muito mais difícil e parcialmente impossível.

Por exemplo, se todo Fooobjeto precisar de um Frobnicator, poderá verificar em seu construtor se o valor Frobnicatoré não nulo. Se você retirar o construtor, fica mais difícil verificar. Você verifica em todos os pontos em que seria usado? Em um init()método (externalizando efetivamente o método construtor)? Nunca verifique e espere o melhor?

Embora você provavelmente ainda possa implementar tudo (afinal, você ainda está completo), algumas coisas serão muito mais difíceis de fazer.

Pessoalmente, sugiro investigar a injeção de dependência / inversão de controle . Essas técnicas também permitem alternar classes de implementação concretas, mas não impedem a gravação / uso de construtores.

Joachim Sauer
fonte
O que você quis dizer com "ou simplesmente negar o construtor de objetos" quebrados "?
Geek
2
@ Geek: um construtor pode inspecionar seu argumento e decidir se isso resultaria em um objeto de trabalho (por exemplo, se seu objeto precisa de um HttpClient, ele verifica se esse parâmetro é não nulo). E se essas restrições não forem atendidas, isso poderá gerar uma exceção. Isso não é realmente possível com a abordagem de construir e definir valores.
Joachim Sauer
1
Acho que o OP estava apenas descrevendo a externalização do construtor interno init(), o que é inteiramente possível - embora isso apenas imponha mais encargos de manutenção.
Peter
26

2 vantagens para os construtores:

Os construtores permitem que as etapas de construção de um objeto sejam executadas atomicamente.

Eu poderia evitar um construtor e usar setters para tudo, mas e as propriedades obrigatórias, como Joachim Sauer sugeriu? Com um construtor, um objeto possui sua própria lógica de construção para garantir que não haja instâncias inválidas dessa classe .

Se a criação de uma instância Fooexigir 3 propriedades a serem configuradas, o construtor poderá fazer referência a todas as 3, validá-las e lançar uma exceção se forem inválidas.

Encapsulamento

Ao confiar apenas em levantadores, o ônus é do consumidor de um objeto para construí-lo adequadamente. Pode haver diferentes combinações de propriedades válidas.

Por exemplo, toda Fooinstância precisa de uma instância de Barcomo propriedade barou de BarFindercomo uma propriedade barFinder. Pode usar qualquer um. Você pode criar um construtor para cada conjunto de parâmetros válido e aplicar as convenções dessa maneira.

A lógica e a semântica dos objetos vivem dentro do próprio objeto. É um bom encapsulamento.

Brandon
fonte
15

Sim, você pode viver sem construtores.

Claro, você pode acabar com um monte de código duplicado da placa da caldeira. E se o seu aplicativo for de qualquer escala, você provavelmente gastará muito tempo tentando localizar a origem de um problema quando esse código padrão não for usado de forma consistente no aplicativo.

Mas não, você não precisa "estritamente" seus próprios construtores. Obviamente, você também não 'precisa' estritamente de classes e objetos.

Agora, se seu objetivo é usar algum tipo de padrão de fábrica para criação de objetos, isso não é mutuamente exclusivo para o uso de construtores ao inicializar seus objetos.

GrandmasterB
fonte
Absolutamente. Pode-se até viver sem classes e objetos, para começar.
JensG
5
Todos saudam o poderoso montador!
Davor Ždralo
9

A vantagem de usar construtores é que eles facilitam a garantia de que você nunca terá um objeto inválido.

Um construtor oferece a oportunidade de definir todas as variáveis ​​de membro do objeto para um estado válido. Quando você garante que nenhum método mutador pode alterar o objeto para um estado inválido, você nunca terá um objeto inválido, o que o salvará de muitos bugs.

Mas quando um novo objeto é criado em um estado inválido e você deve chamar alguns setters para colocá-lo em um estado válido no qual ele pode ser usado, você corre o risco de um consumidor da classe esquecer de chamá-los ou chamá-los incorretamente e você acaba com um objeto inválido.

Uma solução alternativa poderia ser criar objetos apenas através de um método de fábrica que verifica a validade de todos os objetos criados antes de devolvê-los ao chamador.

Philipp
fonte
3

$ obj = CLASS :: getInstance (). Em seguida, injetamos dependências usando setters e executamos a inicialização usando $ obj-> initInstance ();

Eu acho que você está tornando isso mais difícil do que precisa ser. Podemos injetar dependências muito bem através do construtor - e se você tiver muitas delas, use uma estrutura semelhante a dicionário para poder especificar quais você deseja usar:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

E dentro do construtor, você pode garantir consistência da seguinte forma:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args pode então ser usado para definir membros privados conforme necessário.

Quando feito inteiramente dentro do construtor, nunca haverá um estado intermediário onde $objexista, mas não seja inicializado, como ocorreria no sistema descrito na pergunta. É melhor evitar esses estados intermediários, porque você não pode garantir que o objeto sempre será usado corretamente.

Izkata
fonte
2

Na verdade, eu estava pensando em coisas semelhantes.

A pergunta que fiz foi "O que o construtor faz e é possível fazê-lo de maneira diferente?" Cheguei a essas conclusões:

  • Isso garante que algumas propriedades sejam inicializadas. Aceitando-os como parâmetros e definindo-os. Mas isso pode ser facilmente aplicado pelo compilador. Simplesmente anotando os campos ou propriedades como "obrigatório", o compilador verifica durante a criação da instância se tudo está definido corretamente. A chamada para criar a instância provavelmente seria a mesma, simplesmente não haveria nenhum método construtor.

  • Garante que as propriedades são válidas. Isso pode ser facilmente alcançado pela condição de afirmação. Novamente, você anotaria as propriedades com as condições corretas. Alguns idiomas já fazem isso.

  • Alguma lógica de construção mais complexa. Os padrões modernos não recomendam fazer isso no construtor, mas propõem o uso de métodos ou classes especializados de fábrica. Portanto, o uso de construtores neste caso é mínimo.

Então, para responder à sua pergunta: Sim, acredito que é possível. Mas isso exigiria algumas grandes mudanças no design da linguagem.

E acabei de notar que minha resposta é bonita OT.

Eufórico
fonte
2

Sim, você pode fazer quase tudo sem usar construtores, mas isso é claramente um desperdício de benefícios das linguagens de programação orientada a objetos.

Nas linguagens modernas (falarei aqui sobre C # em que programo), você pode limitar partes do código que podem ser executadas apenas em um construtor. Graças ao qual você pode evitar erros desajeitados. Uma dessas coisas é o modificador somente leitura :

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Como recomendado anteriormente por Joachim Sauer, em vez de usar o Factorydesign patter, leia sobre Dependency Injection. Eu recomendaria ler Injeção de Dependência no .NET por Mark Seemann .

SOReader
fonte
1

Instanciar um objeto com um tipo, dependendo dos requisitos, é absolutamente possível. Pode ser o próprio objeto, usando variáveis ​​globais do sistema para retornar um tipo específico.

No entanto, ter uma classe no código que pode ser "Todos" é o conceito de tipo dinâmico . Pessoalmente, acredito que essa abordagem crie inconsistência no seu código, torne os testes complexos * e "o futuro se torna incerto" com relação ao fluxo do trabalho proposto.

* Estou me referindo ao fato de que os testes devem considerar primeiro o tipo, depois o resultado que você deseja alcançar. Então você está criando um grande teste aninhado.

marcocs
fonte
1

Para equilibrar algumas das outras respostas, reivindicando:

Um construtor oferece a oportunidade de definir todas as variáveis ​​de membro do objeto para um estado válido ... você nunca terá um objeto inválido, o que o salvará de muitos bugs.

e

Com um construtor, um objeto possui sua própria lógica de construção para garantir que não haja instâncias inválidas dessa classe.

Tais declarações às vezes implicam a suposição de que:

Se uma classe tem um construtor que, no momento em que sai, coloca o objeto em um estado válido, e nenhum dos métodos da classe o modifica para torná-lo inválido, é impossível que o código fora da classe detecte um objeto dessa classe em um estado inválido.

Mas isso não é bem verdade. A maioria dos idiomas não possui uma regra contra um construtor que passa this( selfou o que o idioma chama) para código externo. Esse construtor cumpre completamente a regra mencionada acima e ainda corre o risco de expor objetos semi-construídos a códigos externos. É um ponto menor, mas facilmente esquecido.

Daniel Earwicker
fonte
0

Isso é algo anedótico, mas eu costumo reservar construtores para um estado essencial para que o objeto seja completo e utilizável. Exceto qualquer injeção de setter, uma vez executado o construtor, meu objeto deve ser capaz de executar as tarefas necessárias.

Qualquer coisa que possa ser adiada, deixo de fora do construtor (preparando valores de saída, etc.). Com essa abordagem, sinto que não estou usando construtores para nada, mas a injeção de dependência faz sentido.

Isso também tem o benefício adicional de conectar seu processo de design mental a não fazer nada prematuramente. Você não inicializará ou executará uma lógica que talvez nunca acabe sendo usada porque, na melhor das hipóteses, tudo o que você está fazendo é uma configuração básica para o trabalho futuro.

Ómega
fonte