Un modo semplice per raggiungere questo obiettivo sarebbe avere un'interfaccia che consenta di leggere le proprietà e chiamare solo i metodi di sola lettura e una classe che implementa quell'interfaccia che consente anche di scrivere quella classe.
Il tuo metodo che lo crea, si occupa del primo e quindi restituisce il secondo fornendo solo un'interfaccia di sola lettura con cui interagire. Ciò non richiederebbe alcuna copia e ti consentirà di mettere a punto facilmente i comportamenti che desideri siano disponibili per il chiamante rispetto al creatore.
Prendi questo esempio:
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;
}
}
L'unico svantaggio di questo approccio è che si potrebbe semplicemente lanciarlo nella classe di implementazione. Se fosse una questione di sicurezza, l'utilizzo di questo approccio non è sufficiente. Per ovviare a questo problema, è possibile creare una classe di facciata per avvolgere la classe mutabile, che presenta semplicemente un'interfaccia con cui il chiamante lavora e che non può avere accesso all'oggetto interno.
In questo modo, nemmeno il casting ti aiuterà. Entrambi possono derivare dalla stessa interfaccia di sola lettura, ma il cast dell'oggetto restituito ti darà solo la classe Facade, che è immutabile in quanto non cambia lo stato sottostante della classe mutabile avvolta.
Vale la pena ricordare che questo non segue la tendenza tipica in cui un oggetto immutabile viene costruito una volta per tutte attraverso il suo costruttore. Comprensibilmente, potresti dover gestire molti parametri, ma dovresti chiederti se tutti questi parametri devono essere definiti in anticipo o se alcuni possono essere introdotti in un secondo momento. In tal caso, dovrebbe essere utilizzato un semplice costruttore con solo i parametri richiesti. In altre parole, non utilizzare questo modello se sta nascondendo un altro problema nel programma.