首先,每个项目都对自己的状态(信息)负责。在良好的 OOP 设计中,对象永远不会被设置为无效状态。你至少应该尽量阻止它。
为了做到这一点,如果需要一个或多个字段组合,则不能使用公共设置器。
在您的示例中,如果Item 缺少orderId 或itemId,则它是无效的。如果没有这些信息,订单将无法完成。
因此,您应该像这样实现该类:
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; }
}
看看我在那里做了什么?我通过直接在构造函数中强制和验证信息来确保项目从一开始就处于有效状态。
ItemName 只是一个奖励,您不需要它来处理订单。
如果属性设置器是公开的,那么很容易忘记指定两个必填字段,从而在处理该信息时会出现一个或多个错误。通过强制包含它并验证信息,您可以更早地发现错误。
订购
订单对象必须确保它的整个结构是有效的。因此它需要控制它携带的信息,其中也包括订单项目。
如果你有这样的事情:
public class Order
{
int OrderID;
string OrderName;
List<Items> OrderItems;
}
您基本上是在说:我有订购商品,但我并不真正关心它们包含多少或包含什么。那是在开发过程中的后期招致错误。
即使你这样说:
public class Order
{
int OrderID;
string OrderName;
List<Items> OrderItems;
public void AddItem(item);
public void ValidateItem(item);
}
您正在传达类似以下内容:请先验证项目,然后通过 Add 方法添加它。但是,如果您有 id 为 1 的订单,仍然有人可以使用 order.AddItem(new Item{OrderId = 2, ItemId=1}) 或 order.Items.Add(new Item{OrderId = 2, ItemId=1}),从而使订单包含无效信息。
恕我直言,ValidateItem 方法不属于Order,而是属于Item,因为它自己有责任保持处于有效状态。
更好的设计应该是:
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");
//make sure that the item is for us
if (item.OrderId != OrderId) throw new InvalidOperationException("Item belongs to another order");
_items.Add(item);
}
}
现在您已经控制了整个订单,如果要对项目列表进行更改,则必须直接在订单对象中完成。
但是,仍然可以在订单不知情的情况下修改项目。例如,如果订单有缓存的 Total 字段,则有人可以发送到 order.Items.First(x=>x.Id=3).ApplyDiscount(10.0);,这将是致命的。
但是,好的设计并不总是 100% 正确地做到这一点,而是在我们可以使用的代码和根据原则和模式正确完成所有事情的代码之间进行权衡。