fomi
fomi

Reputation: 159

How to enforce this one-to-many constraint in C#?

I have 2 classes: Box and Apple.

A Box contains Apples. An apple can only ever belong to one box. An apple must belong to a box. Each apple has a position in the box. This could be managed either by Apple or by Box. An apple also contains a bunch of other properties not relevant to Box. An apple can be moved from one box to another.

But how can I enforce the constraints above?

The only idea I have is that a box keeps track of the position of an apple and also have a static dictionary mapping an apple to a board, but this seems like not good design?

Upvotes: 0

Views: 97

Answers (5)

Claudio Valerio
Claudio Valerio

Reputation: 2342

Disclaimer: as stated by Gabriel C in his answer, ideal design would be one which respects the Single Responsability Principle. Your question though, if interpreted licterally, constraints to only 2 classes, which should contain all the business logic to handle apple boxing. Here my solution with this specific design.

Apple class:

public class Apple
{
    public enum AppleKind
    {
        RedDelicious,
        GrannySmith,
        Golden,
        PinkLady
    }

    public AppleKind Kind { get; }
    public Apple(AppleKind kind)
    {
        this.Kind = kind;
    }

    public Box Box { get; private set; }

    public void MoveToBox(Box box, int position)
    {
        if (box == null) throw new ArgumentNullException(nameof(box));
        int oldPosition = -1;
        var oldBox = this.Box;
        if (oldBox != null)
        {
            oldPosition = oldBox.GetApplePosition(this);
            oldBox.RemoveApple(this);
        }
        var moved = false;
        try
        {

            this.Box = box;
            Box.SetApplePosition(this, position);
            moved = true;
        }
        finally
        {
            if (!moved)
            {
                if (oldBox != null)
                    oldBox.AddApple(this, oldPosition);
                else
                    this.Box = null;
            }
        }
    }

    public void MoveToHand()
    {
        this.Box = null;
    }
}

Box class:

public class Box
{
    private Apple[] _apples;

    public IEnumerable<Apple> Apples
    {
        get
        {
            return _apples.Clone() as Apple[];
        }
    }

    public Box(int maxPositions)
    {
        _apples = new Apple[maxPositions];
    }

    public void RemoveApple(Apple apple)
    {
        var position = GetApplePosition(apple);
        if (position > -1)
        {
            _apples[position] = null;
            apple.MoveToHand();
        }
    }

    public int GetApplePosition(Apple apple)
    {
        return Array.IndexOf(_apples, apple);
    }

    public void SetApplePosition(Apple apple, int position)
    {
        // if apple is already in position, do nothing
        if (object.ReferenceEquals(apple, _apples[position])) return;
        _apples[position] = apple;
    }

    public void AddApple(Apple apple, int position)
    {
        if (_apples[position] != null) throw new ArgumentException("Compartment already occupied", nameof(position));
        if (apple == null) throw new ArgumentNullException(nameof(apple));
        if (position < 0 || position > _apples.Length - 1) throw new IndexOutOfRangeException("Cannot add apple outside compartments");
        apple.MoveToBox(this, position);
    }
}

Please note that Box.Apples property returns a clone of the underlaying array, preventing client code from breaking Apple-Box relationship by manipulating array elements. Apple.Box has private setter for same reason.

Clone full code, including tests, at: https://github.com/cvalerio/TossAnAppleToYourWitcher

Upvotes: 2

Stanley
Stanley

Reputation: 36

The easiest way to deal with this is to have the apple class have a reference to the box it belongs to. Should you need to get all the apples in a box all you need to do is get the id of the box and search all apples that have this id in its FK_BoxID reference.

Should an apple move to another box. All you have to do is update the FK_BoxID in the apple class to move it.

See UML example

Upvotes: 1

svladimirrc
svladimirrc

Reputation: 234

I think a simple way is to create a AppleBoxManager class that contains a list of box instances and a dictionary to maps apples in the box items with surrogate keys

The surrogate key is a composition of boxId+appleId, when you add an apple in the box adds the key to the dictionary, when you change the apple to another box, remove the key and add the new key, the key prevent duplicity. You should to manage the logic errors in add, remove or change apple to box methods. I supose that all box and apples have a unique id.

A similar approach can be applied in the box class with the positions.

Upvotes: 1

Gabriel Cappelli
Gabriel Cappelli

Reputation: 4170

I would create a third class, let's call it AppleBoxer. This class would be responsible for adding\removing\moving apples from boxes.

AppleBoxer would have a List of all Box, when you request to add an Apple to any given Box it will check if that apple is already in another Box before adding.

The free\occupied positions in the Box I would keep on the Box class.

I think the OOP principle here is the Single Responsibility Principle.

Apple is not responsible for knowing in which box it is in.

Box is not responsible for knowing about the apples of other boxes.

Upvotes: 2

Eugene
Eugene

Reputation: 1533

You can create a class ApplePosition, which contains Apple and position. Box will have list of ApplePosition

Upvotes: 2

Related Questions