A Price
object which depends on an updateable BasePrice
cannot be a value object, since value objects should be immutable (at least, by the book).
So if Price
and BasePrice
are both immutable, and Price
holds a reference to a BasePrice
, if one needs a price with a different base price, it will be necessary to create a new Price
object with a new BasePrice
passed in the constructor, so the old Price
object stays valid. However, if Price
and BasePrice
should both be properties of a Product
, this is probably not a good idea, since now the Price
object of the product might reference a different base price than the Product
itself.
Hence, when you want Price
and BasePrice
to be independent properties of your Product
, each one should not know anything about the other directly. Therefore, the constraint "Price must be between BasePrice and BasePrice+1000" makes sense only in the context of a Product
object. This constraint needs to be checked whenever a method like Product.SetPrice
or Product.SetBasePrice
is called.
Still, the business logic for checking against the base price can be part of the Price
object. Design the latter with a method IsInValidRange(BasePrice bp)
, and call it like this
class Product
{
BasePrice basePrice;
Price price;
void ChangePrice(Price newPrice)
{
if(!newPrice.IsInValidRange(basePrice))
throw new InvalidPriceException();
price = newPrice;
}
void ChangeBasePrice(BasePrice newBasePrice)
{
if(!price.IsInValidRange(newBasePrice))
throw new InvalidPriceException();
basePrice = newBasePrice;
}
}
I guess your issue lies in the the phrase "business logic stored in the value objects as constraints" - better replace "stored" by "implemented", this yields to the kind of solution I sketched above.