5

I'm trying to model a multi-dimensional point class in C#. I have about eight different types of points, and there may be more in the future. Right now, I have a superclass (PointBase) that holds all the common behaviors and data. This data includes only simple properties like doubles, strings, enums, and DateTimes (these are the dimensions). The subclasses (let's say 1Point, 2Point, etc.) all override at least things: the methods that have behavior for calculating two of these dimensions. If this behavior were the only changing thing, I could just use the Strategy + Factory pattern and be done with it (and honestly, I probably will refactor those two simple common behaviors out later).

Unfortunately, I'm stuck because some points add a property or two based on their type, or based on values of other properties in the superclass. For example, for point types 1-7, I don't need an additional Breakeven property, but for type 8, I do (this is problem #1).

In addition (this is problem #2), depending on the Product property (which exists and is set in the superclass, and is an enum), I may need to add a Term enum property that would apply to all subclass points. However, only one of my Product enum values needs a term. The others don't.

This situation is complicated by needing to store lists of these points in a List<PointBase> data structure that hides additional public properties. I'd have to cast each PointBase to a 1Point or 2Point or whatever when I took it out, and this (of course) breaks some design principles (separation, at the very least, I think).

Unfortunately, I'm also trying to display these different points on the same WPF DataGrid (in a scheme where the user can select 1Point or 2Point or whatever, and get the points' public properties displayed in the DataGrid). Although all of this code so far is contained within my model (in a separate DLL), I'm also unsure of how to represent this polymorphism in my view in a DataGrid. I'm totally fine with allowing the DataGrid to just autogenerate columns based on the points' public properties, but again, passing these points around in a List<PointBase> means that there's no way to get at those additional properties I need to add. I thought of instead adding a virtual Dictionary to the superclass called CustomAttributes to which I would add a string key and string value every time I needed to include some additional properties. But this is very annoying, because flattening a Dictionary for a DataGrid is really hard. I'm worried that this is the only way though...

From what I know (which is limited w.r.t. software engineering), I need something like an abstract factory to capture all this polymorpism. But I'm not exactly sure. To recap, I need:

  1. The ability to add public properties to certain subclasses based on their type. For example, for the 8Point subclass, I need to add a Breakeven property, but this property doesn't make sense for other subclasses.
  2. The ability to add public properties to all subclasses based on values of properties in the superclass. For example, if the Product (set in the superclass) is A, I need an additional property added to all subclasses called Term. This property does not apply or make sense for any other products.

I'm not married to inheritance here (in fact, I'd like to do something more elegant/simpler if possible, such as delegation + interfaces), so I'm open to any solutions. Thank you in advance!

EDIT: To give some more context, here is what my setup looks like right now:

public abstract class StatisticPointBase 
{
    public DateTime EventTime;
    public Product Product; // an enum
    protected double _delta;
    public string Name;
    public double FooSensitivity;
    public double BarSensitivity;
    public double BazSensitivity;
    public double FlopRatio;
    public double FlapCoefficient;
    ...
    public double Value;     // set by overridden CalculateValue
    public double Velocity;  // set by overridden CalculateVelocity        

    public PointBase(IDataRepository d, string name, double delta, Product p) 
    { 
        Name = name;   // just the name, like "Alpha" or "Beta"
        _delta = delta; // follows name, e.g. for Alpha it's always 0.63
        Product = p;
        FooSensitivity = d.GetFooSensitivity(p);
        ... // other setup
    }

    public abstract double CalculateValue();
    public abstract double CalculateVelocity();
}

public class AlphaStatisticPoint 
{
    public SimpleStatisticPoint()
    {
        Value = CalculateValue();
        Velocity = CalculateVelocity();
    }

    override double CalculateValue()
    {
        return (fooSensitivity + barSensitivity + bazSensitivity) * Velocity / 2;
    }

    override double CalculateVelocity()
    {
        return FlopRatio * (_delta / FlapCoefficient);
    }
}

public class BetaStatisticPoint 
{
    public SimpleStatisticPoint()
    {
        Value = CalculateValue();
        Velocity = CalculateVelocity();
    }

    override double CalculateVelocity()
    {
        return (FlopRatio * 100) * (_delta);
    }

    override double CalculateValue()
    {
        return fooSensitivity * Velocity;
    }
}

// other points...

public class ZetaStatisticPoint 
{
    public double Breakeven;

    public SimpleStatisticPoint()
    {
        Value = CalculateValue();
        Velocity = CalculateVelocity();
        Breakeven = CalculateBreakeven();
    }

    override double CalculateVelocity()
    {
        return (FlopRatio * 100) * (_delta);
    }

    override double CalculateValue()
    {
        return fooSensitivity * Velocity;
    }

    override double CalculateBreakeven()
    {
        return 2 * Math.sqrt(fooSensitivity / barSensitivity);
        // This is not defined for other types of statistics, and is only relevant for Zeta-type statistics. 
        // For other statistics, we can't even calculate the breakeven; there's no formula for it.
    }
}

public enum Product 
{ 
    Widget, 
    Watchet, 
    /* Woffet will be added in future, and needs a Term */
}

Stuff like Name and Delta should probably be set in the subclasses, since each subclass has one, and each value is unique to each subclass. For example, Alpha's delta value is always 0.63, Zeta's is always 0.21, etc. I'm trying to get away from inheritance though, and the real problem is that Breakeven exists only for Zeta (this is problem #1).

Furthermore (and this is problem #2), I may need to create and set a Term property, but only for Points that have a Product value of Woffet. It doesn't make sense for other products. This property, Term, would be added to all points, so if we're going with inheritance, I'd put it in the superclass. Although I'm starting to think I should just expand Product into a class, and then give it a Term property. But we run into the same problem for the new Product class then: Term only applies to the Woffet product, just like how Breakeven only applies to the Zeta statistic.

I realize that when one comes upon this behavior, it's usually the case the inheritance is not the answer (or so I've gathered?). So, I'm looking for a better way to model this. I was thinking of getting rid of all the subclasses but Zeta, and just giving the superclass a ValueCalculationStrategy and VelocityCalculationStrategy in the constructor (these would calculate Value and Velocity). But in this case, I'm still referring to Zeta as a StatisticPointBase, and the client doesn't know it's a ZetaStatisticPoint. So I'm still stuck. Maybe the problem is that I'm conceptually mixing statistics and points? I could make a separate ZetaStatistic class, and a ZetaBreakeven class, and then use these to compose a ZetaPoint class, but I don't know if this is the right thing to do.

Hopefully I've explained this adequately. If you have any questions, please ask in the comments and I'll do my best to expound on things.

  • Can you provide some more details why you need 8 different point classes? What are the main differences between them? – Doc Brown Jul 27 '15 at 20:22
  • Three things change per subclass: (a) The way dimension A is computed (can be replaced with strategy) (b) The way dimension B is computed (can be replaced with strategy) (c) the properties that exist in some subclasses, but not others (I have no idea how to model this). This third part, (c), is the area where I'm having problems. 8Point has a Breakeven property, but the others don't. I often need to switch between which Point subclass I'm looking at (they're being displayed in a graph and DataGrid, switched around by user choice), so I have a List<PointBase>. – TheIntern Jul 27 '15 at 21:31
  • Putting the points into a List<PointBase> hides the Breakeven property of 8Point though. So, I can't show Breakeven in the DataGrid (or do other things with Breakeven, because I don't know that I'm dealing with an 8Point), but the user must see it. Furthermore, in the future, I expect there to be other custom properties for some points, but not others, so this problem will persist. This is where I'm stuck. I either need different lists for each subclass (bad), or to add Breakeven to PointBase (bad, b/c pts 1-7 don't have it). So I'm looking for a better way to model the points – TheIntern Jul 27 '15 at 21:35
  • Why do you need the properties? What are they being used for? – Ben Aaronson Jul 28 '15 at 11:36
  • @Ben Aronson They're being graphed by a graphing control, displayed to the user in a DataGrid, etc. Some dimensions are changed by the user using the DataGrid. They're part of a business process. – TheIntern Jul 28 '15 at 15:13
1

Using strategy objects in the described way is a good start to simplify your PointBase class, I would not hesitate to introduce them even if they do not solve your problem with the BreakEven property.

For allowing custom properties, you could provide some kind "extension mechanism" in your PointBase class (which I would rename to Point after the redesign). In the most simple form, this could be just a list of custom properties for each Point object, together with a list of their values (and the list is empty by default). This might boil down to some kind of EAV model. EAV is sometimes seen as an anti-pattern, but for this specific case its usage is fine as long as you do not use it for the majority of properties, only for your "special custom properties".

See also this former question on "Programmers" about custom fields. In my answer there, I mentioned Martin Fowler's book "Analysis Patterns" - I am pretty sure it will help you for your problem, since it contains a very extensive discussion how to model measurements and observations, including (custom) properties and similar things.

  • Thank you for telling me about Analysis Patterns! I didn't realize Martin Fowler had written a book on object models. I just picked up a copy; it's awesome, and it has some examples that are germane to my domain. I'm looking through it now. – TheIntern Jul 28 '15 at 16:34
  • Eventually ended up doing this by creating a class based on a Dictionary<string, Tuple<object, Type>> which stored keys (string) against a tuple value with the object and type of the object. The class had some methods for retrieving the typed values, as well as adding typed values. I then added an instance of this class as a property to each PointBase class. It's worked wonderfully as a typed key-value store. Thanks for the advice and inspiration! – TheIntern Mar 10 '16 at 2:00
5

I posted a question here that was brought about by the same problem: trying to both take advantage of the convenience of referring to objects by their base class, while at the same time using additional properties that their subclasses may contain.

The solution that was most useful to me was to use the visitor pattern. In your Point base class, you define an abstract method void Accept(Visitor). The Visitor class would define a method for each subclass (so you'd have to keep track of them all, any new subclass would require modifying the visitor class). For example, the visitor would look like this:

interface Visitor {

    void on1Point(1Point);

    void on2Point(2Point);

    ... etc
}

Then each BasePoint subclass would implement the accept method to delegate to the correct Visitor method. So in the 1Point class, accept would be defined as:

void accept(Visitor visitor) {
    visitor.on1Point(this);
}

Now whenever you have a BasePoint and want to do something with it depending on what extra properties it has, make a visitor, figure out what you want it to do in the case of being each possible subclass, and put the logic there. In the body of the Visitor method, the object you'll be handed will be "cast" to the correct type, so you can access any of its additional properties. It's similar to just doing a bunch of instanceof checks, but it's much cleaner, more reusable, and more elegant.

  • Thank you for your answer! I'd like to avoid the Visitor pattern though, because I'm still in the design phase, so I don't need to (yet) add additional functionality without compromising the OCP. In addition, I'm trying to get away from inheritance, and use composition instead here. Consequently, I think there may be a better way to model this. If there really isn't though, I may end up using the Visitor pattern. Thanks again :) – TheIntern Jul 27 '15 at 22:51
0

I don't fully understand your requirements, but to me it seems you hit a point where C#'s type system is more going to get in the way than actually help you.

I would simply drop the idea of using classes and inheritance and just create a generic key-value store where key it name or type of the propery and value is an object containing the value of the property. Then I would create a code that ensures the invariants about which type of point contains which properties are correct during run time. You might actually try using C# dynamic type, which is basically same thing, but with nicer syntax.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.