Верните непрозрачный объект к абонеру, не нарушая безопасность типа
-
05-10-2019 - |
Вопрос
У меня есть метод, который должен вернуть снимок текущего состояния, а другой метод, который восстанавливает это состояние.
public class MachineModel
{
public Snapshot CurrentSnapshot { get; }
public void RestoreSnapshot (Snapshot saved) { /* etc */ };
}
Штат Snapshot
Класс должен быть полностью непрозрачным для вызывающего абонента - нет видимых методов или свойств - но его свойства должны быть видны в MachineModel
сорт. Я мог бы, очевидно, сделать это, по допь, то есть у CurrentSnapshot
Вернуть А.Н. object
, и имеют RestoreSnapshot
принять АН object
аргумент, который он бросает обратно к Snapshot
.
Но принудительное литье, как это заставляет меня чувствовать себя грязным. Какой лучший альтернативный дизайн, который позволяет мне быть как безопасными, так и непрозрачными?
Обновление с решением:
Я завел свою комбинацию принятого ответа и предложения о интерфейсах. То Snapshot
Класс был создан общественным абстрактным классом, с частной реализацией внутри MachineModel
:
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);
}
}
Потому что конструктор и методы Snapshot
являются internal
, абоненты снаружи сборки считают ее полностью непрозрачными и не могут наследовать от него. Абоненты внутри сборки могут позвонить Snapshot.Restore
скорее, чем MachineModel.Restore
, но это не большая проблема. Кроме того, на практике вы никогда не сможете реализовать Snapshot.Restore
без доступа к MachineModel
Частные члены, которые должны отговорить людей, пытаясь сделать это.
Решение
Вы можете обратить вспять зависимость и сделать снимок ребенка (вложенный класс) машиномеделия. Тогда снимок имеет только общественность (или внутреннее) Restore()
Способ, который принимает в качестве параметра экземпляр Machinemodel. Потому что снимок определяется как ребенок Machinemodel, он может видеть частные поля Machinemodel.
Чтобы восстановить состояние, у вас есть два варианта в примере ниже. Вы можете позвонить на Snapshot.restorestate (Machinemodel) или 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);
}
}
Пример:
MachineModel mm1 = new MachineModel();
MachineModel.Snapshot ss = mm1.CurrentSnapshot;
MachineModel mm2 = new MachineModel();
mm2.RestoreState(ss);
* Было бы немного иметь Snapshot.restorestate () как internal
И поставить все абоненты за пределами сборки, поэтому единственный способ сделать восстановление - через Machinemodel.Restorestate (). Но вы упомянули на ответе Джона, что в той же сборке будут звонящие в то же самое, поэтому нет особой точки.
Другие советы
Могу MachineModel
а также Snapshot
быть в одной и той же сборке, а также абоненты в другой сборке? Если так, Snapshot
может быть публичным классом, но с совершенно внутренними членами.
Я мог бы, очевидно, сделать это путем Dreaking, т.е. есть CURRORTSNAPSHOT вернуть объект, и у вас RESTORESSNAPSHOT принять аргумент объекта, который он бросил обратно к снижению.
Проблема в том, что кто-то может затем пройти экземпляр объекта, который не является Snapshot
.
Если вы представите интерфейс ISnapshot
Что не дает никаких методов, и существует только одна реализация, вы можете почти обеспечить безопасность типа по цене нисходящего.
Я почти говорю, потому что вы не можете полностью помешать кому-то создать еще одну реализацию ISnapshot
И пропустите его, что сломалось бы. Но я чувствую, что это должно обеспечить желаемый уровень скрытия информации.
Это старый вопрос, но я искал что-то очень похожее, и я оказался здесь, и между информацией, сообщаемой здесь, и некоторые другие, я придумал это решение, может быть, немного излишек, но таким образом, чтобы государственный объект полностью непрозрачен даже на уровне сборки
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);
}
}