Classe com membros mutáveis ​​durante a criação, mas imutáveis ​​posteriormente


22

Eu tenho um algoritmo que cria uma coleção de objetos. Esses objetos são mutáveis ​​durante a criação, pois começam com muito pouco, mas são preenchidos com dados em locais diferentes dentro do algoritmo.

Após a conclusão do algoritmo, os objetos nunca devem ser alterados - no entanto, são consumidos por outras partes do software.

Nesses cenários, é considerado uma boa prática ter duas versões da classe, conforme descrito abaixo?

  • O mutável é criado pelo algoritmo, então
  • Após a conclusão do algoritmo, os dados são copiados em objetos imutáveis ​​que são retornados.

3
Você poderia editar sua pergunta para esclarecer qual é o seu problema / pergunta?
Simon Bergot

Você pode também estar interessado nesta questão: Como congelar um picolé em .NET (fazer uma imutável classe)
R0MANARMY

Respostas:


46

Talvez você possa usar o padrão do construtor . Ele usa um objeto 'construtor' separado com o objetivo de coletar os dados necessários e, quando todos os dados são coletados, ele cria o objeto real. O objeto criado pode ser imutável.


Sua solução foi a correta para meus requisitos (nem todos foram declarados). Após a investigação, os objetos retornados nem todos têm as mesmas características. A classe do construtor possui muitos campos anuláveis ​​e, quando os dados são construídos, escolhe qual das 3 classes diferentes gerar: elas são relacionadas entre si por herança.
Paul Richards

2
@Paul Nesse caso, se esta resposta resolveu seu problema, você deve marcá-lo como aceito.
Riking 27/07

24

Uma maneira simples de conseguir isso seria ter uma interface que permita ler propriedades e chamar apenas métodos somente leitura e uma classe que implemente essa interface, que também permite que você escreva essa classe.

Seu método que o cria, lida com o primeiro e, em seguida, retorna o último, fornecendo apenas uma interface somente leitura para interagir. Isso não requer cópia e permite que você ajuste facilmente os comportamentos que deseja que estejam disponíveis para o chamador, em vez do criador.

Veja este exemplo:

public interface IPerson 
{
    public String FirstName 
    {
        get;
    }

    public String LastName 
    {
        get;
    }
} 

public class PersonImpl : IPerson 
{
    private String firstName, lastName;

    public String FirstName 
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public String LastName 
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

class Factory 
{
    public IPerson MakePerson() 
    {
        PersonImpl person = new PersonImpl();
        person.FirstName = 'Joe';
        person.LastName = 'Schmoe';
        return person;
    }
}

A única desvantagem dessa abordagem é que alguém poderia simplesmente convertê-la na classe de implementação. Se fosse uma questão de segurança, simplesmente usar essa abordagem é insuficiente. Uma solução alternativa para isso é que você pode criar uma classe de fachada para agrupar a classe mutável, que simplesmente apresenta uma interface com a qual o chamador trabalha e não pode ter acesso ao objeto interno.

Dessa forma, nem mesmo o elenco o ajudará. Ambos podem derivar da mesma interface somente leitura, mas converter o objeto retornado fornecerá apenas a classe Facade, que é imutável, pois não altera o estado subjacente da classe mutável empacotada.

Vale ressaltar que isso não segue a tendência típica na qual um objeto imutável é construído de uma vez por todas através de seu construtor. Compreensivelmente, você pode estar tendo que lidar com muitos parâmetros, mas deve se perguntar se todos esses parâmetros precisam ser definidos antecipadamente ou se alguns podem ser introduzidos posteriormente. Nesse caso, um construtor simples com apenas os parâmetros necessários deve ser usado. Em outras palavras, não use esse padrão se estiver encobrindo outro problema no seu programa.


1
A segurança não é melhor retornando um objeto "somente leitura", porque o código que obtém o objeto ainda pode modificá-lo usando a reflexão. Até uma string pode ser modificada (não copiada, modificada no local) usando a reflexão.
MTilsted

O problema de segurança pode ser resolvido com classe particular
Esben Skov Pedersen

@Esben: Você ainda precisa lidar com o MS07-052: a execução do código resulta na execução do código . Seu código está sendo executado no mesmo contexto de segurança que o código, para que eles possam anexar um depurador e fazer o que quiserem.
27715 Kevin

Kevin1, você pode dizer isso sobre todos os encapsulamentos. Não estou tentando me proteger contra a reflexão.
Esben Skov Pedersen

1
O problema de usar a palavra "segurança" é que imediatamente alguém assume que o que eu digo ser a opção mais segura é equivalente à segurança máxima e às melhores práticas. Eu também nunca disse. Se você estiver entregando a biblioteca para alguém usar, a menos que seja ofuscado (e às vezes até ofuscado), você pode esquecer de garantir a segurança. No entanto, acho que todos podemos concordar com o fato de que, se você estiver mexendo com um objeto de fachada retornado usando reflexão, a fim de recuperar o objeto interno contido nele, não estará usando exatamente a biblioteca, pois ela deve ser usada.
294 Neil

8

Você pode usar o padrão Builder como o @JacquesB diz, ou alternativamente pensar por que é que esses objetos precisam ser mutáveis ​​durante a criação?

Em outras palavras, por que o processo de criá-los precisa se espalhar no tempo, em vez de passar todos os valores necessários para o construtor e criar instâncias de uma só vez?

Porque o Builder pode ser uma boa solução para o problema errado.

Se o problema é que você acaba com um construtor com 10 parâmetros e deseja mitigá-lo construindo o objeto pouco a pouco, isso pode indicar que o design está estragado e esses 10 valores devem ser " agrupado "/ agrupado em alguns objetos ... ou o objeto principal dividido em alguns objetos menores ...

Nesse caso, mantenha a imutabilidade até o fim, apenas melhore o design.


Seria difícil criar tudo de uma vez, já que o código realiza várias consultas ao banco de dados para obter os dados; Ele compara dados em diferentes tabelas e em diferentes bancos de dados.
Paul Richards
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.