Reputation: 159
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
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
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.
Upvotes: 1
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
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
Reputation: 1533
You can create a class ApplePosition, which contains Apple and position. Box will have list of ApplePosition
Upvotes: 2