First of all, each element is responsible for its own state (information). In a good OOP design, an object can never be installed in an invalid state. You should at least try to prevent this.
For this, you cannot have public setters if one or more fields in combination are required.
In your example, Item not valid if orderId or itemId . Without this information, the order cannot be completed.
Therefore, you should implement this class as follows:
public class Item { public Item(int orderId, int itemId) { if (orderId <= 0) throw new ArgumentException("Order is required"); if (itemId <= 0) throw new ArgumentException("ItemId is required"); OrderId = orderId; ItemId = itemId; } public int OrderID { get; private set; } public int ItemID { get; private set; } public string ItemName { get; set; } }
See what I did there? I guaranteed that the element is in the correct state from the very beginning, forcing and checking the information directly in the constructor.
ItemName is just a bonus, you do not need to process the order.
If property developers are publicly available, it is easy to forget to specify as required fields, thereby getting one or more errors later when this information is processed. Forcing him to turn on, as well as verify the information that you most often break.
Order
The order object must ensure that its entire structure is valid. Thus, he must have a control over the information that he carries, including also the elements of the order.
if you have something like this:
public class Order { int OrderID; string OrderName; List<Items> OrderItems; }
You basically say: I have order items, but I don't care how much or what they contain. This is an invitation to errors later in the development process.
Even if you say something like this:
public class Order { int OrderID; string OrderName; List<Items> OrderItems; public void AddItem(item); public void ValidateItem(item); }
You are reporting something like: Please be nice, check the item first, and then add it via the Add method. However, if you have an order with identifier 1, someone can still make order.AddItem(new Item{OrderId = 2, ItemId=1}) or order.Items.Add(new Item{OrderId = 2, ItemId=1}) , so the order will contain invalid information.
imho a ValidateItem method is not included in Order , but in Item , since it is itself responsible for the correct state.
Best design:
public class Order { private List<Item> _items = new List<Item>(); public Order(int orderId) { if (orderId <= 0) throw new ArgumentException("OrderId must be specified"); OrderId = orderId; } public int OrderId { get; private set; } public string OrderName { get; set; } public IReadOnlyList<Items> OrderItems { get { return _items; } } public void Add(Item item) { if (item == null) throw new ArgumentNullException("item");
Now you have gained control over the entire order, if changes should be made to the list of elements, this should be done directly in the order object.
However, the item can still be changed without specifying the order. Someone can, for example, before order.Items.First(x=>x.Id=3).ApplyDiscount(10.0); , which would be fatal if the order had a cached Total field.
However, good design does not always do this 100% correctly, but it is a compromise between the code we can work with and the code that does everything correctly in accordance with principles and patterns.