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.

salparadise
  • 173
  • 6
  • 1
    I would work out the complete domain design rules for the desired behavior for A, B, C, before considering implementation details (field/properties, inheritance...), rather than experimenting in code to see if you end up getting something acceptible. Maybe consider using [TDD](https://en.wikipedia.org/wiki/Test-driven_development) as a methodology. – Erik Eidt Nov 30 '22 at 17:45
  • @ErikEidt Are the domain rules not clear from the question? If anything is missing to make clearer I can add it. – salparadise Nov 30 '22 at 22:50
  • (1) What happens if someone does `set-state(ON, A)`, after the explicit `set-state(ON, B)`, but before the explicit `set-state(ON, C)` -- should it skip `B` but do `C`, or skip `B` and `C`? (2) Is there a way to restore the dependence of `C` on `B` once `set-state(ON, C)` is done -- if so, how; if not then why is `C` an "inheriting child" of `B`? – Erik Eidt Nov 30 '22 at 22:53
  • `this value needs to be inherited by the children` and `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, it should leave it to ON.` are mutually exclusive. Aren't they? After the explicit set on C, C is no longer exposed to the hierarchy. To set `C` state again, there's no way other than explicitly accessing `C` and setting it. it seems a dysfunction given the first premise. – Laiv Dec 01 '22 at 12:17
  • If my assumptions in the above comment aren't right and you need to prevent the propagation downstream of certain changes. Have you considered versioning each element of the hierarchy? Like, if `C` version `>=` parent version, then skip? – Laiv Dec 01 '22 at 12:28
  • @ErikEidt I added domain rules and examples to hopefully help clarify the goal (if it doesn't, let me know) Laiv hope this clarifies your questions as well. Versioning is very interesting and it makes sense, let me chew on that. – salparadise Dec 01 '22 at 19:28
  • @Laiv: My interpretation is that inheriting the state from the parent only applies when the child has not been explicitly given a state directly. Once it has been given a state directly, the value inheritance ceases to be. At least, that is what I understand from how it is explained. – Flater Dec 02 '22 at 00:44
  • Operation 3 and 4 are contradictory. Op 3 `set-state(C, ON)` and OP 4 `set-state(B, OFF)`... and the result is C back to OFF? Didn't we agree that once a child is set directly, it's no longer affected by its parent state changes? – Laiv Dec 02 '22 at 08:15
  • @Laiv yes you are right. Fixing this. – salparadise Dec 03 '22 at 00:04

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
  • 44,596
  • 8
  • 88
  • 122
  • I was about to upvote your answer because it's, essentially, similar to the one I had in mind but I have read the question again because of the edition and I found that the examples given as `Op3` and `Op4` defeat everything we assumed so far. Is worth checking if these 2 operations fit into your solution. I think they don't, but I could be missed something. Right now I'm less confident about the requirements than I was at the beginning – Laiv Dec 02 '22 at 08:23
  • 1
    @Laiv It's a bit of a mess tbh. Op4 defines a different resulting state than what is read, which just causes confusion. What's the point of having a certain state if it's going to be spitting out different values anyway. This is looking like an XY problem where OP's current attempted solution is distracting from the actual behavior they want. – Flater Dec 02 '22 at 09:29
  • Looks like it was a typo. Here goes my vote – Laiv Dec 03 '22 at 11:07