Un modo semplice per ottenere ciò sarebbe avere un'interfaccia che permetta 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 poi restituisce quest'ultimo fornendo solo un'interfaccia di sola lettura con cui interagire. Ciò non richiederebbe la copia e consente di ottimizzare facilmente i comportamenti che si desidera rendere disponibili al chiamante anziché 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, allora semplicemente usare questo approccio è insufficiente. Una soluzione per questo è che puoi creare una classe facade per avvolgere la classe mutabile, che presenta semplicemente un'interfaccia che il chiamante funziona con e 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 lanciare l'oggetto restituito ti darà solo la classe Facade, che è immutabile in quanto non cambia lo stato sottostante della classe mutevole incapsulata.
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 possono essere introdotti in seguito. In tal caso, dovrebbe essere utilizzato un semplice costruttore con solo i parametri richiesti. In altre parole, non utilizzare questo modello se copre un altro problema nel programma.