Existe algum ponto em usar construtores e interfaces fluidas com inicializadores de objetos?


10

Em Java e C #, é possível criar um objeto com propriedades que podem ser configuradas na inicialização, definindo um construtor com parâmetros, definindo cada propriedade após a construção do objeto ou usando o padrão de interface do construtor / fluido. No entanto, o C # 3 introduziu inicializadores de objetos e coleções, o que significava que o padrão do construtor era praticamente inútil. Em uma linguagem sem inicializadores, pode-se implementar um construtor e usá-lo da seguinte maneira:

Vehicle v = new Vehicle.Builder()
                    .manufacturer("Toyota")
                    .model("Camry")
                    .year(1997)
                    .colour(CarColours.Red)
                    .addSpecialFeature(new Feature.CDPlayer())
                    .addSpecialFeature(new Feature.SeatWarmer(4))
                    .build();

Por outro lado, em C #, pode-se escrever:

var vehicle = new Vehicle {
                Manufacturer = "Toyota",
                Model = "Camry",
                Year = 1997,
                Colour = CarColours.Red,
                SpecialFeatures = new List<SpecialFeature> {
                    new Feature.CDPlayer(),
                    new Feature.SeatWarmer { Seats = 4 }
                }
              }

... eliminando a necessidade de um construtor, como visto no exemplo anterior.

Com base nesses exemplos, os construtores ainda são úteis em C # ou foram substituídos inteiramente pelos inicializadores?


are builders usefulpor que continuo vendo esse tipo de pergunta? Um Construtor é um padrão de design - com certeza, pode ser exposto como um recurso de linguagem, mas no final do dia é apenas um padrão de design. Um padrão de design não pode estar "errado". Pode ou não atender ao seu caso de uso, mas isso não faz com que todo o padrão esteja errado, apenas a aplicação dele. Lembre-se de que, no final do dia, os padrões de design resolvem problemas específicos - se você não enfrenta o problema, por que você tentaria resolvê-lo?
VLAZ 01/10/19

@vlaz Não estou dizendo que os construtores estão errados - na verdade, minha pergunta foi perguntada se havia algum caso de uso para construtores, pois os inicializadores são uma implementação do caso mais comum em que você usaria construtores. Obviamente, as pessoas responderam que um construtor ainda é útil para definir campos particulares, o que, por sua vez, respondeu à minha pergunta.
amigos estão dizendo sobre svbnet

3
@vlaz Para esclarecer: estou dizendo que os inicializadores substituíram amplamente o padrão "tradicional" de interface do construtor / fluido de escrever uma classe interna do construtor e depois escrever métodos de encadeamento dentro dessa classe que criam uma nova instância dessa classe pai. Eu estou não dizer que initialisers são um substituto para esse específico construtor padrão; Estou dizendo que os inicializadores economizam os desenvolvedores que precisam implementar esse padrão de construtor específico, exceto os casos de uso mencionados nas respostas, o que não torna o padrão do construtor inútil.
svbnet

5
@ vlaz Não entendo sua preocupação. "Você estava dizendo que um padrão de design não é útil" - não, Joe estava perguntando se um padrão de design é útil. Como essa é uma pergunta ruim, errada ou equivocada? Você acha que a resposta é óbvia? Não acho que a resposta seja óbvia; isso parece ser uma boa pergunta para mim.
Tanner Swett

2
@vlaz, os padrões singleton e localizador de serviço estão incorretos. Padrões de design errados podem estar errados.
David Arno

Respostas:


12

Conforme abordado por @ user248215, o verdadeiro problema é imutabilidade. O motivo pelo qual você usaria um construtor em C # seria manter a testemunha explícita de um inicializador sem precisar expor propriedades configuráveis. Não é uma questão de encapsulamento e é por isso que escrevi minha própria resposta. O encapsulamento é razoavelmente ortogonal, pois invocar um setter não implica o que o setter realmente faz ou o vincula à sua implementação.

A próxima versão do C #, 8.0, provavelmente introduzirá uma withpalavra - chave que permitirá que objetos imutáveis ​​sejam inicializados de forma clara e concisa, sem a necessidade de escrever construtores.

Outra coisa interessante que você pode fazer com os construtores, ao contrário dos inicializadores, é que eles podem resultar em diferentes tipos de objetos, dependendo da sequência de métodos chamados.

Por exemplo

value.Match()
    .Case((DateTime d) => Console.WriteLine($"{d: yyyy-mm-dd}"))
    .Case((double d) => Console.WriteLine(Math.Round(d, 4));
    // void

var str = value.Match()
    .Case((DateTime d) => $"{d: yyyy-mm-dd}")
    .Case((double d) => Math.Round(d, 4).ToString())
    .ResultOrDefault(string.Empty);
    // string

Apenas para esclarecer o exemplo acima, é uma biblioteca de correspondência de padrões que usa o padrão do construtor para criar uma "correspondência" especificando casos. Os casos são anexados chamando o Casemétodo que passa por uma função. Se valuefor atribuível ao tipo de parâmetro da função, é invocado. Você pode encontrar o código fonte completo no GitHub e, como os comentários XML são difíceis de ler em texto simples, aqui está um link para a documentação criada pelo SandCastle (consulte a seção Comentários )


Não vejo como isso não pôde ser feito, um inicializador de objeto, usando um IEnumerable<Func<T, TResult>>membro.
Caleth

Caleth @ Isso foi realmente algo que eu experimentei, mas há alguns problemas com essa abordagem. Ele não permite cláusulas condicionais (não mostradas, mas usadas e demonstradas na documentação vinculada) e não permite a inferência de tipo de TResult. Em última análise, a inferência de tipo tinha muito a ver com isso. Eu também queria que "parecesse" uma estrutura de controle. Também não queria usar um inicializador, pois isso permitiria a mutação.
Aluan Haddad

12

Os inicializadores de objetos exigem que as propriedades sejam acessíveis pelo código de chamada. Construtores aninhados podem acessar membros privados da classe.

Se você quisesse tornar Vehicleimutável (via tornar todos os setters privados), o construtor aninhado poderia ser usado para definir as variáveis ​​privadas.


0

Todos eles servem a propósitos diferentes !!!

Os construtores podem inicializar campos marcados readonly, além de membros privados e protegidos. No entanto, você é um pouco limitado no que pode fazer dentro de um construtor; evite passar thispara qualquer método externo, por exemplo, e evite chamar membros virtuais, pois eles podem ser executados no contexto de uma classe derivada que ainda não foi construída. Além disso, é garantido que os construtores são executados (a menos que o chamador esteja fazendo algo muito incomum); portanto, se você definir campos no construtor, o código no restante da classe poderá assumir que esses campos não serão nulos.

Os inicializadores são executados após a construção. Eles apenas permitem que você chame propriedades públicas; campos privados e / ou somente leitura não podem ser definidos. Por convenção, o ato de estabelecer uma propriedade deve ser bastante limitado, por exemplo, deve ser idempotente e ter efeitos colaterais limitados.

Os métodos do construtor são métodos reais e, portanto, permitem mais de um argumento e, por convenção, podem ter efeitos colaterais, incluindo a criação de objetos. Embora não possam definir campos somente leitura, eles podem praticamente fazer qualquer outra coisa. Além disso, os métodos podem ser implementados como métodos de extensão (como praticamente toda a funcionalidade LINQ).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.