Restituire un oggetto opaco al chiamante senza violare tipo di sicurezza
-
05-10-2019 - |
Domanda
Ho un metodo che dovrebbe restituire una fotografia dello stato attuale, e un altro metodo che restituisce quello stato.
public class MachineModel
{
public Snapshot CurrentSnapshot { get; }
public void RestoreSnapshot (Snapshot saved) { /* etc */ };
}
La classe Snapshot
stato dovrebbe essere completamente opaca al chiamante - metodi né proprietà visibili - ma le sue proprietà devono essere visibili all'interno della classe MachineModel
. Potrei ovviamente fare questo downcasting, vale a dire Hanno CurrentSnapshot
restituire un object
, e hanno RestoreSnapshot
accettare un argomento object
che si getta di nuovo ad un Snapshot
.
Ma fusione forzata del genere mi fa sentire sporca. Qual è il miglior design alternativo che mi permette di essere sia di tipo-sicuro e opachi?
Aggiornamento con la soluzione :
I liquidata facendo una combinazione di risposta accettata e la suggestione sulle interfacce. La classe Snapshot
è stata fatta una classe astratta pubblica, con un MachineModel
implementazione all'interno privato:
public class MachineModel
{
public abstract class Snapshot
{
protected internal Snapshot() {}
abstract internal void Restore(MachineModel model);
}
private class SnapshotImpl : Snapshot
{
/* etc */
}
public void Restore(Snapshot state)
{
state.Restore(this);
}
}
Poiché il costruttore e metodi di Snapshot
sono internal
, chiamanti esterni al complesso vedono come un completamente opaco e non possono ereditare da esso. I chiamanti in seno all'Assemblea potevano chiamare Snapshot.Restore
piuttosto che MachineModel.Restore
, ma non è un grosso problema. Inoltre, in pratica non si poteva mai implementare Snapshot.Restore
senza accesso ai membri privati ??di MachineModel
, che dovrebbe dissuadere le persone dal cercare di farlo.
Soluzione
Si potrebbe invertire la dipendenza e fare Snapshot un bambino (classe annidata) di MachineModel. Poi istantanea ha solo pubblica (o interna) Metodo Restore()
che prende come parametro un'istanza di MachineModel. Perché Snapshot è definito come un bambino di MachineModel, può vedere i campi privati ??di MachineModel.
Per ripristinare lo stato, si hanno due opzioni nel seguente esempio. È possibile chiamare Snapshot.RestoreState (MachineModel) o MachineModel.Restore (Snapshot) *.
public class MachineModel
{
public class Snapshot
{
int _mmPrivateField;
public Snapshot(MachineModel mm)
{
// get mm's state
_mmPrivateField = mm._privateField;
}
public void RestoreState(MachineModel mm)
{
// restore mm's state
mm._privateField = _mmPrivateField;
}
}
int _privateField;
public Snapshot CurrentSnapshot
{
get { return new Snapshot(this); }
}
public void RestoreState(Snapshot ss)
{
ss.Restore(this);
}
}
Esempio:
MachineModel mm1 = new MachineModel();
MachineModel.Snapshot ss = mm1.CurrentSnapshot;
MachineModel mm2 = new MachineModel();
mm2.RestoreState(ss);
* Sarebbe neater avere Snapshot.RestoreState () come internal
e mettere tutti i chiamanti fuori del complesso, quindi l'unico modo per fare un ripristino è tramite MachineModel.RestoreState (). Ma lei ha citato sulla risposta di Jon che ci sarà chiamanti all'interno dello stesso assieme, quindi non c'è molto senso.
Altri suggerimenti
Can MachineModel
e Snapshot
essere nella stessa assemblea, e chiamanti in un assembly diverso? Se è così, Snapshot
potrebbe essere una classe pubblica, ma con i membri del tutto interne.
I potrebbe ovviamente fare questo downcasting, cioè Avere CurrentSnapshot restituire un oggetto, e hanno RestoreSnapshot accettare un oggetto argomento che si getta di nuovo ad un Snapshot.
Il problema è che qualcuno potrebbe poi passare un'istanza di un oggetto che non è Snapshot
.
Se si introduce un interfaccia ISnapshot
che espone alcun metodo, e solo esiste un'implementazione, si può quasi garantire tipo di sicurezza al prezzo di un abbattuto.
Dico quasi, perché non è possibile prevenire completamente a qualcuno di creare un'altra implementazione di ISnapshot
e passarlo, che si rompono. Ma mi sento come che dovrebbe fornire il livello desiderato di nascondere informazioni.
Questa è una vecchia questione, ma ero alla ricerca di qualcosa di molto simile e ho finito qui e tra le informazioni ha riportato qui e qualche altra mi si avvicinò con questa soluzione, forse è un po 'eccessivo, ma in questo modo l'oggetto di stato è completamente opaco, anche a livello di assieme
class Program
{
static void Main(string[] args)
{
DoSomething l_Class = new DoSomething();
Console.WriteLine("Seed: {0}", l_Class.Seed);
Console.WriteLine("Saving State");
DoSomething.SomeState l_State = l_Class.Save_State();
l_Class.Regen_Seed();
Console.WriteLine("Regenerated Seed: {0}", l_Class.Seed);
Console.WriteLine("Restoring State");
l_Class.Restore_State(l_State);
Console.WriteLine("Restored Seed: {0}", l_Class.Seed);
Console.ReadKey();
}
}
class DoSomething
{
static Func<DoSomething, SomeState> g_SomeState_Ctor;
static DoSomething()
{
Type type = typeof(SomeState);
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
}
Random c_Rand = new Random();
public DoSomething()
{
Seed = c_Rand.Next();
}
public SomeState Save_State()
{
return g_SomeState_Ctor(this);
}
public void Restore_State(SomeState f_State)
{
((ISomeState)f_State).Restore_State(this);
}
public void Regen_Seed()
{
Seed = c_Rand.Next();
}
public int Seed { get; private set; }
public class SomeState : ISomeState
{
static SomeState()
{
g_SomeState_Ctor = (DoSomething f_Source) => { return new SomeState(f_Source); };
}
private SomeState(DoSomething f_Source) { Seed = f_Source.Seed; }
void ISomeState.Restore_State(DoSomething f_Source)
{
f_Source.Seed = Seed;
}
int Seed { get; set; }
}
private interface ISomeState
{
void Restore_State(DoSomething f_Source);
}
}