Fagioli immutabili in Java
-
08-07-2019 - |
Domanda
Sono molto curioso della possibilità di fornire immutabilità per i java bean (per bean qui intendo classi con un costruttore vuoto che fornisce getter e setter per i membri). Chiaramente queste classi non sono immutabili e dove vengono utilizzate per trasportare valori dal livello dati questo sembra un vero problema.
Un approccio a questo problema è stato menzionato qui in StackOverflow chiamato "modello a oggetti immutabili in C #" dove l'oggetto viene congelato una volta creato completamente. Ho un approccio alternativo e vorrei davvero sentire le opinioni delle persone su di esso.
Il modello coinvolge due classi Immutable e Mutable dove Mutable e Immutable implementano entrambe un'interfaccia che fornisce metodi bean non mutanti.
Ad esempio
public interface DateBean {
public Date getDate();
public DateBean getImmutableInstance();
public DateBean getMutableInstance();
}
public class ImmutableDate implements DateBean {
private Date date;
ImmutableDate(Date date) {
this.date = new Date(date.getTime());
}
public Date getDate() {
return new Date(date.getTime());
}
public DateBean getImmutableInstance() {
return this;
}
public DateBean getMutableInstance() {
MutableDate dateBean = new MutableDate();
dateBean.setDate(getDate());
return dateBean;
}
}
public class MutableDate implements DateBean {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public DateBean getImmutableInstance() {
return new ImmutableDate(this.date);
}
public DateBean getMutableInstance() {
MutableDate dateBean = new MutableDate();
dateBean.setDate(getDate());
return dateBean;
}
}
Questo approccio consente di costruire il bean usando la riflessione (secondo le consuete convenzioni) e ci consente anche di convertirci in una variante immutabile alla più vicina opportunità. Sfortunatamente esiste chiaramente una grande quantità di boilerplate per bean.
Sono molto interessato a sentire l'approccio di altre persone a questo problema. (Mi scuso per non aver fornito una buona domanda, alla quale si può rispondere anziché discutere :)
Soluzione
Penso che userei il modello di delega - creare una classe ImmutableDate con un singolo membro DateBean che deve essere specificato nel costruttore:
public class ImmutableDate implements DateBean
{
private DateBean delegate;
public ImmutableDate(DateBean d)
{
this.delegate = d;
}
public Date getDate()
{
return delegate.getDate();
}
}
Se mai dovessi forzare l'immutabilità su un DateBean d, ho appena immutato la nuova ImmutableDate (d) su di esso. Avrei potuto essere intelligente e assicurarmi di non delegare il delegato, ma tu hai avuto l'idea. Ciò evita il problema di un client che tenta di inserirlo in qualcosa di mutabile. Questo è molto simile a JDK con Collections.unmodifiableMap () ecc. (In quei casi, tuttavia, le funzioni di mutazione devono ancora essere implementate e sono codificate per generare un'eccezione di runtime. Molto più semplice se si dispone di un'interfaccia di base senza il mutatori).
Ancora una volta è un noioso codice boilerplate ma è il genere di cose che un buon IDE come Eclipse può generare automaticamente per te con pochi clic del mouse.
Se è il genere di cose che finisci per fare a molti oggetti di dominio, potresti prendere in considerazione l'uso di proxy dinamici o forse anche AOP. Sarebbe relativamente semplice quindi creare un proxy per qualsiasi oggetto, delegando tutti i metodi get e intercettando o ignorando i metodi impostati come appropriato.
Altri suggerimenti
Alcuni commenti (non necessariamente problemi):
- La classe Date è essa stessa mutabile, quindi la stai copiando correttamente per proteggere l'immutabilità, ma personalmente preferisco convertirla in long nel costruttore e restituire una nuova data (longValue) nel getter.
- Entrambi i tuoi metodi getWhateverInstance () restituiscono DateBean che richiederà il cast, potrebbe essere un'idea cambiare l'interfaccia per restituire il tipo specifico.
- Detto tutto ciò che sarei propenso ad avere solo due classi, una mutabile e una immutabile, condividendo un'interfaccia comune (cioè solo get) se appropriato. Se pensi che ci saranno molte conversioni avanti e indietro, aggiungi un costruttore di copie ad entrambe le classi.
- Preferisco le classi immutabili per dichiarare i campi come final per fare in modo che anche il compilatore imponga l'immutabilità.
per es.
public interface DateBean {
public Date getDate();
}
public class ImmutableDate implements DateBean {
private final long date;
ImmutableDate(long date) {
this.date = date;
}
ImmutableDate(Date date) {
this(date.getTime());
}
ImmutableDate(DateBean bean) {
this(bean.getDate());
}
public Date getDate() {
return new Date(date);
}
}
public class MutableDate implements DateBean {
private long date;
MutableDate() {}
MutableDate(long date) {
this.date = date;
}
MutableDate(Date date) {
this(date.getTime());
}
MutableDate(DateBean bean) {
this(bean.getDate());
}
public Date getDate() {
return new Date(date);
}
public void setDate(Date date) {
this.date = date.getTime();
}
}
Uso interfacce e casting per controllare la mutabilità dei bean. Non vedo una buona ragione per complicare i miei oggetti di dominio con metodi come getImmutableInstance ()
e getMutableInstance ()
.
Perché non usare solo l'eredità e l'astrazione? per es.
public interface User{
long getId();
String getName();
int getAge();
}
public interface MutableUser extends User{
void setName(String name);
void setAge(int age);
}
Ecco cosa farà il client del codice:
public void validateUser(User user){
if(user.getName() == null) ...
}
public void updateUserAge(MutableUser user, int age){
user.setAge(age);
}
Risponde alla tua domanda?
YC