Pretty simple setup: I’ve got a Cart
and the cart has Items
. I’ve got many varying types of “products” that can’t really be boiled down into one thing or even a common ancestor, so I’ve instead decided to simply create a representation of the product as a CartItem
, which contains all the information from the product needed to make the sale.
That’s where the question comes in. I could handle this in three ways that I see. For these examples we’ll just say I have two types of “products”: Widget
s and Gizmo
s. Again, these two things are so vastly different that it doesn’t make sense for them to inherit from any base class.
1) Both a Widget
and a Gizmo
have the ability to create a Cart Item
to represent themselves, but this requires both to have knowledge of CartItem
and any change to CartItem
will require me to touch both Widget
and Gizmo
(not ideal, obviously)
2) A CartItem
can create itself from either a Widget
or a Gizmo
. This would essentially be a factory pattern and would require CartItem
to have knowledge of both Widget
and Gizmo
. This is better than #1 in that I don’t have to make changes to every kind of “product” if I change CartItem
, but I do have to remember that if I change a particular “product” then I must change the factory method of CartItem
for that. (still not ideal, because Murphy says I will forget).
3) Use something like the controller as the “glue”. WidgetController
and GizmoController
will have AddToCart
actions that translate a Widget
or Gizmo
, respectively, into a CartItem
. This really sort of presents the same problems as #1, in that if CartItem
changes, I need to update all the “product” controllers, and in fact mixes in the problems of #2, in that I must remember to always maintain this mapping if something on either side changes. However, it feels more like a job for the controller: it’s oriented for dealing with multiple types, whereas it just feels wrong to mix in types at the model level.
4) Three that I can see; #4 would be something I have yet to consider that one of you kind individuals may happen to suggest.
I’m open to any comments here. If I’m missing something completely obvious, feel free to call me a moron and point how just how big a moron I am. I like feeling stupid; that’s when I’m learning ;).
1
The obvious simple solution is to have both Gizmo
and Widget
implement ICartItem
, which would be an interface containing exactly what it takes for something to be represented in a cart, and nothing more (e.g., unit price, quantity, unit, description). Gizmo
and Widget
could have radically different ways of coming up with these, as long as they implement the interface correctly.
The obvious downside with the obvious simple solution is that now both Gizmo
and Widget
have to know a few things about shopping carts, which kind of violates the Single Responsibility principle. If you feel this is happening, consider splitting things up: Gizmo
and Widget
are completely unrelated, without any shared functionality whatsoever; GizmoCartItem
and WidgetCartItem
both implement the ICartItem
interface and know how handle a Gizmo
or a Widget
as a cart item; the shopping cart still deals strictly with ICartItem
s; and you probably want some kind of factory that creates ICartItem
s for Gizmo
s and Widget
s so that you never have to touch the glue classes directly. With this design, the Gizmo doesn’t know anything about shopping carts, nor about Widgets; the shopping cart doesn’t know anything about Gizmos; and the cart item glue know just enough about both Gizmos and shopping carts to interface them.
1
It really smells to me like you want an ICartItem interface. If it’s contract needs changing, every single implementer will break the build until they all have been updated appropriately, so it’ll keep you honest.
If you’re in a dynamically typed language however then I would say the same thing except as an interface exists to be a contract that keeps objects honest, in dynamically typed languages the only technique you have available to keep your objects honest is run-time tests, so I’d write some tests that ensure all the objects you expect to implement the ICartItem interface actually implement all of it’s parts with expected signatures. If you’re in a static-typed language though then you get these guarantees for free at compile-time.
I think all 3 you’re mentioning is thinking too much in the framework of common objects, when common contracts are lighter weight while maintaining greater compile-time guarantees. Common objects are for when you have shared functionality you don’t want to repeat, but in your situation you’re claiming zero logical shared functionality. You just want to treat different types like the same type, that’s the definition of polymorphism and the entire singular purpose of interfaces.
1
It depends a little on how complex it is to create a CartItem
, but I would either go with option (1), or option (1) that delegates the actual creation to another class.
(2) violates the open-closed principle – if you are to add a new third product type Gadget
you would have to change CartItem
so it can create itself from a Gadget
as well.
(3) is less reusable than having it in the model. If you need a new controller that serves a different view you would be tempted to call methods from the other WidgetController
just to create a CartItem
.
Also, I don’t think your objections to #1 is justified. I think it’s natural that a product knows how to create a CartItem
of itself. But if you think it seems smelly, perhaps it is more natural to have a addToCart(cart, quantity)
method in Widget
and Gizmo
instead, hiding where the actual CartItem
is created?
I would prefer option (2) but put the factory in a separate class. This way you can avoid polluting your products with information about how to make cartitems. If the product changes the factory doesn’t really have to change like you have mentioned. Only if the construction of the cartitem changes, which is exactly what the factory is for.
1