1

I have the following entity relationship.

A {state: ON | OFF} => B {state: ON | OFF} => C {state: ON | OFF}

So, in this case, B is a child of A, and C is a child of B. Moreover, they all share a property called state that can be either ON or OFF and defaults to OFF.

Domain rules

A child must inherit the state of the parent when setting the values, but it must leave the current value if it was set explicitly.

When I set the state to the parent, this value needs to be inherited by the children. Assuming a default state of OFF if my Operation is set-state(ON, B), this should set the state to entities B and C (but not A since this is the parent) so the resulting state should be:

A {state: OFF} => B {state: ON} => C {state: ON}

So when someone reads the state of C, it should return ON.

Examples

Initial state:

A {state: OFF} => B {state: OFF} => C {state: OFF}

reading values:

read(state, A) => OFF
read(state, B) => OFF
read(state, C) => OFF

Operation1 set-state(ON, B)

resulting state:

A {state: OFF} => B {state: ON} => C {state: ON}

reading values:

read(state, A) => OFF
read(state, B) => ON
read(state, C) => ON

Operation2 set-state(ON, A)

resulting state:

A {state: ON} => B {state: ON} => C {state: ON}

reading values:

read(state, A) => ON
read(state, B) => ON
read(state, C) => ON

Operation3 set-state(ON, C)

resulting state:

A {state: ON} => B {state: ON} => C {state: ON}

reading values:

read(state, A) => ON
read(state, B) => ON
read(state, C) => ON

Operation4 set-state(OFF, B)

resulting state:

(since operation 3 set the state to ON on C it leaves it ON)

A {state: ON} => B {state: OFF} => C {state: ON} 

reading values:

read(state, A) => ON
read(state, B) => OFF
read(state, C) => ON

Operation5 set-state(OFF, A)

resulting state:

A {state: OFF} => B {state: OFF} => C {state: ON}

reading values:

read(state, A) => OFF
read(state, B) => OFF
read(state, C) => ON

Problem

Someone can set the state of C to ON explicitly set-state(ON, C), and since C is already assigned to ON, this should result in a NOOP since C is already assigned to ON however if B goes back to OFF it usually should set its child(C) back to OFF as well. Still, since there was an explicit operation on C to ON, it should leave it to ON. In this state, a read operation of state of C should return ON (This behavior must be extended to A as well)

Potential Solution

I can add a field to each property to track its explicit and implicit states. However, I am crowdsourcing this problem to see if there is a more clever/elegant way to handle it, maybe using the value or something else I could be missing.

1 Answers1

2

Still, since there was an explicit operation on C to ON, it should leave it to ON."

This dramatically changes the problem domain.

You're not just tracking the current state of the switches, you're tracking the operations on those switches. The code you show only tracks the current state, and it's simply not possible to contain the kind of information you want to track.

In effect, flicking the C switch changed the C switch. Before, it deferred to its parent. But now that it has been explicitly flicked, it stands on its own two legs and it no longer defers to its parent to define its state.

There are several possible solutions here, but the abstract nature of your example makes it hard to decide which is better. I've opted for the simplest solution that seems appropriate.

I'll be using C# here but the core answer is language agnostic.

Essentially, a switch can be in one of three states: deferring to its parent, forced on, forced off. Instead of a boolean, this can be represented by an enum:

public enum State { DefersToParent, ForcedOn, ForcedOff }

A switch starts off as deferring to its parent, but once you've explicitly set its value, it only goes to one of the forced options, not back to the deferring state.

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;
}

}

Note also that in this solution parents don't flick their children. If you set B, then C remains untouched.

  • If C is in a deferring state, it will inherently rely on the state of B.
  • If C is not in a deferring state, it will report its own state.

This seems to be the behavior you're after, and this is easier to achieve if you parent switches don't mess with their child switches, because this can cause them to wrongly override an explicitly set value.


I've taken the liberty to flesh out the example a bit more, to account for the differences between the top level switch (which has no parent) and all of the subsequent child switches.

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;
}

}

Flater
  • 58,824