Click here to Skip to main content
15,902,937 members
Please Sign up or sign in to vote.
4.00/5 (2 votes)
I have a design question for the geeks. I shall present a hypothetical situation.
I have a couple of interfaces required by my application to make an object usable. Let’s say they are;

1. IAdult.cs, with two properties
a. Age
b. LicenceNo.
2. IEducated.cs, with two methods
a. Read()
b. Write()

If we had to implement an abstract class out of these interfaces, we could simply create a class, say, abstract class EducatedAdult{}. Let’s assume that the implementation of these interfaces shall remain the same most of the times.

Now we define three different objects with their own respective inheritances and a set of respective properties;

1. Carpenter.cs (derives from class A)
2. Plumber.cs (derives from class B)
3. Programmer.cs (derives from class C)

(Here class A, B, C belong to an external library) Now to make these objects usable in the Program, they need to implement the above given interfaces. So the issue is that for each one of these classes, I’ll have to explicitly implement the above stated interfaces. Could there be a better design where I do not have to implement the interfaces redundantly in all the classes. This could be a solution, had it been possible;
C#
class EducatedAdult<t>: t, IAdult, IEducated 
{ 
 <implementation of="" interfaces=""> 
}
</implementation></t>

And later, maybe, I could just initialize my derived classes like;

C#
Carpenter carpenter = new EducatedAdult<carpenter>().</carpenter>


Not that this would be an ideal solution, ‘cause the public interface of the carpenter object shall still not include my interface members (without required casting). But then, what could be the most suitable design?
Posted
Updated 29-Apr-11 23:58pm
v2
Comments
Albin Abel 30-Apr-11 6:18am    
Hi VermaManish, Is the stackoverflow.com response solved your issue or still waiting for a better response? :)
Nish Nishant 30-Apr-11 12:37pm    
Dude, you are everywhere huh? SO, CP, and MSDN? :-)
VermaManish 30-Apr-11 12:51pm    
Who me? No! Its Lord Krishna who's everywhere - The Omnipresent! :)
Nish Nishant 30-Apr-11 12:54pm    
No, not you. I meant Albin. And Krishna is not omnipresent, if so he'd have been there for the WC finals and he clearly wasn't! *grin*
Albin Abel 30-Apr-11 14:48pm    
Hi Nishant, It is nice to meet you everywhere too. :) :)

Your problem is classified in two parts:
1) Adequate modeling,
2) Technical aspects of week form of multiple inheritance.

1) Adequate modeling

I think you missing one important principle: if you have 1-3 design options using different technical tools. You cannot just ask "what could be the most suitable design?". It depends on semantic of the members of you terminal classes; if you know the classes and the members, you should fist ask "do I really need them?".

Let me illustrate this. What is IAdult? I don't need it! You need IPerson or just a class Person. A person always has the age and can have null or non-null License. It would cover all people including children. When a child obtains License, the class instance changes. For this purpose, your need LicenseNumber? (nullable type). You don't need age, ever. You need date of birth instead. Having age will cause you to change the instance all the time.

You don't need IEdicated as well. Consider everyone as educated, this is fair. You should create a type Education. All persons will instantiate the instance of this type, but the qualify of education will be changed; so the values in this instance will change (grow, hopefully).

What complexity has left? None! You see, by simple analysis I removed all your needs in multiple inheritance by turning it all into on single base class. I made the picture simpler technically yet more adequate to the real life we're trying to model.

2) Technical aspect of week form of multiple inheritance.

After the model is simplified, let's go back to multiple inheritance from more then one interface, so called week form of multiple inheritance. How to make different implementation in different classes in a way free from a predefined hierarchy of implementation classes? I don't want to use your interfaces anymore. Let's talk about interfaces IFirst and ISecond.

I would suggest using a technique of implementation helpers. Something like this:

C#
public interface IFirst {
    void A();
    void B();
}
public interface ISecond {
    void A();
    void C();
}

public class FirstHelperDetail { }
public class FirstHelper : IFirst {
    public FirstHelper(FirstHelperDetail userDetail) {
        this.UserDetail = userDetail;
    }
    void IFirst.A() { /* implementaion using UserDetail */ }
    void IFirst.B() { /*...*/ }
    FirstHelperDetail UserDetail;
}

public class SecondHelper : ISecond {
    public SecondHelper(System.Func<string, bool> userPredicate) {
        this.UserPredicate = userPredicate;
    }
    void ISecond.A() { /* implementaion using UserDetail */ }
    void ISecond.C() {
        if (UserPredicate != null) { /*...*/ }
        //...
    }
    System.Func<string, bool> UserPredicate;
}

class User : IFirst, ISecond {
    internal User(/*...*/) {
        FirstHelper = new FirstHelper(new FirstHelperDetail(/*...*/));
        SecondHelper = new SecondHelper(predicate);
    }
    void IFirst.A() { FirstHelper.A(); }
    void IFirst.B() { FirstHelper.B(); }
    void ISecond.A() { SecondHelper.A(); }
    void ISecond.C() { SecondHelper.C(); }
    IFirst FirstHelper;
    ISecond SecondHelper;
    System.Func<string, bool> predicate = (name) => { /* use name... */; return false; };
}


The sample demonstrate separate techniques of parametrization of helpers. Also, pay attention to explicit form of interface method implementation; this style is usually better then public: the method can not be called through the reference as class, only as interface which provide nice isolation. In particular, one can have too methods A of the same signatures. This style of interfacing is of course optional.


—SA
 
Share this answer
 
v3
Comments
VermaManish 1-May-11 5:19am    
This is perfectly valid in some cases. We could even use extension methods that can achieve the same pattern a little more elegantly. But let's agree to your argument that we should have bigger preference for language independent semantics. And so, your approach of using implementation helpers seems quite valid. But Nishant’s solution has one big advantage - we are able to encapsulate the implementation of our interfaces. This advantage of (encapsulated implementation) holds big relevance in case of my solution, even if it is at the cost of using language specific semantics. And after all, if everybody would avoid using language specific semantics, there wouldn’t be a need for second language. But I do really appreciate a good pattern that your solution manifests. Thanks
Sergey Alexandrovich Kryukov 1-May-11 14:45pm    
Agree.
Would you formally accept this answer?
I think it is adequate based on your formulation of the problem.
--SA
VermaManish 2-May-11 3:17am    
With pleasure! But it was Nishant's solution (Solution 3) that worked for me.
Sergey Alexandrovich Kryukov 2-May-11 4:17am    
Thank you, Verma. It's good Nishant's code was better for your, but I hope you understand I provided some wider understanding of the problem. Isn't it the meaningful criterion for acceptance of the solution, along with ad-hoc purpose? :-)
--SA
Albin Abel 2-May-11 0:07am    
My 5 for language independent semanatics. This way provide little flexibility to the user class how to use the interface implementation, whereas encapsulating the interface methods hinders further flexibility. void IFirst.A() { FirstHelper.A(); } and I have flexibility to use void IFirst.A() { NewHelper.A(); }. By design I like this way. Nishant's answer satisfy Verma's question. So question answered and you solution is a better design. :) 5++
Here's a simplified example of one possible approach :

C#
class Program
{
    static void Main()
    {
        EducatedAdult<Carpenter> eac = new EducatedAdult<Carpenter>();
        eac.Age = 10;
        eac.Read();
        Carpenter cpr = eac;
        cpr.DoWork();
    }
}

interface IAdult
{
    int Age { get; set; }
}

interface IEducated
{
    void Read();
}

class EducatedAdult : IAdult, IEducated
{
    public void Read()
    {
    }

    public int Age
    {
        get
        {
            return 18;
        }

        set
        {
        }
    }
}

class A { }

class Carpenter : A
{
    public void DoWork()
    {
    }
}

class B { }

class Plumber : B { }

class EducatedAdult<T> : EducatedAdult where T:new()
{
    private T t;

    public EducatedAdult()
    {
        this.t = new T();
    }

    public static implicit operator T(EducatedAdult<T> eat)
    {
        return eat.t;
    }
}
 
Share this answer
 
v2
Comments
VermaManish 30-Apr-11 13:31pm    
I think your solution is par excellent!
I'll have to do few more tests related to serializing the generic EducatedAdult, before I come back.
Thanks!
Albin Abel 30-Apr-11 14:49pm    
Sure, my 5++
Sergey Alexandrovich Kryukov 30-Apr-11 23:14pm    
Things can be simplified through better analysis but needs some more flexibility; also, the final solution depends on semantic.

Please see my variant.
--SA
How about some sort of construct like:

C#
class MyCarpenter : carpenter
{
    public EducatedAdult EA = null;

    public MyCarpenter()
    {
        EA = new EducatedAdultSubClass();
    }
}



i.e. embed rather than inherit - you can put whatever behaviours you like in your EducatedAdult class and any sub-classes (do you even need the base EducatedAdult class to be abstract?) and implement them once only.

I'm sure others will come up with alternative ways of doing it, but this seems to me to be relatively straightforward.
 
Share this answer
 
Comments
VermaManish 30-Apr-11 6:22am    
The application that shall use these objects identifies the interfaces (IAdult, IEducated) only. So I need to implement these contracts to make these objects (Carpenter, Programmer, Plumber) usable.
Sergey Alexandrovich Kryukov 30-Apr-11 23:12pm    
The idea make sense and should be use an many cases but have limitations. Could be a variety of methods. My 4. Thinks are more simple and more complext at the same time.

Please see my solution.
--SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900