- Home
- LLBLGen Pro
- Architecture
Class Hierarchy Design
Joined: 01-Apr-2005
Hi there,
Lets say I have a 'Widget' class and each widget instance has a reference to a 'WidgetCategory' instance. Some categories of Widget require that the widget instance holds extra data. So I could create a sub-class of Widget with extra fields which could be used when a Widget is assigned one of these special categories.
I can't really think of another way of doing this but I can see a problem with this design in that a Widget instance's type may be invalid for the WidgetCategory that its been assigned.
This may be because the instances have been set up incorrectly to begin with or because a Widget instance's category changes at runtime. In this case, the Widget instance might need to change its own type but of course its not possible to change an object's type once its been created.
So the question is, how does one include this extra data when its required by the category?
Cheers, WT.
Joined: 21-Aug-2005
Thinking of it from the other side.
I think you should subType the WidgetCategories, to have a physical relation from the SpecialCategory to the SpecialWidget.
And since an entity can't chane its type once created, you'll have to destroy it nd copy the data to a new instance of the new type, when you need to switch the type.
Joined: 04-Feb-2004
Thinking from a pure OO standpoint, it soulds like a Widget has attributes. It also sounds like the the attributes for a given widget are driven by the categorization of the widget.
So, if you want to decouple how the attributes of a widget are managed it sounds like you could use a decorator.
http://www.dofactory.com/Patterns/PatternDecorator.aspx
If youre not concerned with an OO solution, and youre speaking purely ORM and LLBLGen, then stop reading.
However, if youre still here, the code below shows a simple implementation of the decorator.
namespace CustomAttributes {
class Program {
static void Main(string[] args) {
Widget widget1 = new Widget() { Category = new Category() { Name = "Shoes" },
Name = "MyWidget1",Attributes = new List<CustomAttribute>()};
Widget widget2 = new Widget() { Category = new Category() { Name = "Socks" },
Name = "MyWidget2",Attributes = new List<CustomAttribute>()};
Console.WriteLine("Decorating widget1...");
AttributeDecorator decorator = new AttributeDecorator(widget1);
decorator.LoadAttributes();
foreach(CustomAttribute attribute in widget1.Attributes) {
Console.WriteLine("{0}, {1}",attribute.Name,attribute.Value);
}
Console.WriteLine();
Console.WriteLine("Decorating widget2...");
decorator = new AttributeDecorator(widget2);
decorator.LoadAttributes();
foreach(CustomAttribute attribute in widget2.Attributes) {
Console.WriteLine("{0}, {1}",attribute.Name,attribute.Value);
}
Console.ReadLine();
}
}
public class Category {
public string Name { get; set; }
}
public class CustomAttribute {
public string Name { get; set; }
public string Value { get; set; }
}
public class Widget {
public Widget() {
Category = new Category();
Attributes = new List<CustomAttribute>();
}
public string Name { get; set; }
public Category Category { get; set; }
public List<CustomAttribute> Attributes { get; set; }
}
public abstract class DecoratorBase : Widget {
protected Widget widget;
public DecoratorBase(Widget widget) {
this.widget = widget;
}
}
public class AttributeDecorator:DecoratorBase {
public AttributeDecorator(Widget widget):base(widget) {
}
public virtual void LoadAttributes() {
switch (widget.Category.Name)
{
case "Socks":
widget.Attributes.Add(new CustomAttribute() { Name = "Type",Value = "Tube" });
widget.Attributes.Add(new CustomAttribute() { Name = "Color",Value = "White w/ Green Stripes" });
break;
case "Shoes":
widget.Attributes.Add(new CustomAttribute() { Name = "Size",Value = "10" });
widget.Attributes.Add(new CustomAttribute() { Name = "Color",Value = "Black" });
widget.Attributes.Add(new CustomAttribute() { Name = "Material",Value = "Leather" });
widget.Attributes.Add(new CustomAttribute() { Name = "Style",Value = "Boots" });
break;
}
}
}
}
Joined: 01-Apr-2005
One thing I think is missing from your decorator implementation is per widget values for the attributes. I suppose that in order to have this, then in the db there'd be a table of category attributes where each attribute referenced a category. Then there'd be a table of values where each value would reference an attributes and a widget. If one changed a widget's category, then surely one would also need to make sure that the values for that widget were also deleted?
Anyhow, I reckon that the decorator/attributes pattern maps to an EAV db design where as using an inheritance hierarchy maps to an exploded schema. Please see...
http://weblogs.sqlteam.com/davidm/articles/12117.aspx
I think the problem I've been having is trying to mix the two in the same design - EAV with exploded. So lets say I wanted to go with exploded, then this would still leave me with a problem.
Where in the db would I store class data? i.e. values for static's and constants? For a start there should be a table of these values, perhaps one row per class but in order not to mix the db designs up again, I shouldn't have a widget reference a category (or in this case a class).
My idea is that a method in the widget base class would use the concrete class type that its running in using reflection to look up the values in the db for that class type. How does this sound?
Joined: 04-Feb-2004
My idea is that a method in the widget base class would use the concrete class type that its running in using reflection to look up the values in the db for that class type. How does this sound?
This sounds slow and probalby overkill.
From my limited perspective of your system, you have some attributes to load. The attributes to be loaded are based on category (if I understood you correctly.)
The underlying schema doesnt matter in my sample. So, how you store your data in the DB doesnt matter. Lets say you have an abstract factory, that chooses which decorator to create, based on category. If the category of the widget is "Socks" you return the socks decorator, and set the attributes on the widget. The socks decorator knows how to get its attributes from whatever your "Socks attribute" schema is (could be EAV, could be exploded, could be EAV). Repeat the process for any other category that you may want to implement.
You could use reflection to dynamically create decorators at runtime, but this seems to be over kill.
How many times do you add new categories? If you are building new categories and new attribute definitions often, and you dont want to re-code, then I dont see much way around an EAV model.
Regarding davidM's article, he did a great job of pointing out the pain points of the EAV model, but what solution did he propose?