У меня есть следующие отношения сущностей.
A {state: ON | OFF} => B {state: ON | OFF} => C {state: ON | OFF}
Таким образом, в этом случае B
является дочерним элементом A,
, а C
является дочерним элементом B.
. Более того, все они имеют общее свойство state
, которое может быть либо ON
, либо OFF
и по умолчанию равно OFF.
.
Дочерний элемент должен наследовать состояние родителя при установке значений, но он должен оставить текущее значение, если оно было задано явно.
Когда я устанавливаю состояние для родителя, это значение должно быть унаследовано дочерними элементами. Предполагая состояние по умолчанию OFF
, если моя операция set-state(ON, B),
, это должно установить состояние для объектов B
и C
(но не A
, поскольку это родитель), поэтому результирующее состояние должно быть:
A {state: OFF} => B {state: ON} => C {state: ON}
Поэтому, когда кто-то читает state
из C,
, он должен вернуться ON.
Начальное состояние:
A {state: OFF} => B {state: OFF} => C {state: OFF}
значения чтения:
read(state, A) => OFF
read(state, B) => OFF
read(state, C) => OFF
Операция1 set-state(ON, B)
результирующее состояние:
A {state: OFF} => B {state: ON} => C {state: ON}
значения чтения:
read(state, A) => OFF
read(state, B) => ON
read(state, C) => ON
Операция2 set-state(ON, A)
результирующее состояние:
A {state: ON} => B {state: ON} => C {state: ON}
значения чтения:
read(state, A) => ON
read(state, B) => ON
read(state, C) => ON
Операция3 set-state(ON, C)
результирующее состояние:
A {state: ON} => B {state: ON} => C {state: ON}
значения чтения:
read(state, A) => ON
read(state, B) => ON
read(state, C) => ON
Операция4 set-state(OFF, B)
результирующее состояние:
(поскольку операция 3 устанавливает состояние ON на C, она оставляет его включенным)
A {state: ON} => B {state: OFF} => C {state: ON}
значения чтения:
read(state, A) => ON
read(state, B) => OFF
read(state, C) => ON
Операция5 set-state(OFF, A)
результирующее состояние:
A {state: OFF} => B {state: OFF} => C {state: ON}
значения чтения:
read(state, A) => OFF
read(state, B) => OFF
read(state, C) => ON
Кто-то может установить состояние C
в ON
явно set-state(ON, C),
, и, поскольку C
уже назначено ON,
, это должно привести к NOOP, поскольку C
уже назначено ON
, однако, если B
возвращается к OFF
, обычно следует устанавливать его дочерний элемент (C
) также вернуться к OFF
. Тем не менее, поскольку была операция explicit
от C
до ON
, она должна оставить ее для ON.
В этом состоянии операция чтения state
of C должна вернуть ON
(это поведение также должно быть распространено на A
)
Я могу добавить поле к каждому свойству, чтобы отслеживать его явные и неявные состояния. Тем не менее, я использую краудсорсинг этой проблемы, чтобы посмотреть, есть ли более умный/элегантный способ справиться с ней, возможно, используя значение или что-то еще, что я мог упустить.
Тем не менее, поскольку была явная операция над C в ON, она должна оставить его в ON."
Это кардинально меняет проблемную область.
Вы не просто отслеживаете текущее состояние переключателей, вы отслеживаете операции с этими переключателями. Код, который вы показываете, отслеживает только текущее состояние, и он просто не может содержать информацию, которую вы хотите отслеживать.
По сути, щелчок переключателя C изменил переключатель C. Раньше он подчинялся своему родителю. Но теперь, когда его явно щелкнули, он стоит на своих собственных ногах и больше не зависит от своего родителя для определения своего состояния.
Здесь есть несколько возможных решений, но из-за абстрактного характера вашего примера трудно решить, какое из них лучше. Я выбрал самое простое решение, которое кажется подходящим.
Здесь я буду использовать C#, но основной ответ не зависит от языка.
По сути, переключатель может находиться в одном из трех состояний: отложенный по отношению к своему родителю, принудительно включенный, принудительно выключенный. Вместо булева числа это может быть представлено перечислением:
public enum State { DefersToParent, ForcedOn, ForcedOff }
Переключатель начинает работать в режиме отсрочки по отношению к своему родителю, но после того, как вы явно установите его значение, он перейдет только в один из вариантов принудительного включения, а не обратно в состояние отсрочки.
public class Switch
{
private Switch parent;
private State state = State.DefersToParent; // default value
public bool IsOn()
{
switch(state)
{
case State.ForcedOn:
return true;
case State.ForcedOff:
return false;
case State.DefersToParent:
return parent.IsOn();
}
}
public void Set(bool isOn)
{
state = isOn ? State.ForcedOn : State.ForcedOff;
}
}
Заметьте также, что в этом решении родители не листают своих детей. Если вы устанавливаете B, то C остается нетронутым.
Похоже, именно такого поведения вы и добиваетесь, и этого легче достичь, если родительские переключатели не связываются с дочерними, поскольку это может привести к тому, что они ошибочно переопределят явно установленное значение.
Я позволил себе немного доработать пример, чтобы учесть различия между переключателем верхнего уровня (у которого нет родителя) и всеми последующими дочерними переключателями.
public class Switch
{
// Top-level switches must have their own initial state,
// since they have no parent to rely on.
public Switch(bool isOn)
{
this.parent = null;
Set(isOn);
}
// Child switches must reference their parent,
// but you don't need to set their initial state
public Switch(Switch parent)
{
this.parent = parent;
this.state = State.DefersToParent;
}
private Switch parent;
private State state;
public bool IsOn()
{
switch(state)
{
case State.ForcedOn:
return true;
case State.ForcedOff:
return false;
case State.DefersToParent:
return parent.IsOn();
}
}
public void Set(bool isOn)
{
state = isOn ? State.ForcedOn : State.ForcedOff;
}
// If necessary, you could implement a reset which
// sets the switch back to deferring to its parent
// BUT this would not work for the top level switch!
public void Reset()
{
if (this.parent != null)
this.state = State.DefersToParent;
}
}