Моделирование свойств связанных сущностей

Моделирование свойств связанных сущностей
Моделирование свойств связанных сущностей - kai_wenzel @ Unsplash

У меня есть следующие отношения сущностей.

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 остается нетронутым.

  • Если C находится в состоянии отсрочки, то он будет полагаться на состояние 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;
    }
}


LetsCodeIt, 18 января 2023 г., 13:00