views:

419

answers:

5

UPDATE: So pretty much everyone here told me that i just need to start over again on how i designed my classes (thank you folks for your excellent answers by the way!). Taking the hint, i started doing extensive reading on the strategy pattern. I want to create behavior classes (or strategy classes) that inherit from an abstract base classes. The Candidate class would then have properties w/ the different abstract base classes as the types for the behaviors or strategies. maybe something like this:

public abstract class SalaryStrategy {
    public abstract decimal Salary { get; set; }
    public abstract decimal Min { get; set; }
    public abstract decimal Mid { get; set; }
    public decimal CompaRatio {
        get {
            if (this.Mid == 0) { return 0; }
            else { return this.Salary / this.Mid; }
        }
    }
}

public class InternalCurrentSalaryStrategy {
    public override decimal Salary { get; set; }
    public override decimal Min {
        get { return this.Salary * .25m; }
        set { }
    }
    public override decimal Mid { get; set; }
}

public class Candidate {
    public int Id { get; set; }
    public string Name { get; set; }
    public SalaryStrategy CurrentSalaryStrategy { get; set; }
}

public static void Main(string[] args) {
    var internal = new Candidate();
    internal.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var internalElp = new Candidate();
    internalElp.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var elp = new Candidate();
    // elp.CurrentSalaryStrategy can stay null cause it's not used for elps
}

Any comments or suggestions?


ORIGINAL Question:

I am trying to learn and become more proficient at design patterns and principles. I have am currently working on a design for few classes that has stumped me. Here's a very condensed version of the code:

public class Candidate {
    public int Id { get; set; }
    public string Comments { get; set; }
    // lots more properties and behaviors...
}

public class InternalCandidate : Candidate {
    public decimal CurrentMid { get; set; }
    public decimal CurrentMax {
         get { return this.CurrentMin * 1.3m;
    }
    // lots more properties and behaviors...
}

public class EntryLevelCandidate : Candidate {
    public string Gpa { get; set; }
    // lots more properties and behaviors...
}

public class InternalEntryLevelCandidate /* what do I inherit here??? */ {
    // needs all of the properties and behaviors of
    // EntryLevelCandidate but also needs the CurrentMin and
    // CurrentMax (and possibly more) in InternalCandidate
}

The InternalEntryLevelCandidate class is primarily an EntryLevelCandidate but needs to share some of the implementations of InternalCandidate. I say implementations because I don't want the implementations to be different or repeated, otherwise I would use an interface for common contracts and have concrete implementations in each class. Some of the implementations of the InternalCandidate properties and behaviors need to be common or shared. I have read about C++ and Ruby mixins, which seem to be something similar to what I what I want to do. I also read this interesting blog post that discusses an idea for a behavior type where a class would be able to inherit multiple behaviors while still maintaining a single "is a" relationship: http://www.deftflux.net/blog/post/A-good-design-for-multiple-implementation-inheritance.aspx. This seems to convey what I am wanting. Can anyone give me some direction on how I can accomplish this using good design practices?

+1  A: 

I agree that inheritance doesn't seem to be the right thing here. I'm not sure that I know the perfect answer, but perhaps the Decorator pattern is appropriate.

Another, more esoteric idea is to think about aspect-oriented programming. You can do some pretty amazing things with aspects, but it's a very advanced topic that I still haven't mastered. The kind of folks who have are like Rikard Oberg and his Qi4J cohorts.

duffymo
I was thinking about Decorator as well. Instantiating candidates will be a bit tricky though and needs some thought. Maybe a factory that creates Candidates and decorates them based on the kind of candidate which also means that you need to define some sort of a candidate state or type. There must be a better way.
Mehmet Aras
I did read about the decorator pattern on http://www.dofactory.com/Patterns/PatternDecorator.aspx, but I was having difficulty understanding how to implementing it for my code. Would you be able to take a shot at the implementation w/ my code if you are more familiar w/ the pattern? Any help would be appreciated.
gabe
No time right now gabe, but I'll try to get to it soon.
duffymo
+2  A: 

Disclaimer:

In my experience needing multiple inheritance is the exception rather than the rule, careful design of class hierarchies can usually avoid needing this feature. I agree with JP that this requirement could be avoided in your sample.

Back to the question, there is no clean solution, however you have a few options:

  • Use extension methods, has the disadvantage that right click Resolve does not works, also some people really dislike these puppies.

  • Create an aggregate object that holds and instance of each class you want composited, re-implement stub methods that delegate.

  • Define an interface for each behavior and have the methods in the base check if this is IInterface before executing the behavior. (allows you to pull behavior definitions to the base)

Near duplicate: Multiple inheritance in C#

Sam Saffron
+4  A: 

Here's a scholarly paper on the subject that I think is pretty interesting (PDF link).

But, I think you are trying to impose business logic in your generalizations. You happen to know that an InternalCandidate will never have his GPA looked at. But, an InternalCandidate certainly has a GPA. So, you have cracked out this strange guy called an InternalEntryLevelCandidate because you happen to know that you want to look at this guy's GPA. Architecturally, I think the EntryLevelCandidate is incorrect. I would add a "Level" concept to a Candidate and give him a GPA. It's up to the business logic to decide if they look at the GPA or not.

Edit: Also, Scott Meyers does a great job of dissecting this issue in his books.

JP Alioto
@JP: Thx for the answer, JP! However, my requirements in this case do not require the GPA be used at all in InternalCandidate. This was just example code anyway. The Gpa property just represents a property that's completely not needed/used/stored for Candidate or any derived class except for EntryLevelCandidate and it's derived classes.
gabe
Understand. It think the general answer is to refactor and rethink the relationship to avoid the diamond. :)
JP Alioto
Would you be able to give me a code example of what you are suggesting? This design space is something I have been struggling w/ for a while. Seeing some implementation would help me greatly.
gabe
+5  A: 

Immutable data value classes. If any properties in your various Candidate subclasses represent some kind of meaningful data value, create an immutable class for it, with the behaviors you need. Each of your distinct Candidate subclasses can then use the data type, but your code is still encapsulated in the data classes.

Extension methods. These could be overloaded to work with any classes.

I'd avoid the decorator pattern and stick with compiled/reflectable functionality.

Composition. Develop the unique behaviors in separate classes right away, and build your Candidate classes around them, rather than writing unique behaviors in your Candidate classes and trying to pull out their functionality for use in related classes later.

Depending on how you use the classes, you could also implement and make use of explicit and implicit conversion operators to a related type, so instead of reimplementing interfaces (which you wanted to avoid), you could actually cast your object into the type/implementation you need for whatever purpose.

Another thing I just thought of, related to that last paragraph, is to have a leasing system, where your class spawns and object of the appropriate type, allows it to be manipulated, then consumes it later to assimilate the updated information.

Triynko
A: 

I'd just use the Delegation pattern. Ultimately I'd use an interface for each distinct piece of functionality, then have a concrete class as a delegate for each interface. Then your final classes just use the delegates they need and can inherit from multiple interfaces.

public class InternalEntryLevelCandidate : EntryLevelCandidate {
    private  InternalCandidate internalCandidateDelegate
        = new InternalCandidate();

    public decimal CurrentMid { 
        get { return internalCandidateDelegate.CurrentMid; }
        set { internalCandidateDelegate.CurrentMid = value; }
    }

    public decimal CurrentMax {
        get { return internalCandidateDelegate.CurrentMax }
    }
}
Cameron MacFarland