views:

300

answers:

8

I have the following problem and I'm wondering if there's a nice way to model these objects without using multiple inheritance. If it makes any difference, I am using Python.

Students need contact information plus student information. Adults need contact information plus billing information. Students can be adult students, in which case I need contact/student/billing info, or they can be children, in which case I need contact/student/parent info.

Just to be clear on how the system will be used, I need to be able to ask for a list of all adults (and I will get adult students plus parents), or a list of all students (and I will get child students plus adult students).

Also, all of these objects need to have a common base class.

+2  A: 

Very simple solution: Use composition rather than inheritance. Rather than having Student inherit from Contact and Billing, make Contact a field/attribute of Person and inherit from that. Make Billing a field of Student. Make Parent a self-reference field of Person.

Randolpho
+2  A: 

It doesn't sound like you really need multiple inheritance. In fact, you don't ever really need multiple inheritance. It's just a question of whether multiple inheritance simplifies things (which I couldn't see as being the case here).

I would create a Person class that has all the code that the adult and student would share. Then, you can have an Adult class that has all of the things that only the adult needs and a Child class that has the code only the child needs.

Jason Baker
The Person class would be huge. Every time a new contact type was added Person would grow and one or more other wrapper classes would grow as well. Alex Martelli has it right.
Mike
+8  A: 

What you have is an example of Role -- it's a common trap to model Role by inheritance, but Roles can change, and changing an object's inheritance structure (even in languages where it's possible, like Python) is not recommended. Children grow and become adults, and some adults will also be parents of children students as well as adult students themselves -- they might then drop either role but need to keep the other (their child changes schools but they don't, or viceversa).

Just have a class Person with mandatory fields and optional ones, and the latter, representing Roles, can change. "Asking for a list" (quite independently of inheritance or otherwise) can be done either by building the list on the fly (walking through all objects to check for each whether it meets requirements) or maintaining lists corresponding to the possible requirements (or a mix of the two strategies for both frequent and ad-hoc queries). A database of some sort is likely to help here (and most DBs work much better without inheritance in the way;-).

Alex Martelli
Is the reason you're recommending this solution because the roles can change?
Mike J
Yes, mutability of Role[s] is the #1 motivation for avoiding inheritance in this case (there are others, too, which apply more widely and other responses have mentioned).
Alex Martelli
+5  A: 

As I'm sure someone else will comment soon (if they haven't already), one good OO principle is "Favor composition over inheritance". From your description, it sounds suspiciously like you're breaking the Single Responsibility Principle, and should be breaking down the functionality into separate objects.

It also occurs to me that Python supports duck typing, which begs the question "Why is it so important that all the classes have a common base class?"

Richard J Foster
+1 for linking favor composition over inheritance. Exactly my point!
Randolpho
Keep in mind that, as pointed out in the linked article, composition is most frequently implemented through interface inheritance. If that is the case, then there is still the original problem.
Mike
A: 

One solution is to create a base Info class/interface that the classes ContactInfo, StudentInfo, and BillingInfo inherit from. Have some sort of Person object that contains a list of Info objects, and then you can populate the list of Info objects with ContactInfo, StudentInfo, etc.

Ski
A: 

This sounds like something that could be done quite nicely and flexibly with a component architecture, like zope.components. Components are in a way a sort of super-flexible composition patterns.

In this case I'd probably end up doing something when you load the data to also set marker interfaces on it depending on some information, like if age >= 18 you set the IAdult interface, etc. You can then get the adult information by doing

adultschema = IAdultSchema(person)

or something like that. (Edit: Actually I'd probably use

queryAdapters(person, ISchema)

to get all schemas in one go. :)

A component architecture may be overkill, but once you got used to thinking like that, many problems get trivial. :)

Check out Brandons excellent PyCon talk about it: http://www.youtube.com/watch?v=UF77e2TeeQo And my intro blog post: http://regebro.wordpress.com/2007/11/16/a-python-component-architecture/

Lennart Regebro
+1  A: 

I think your requirements are over-simplified, since in a real situation, you might have students with their own accounts to handle billing even if they are minors who need parent contact information. Also, you might have parental contact information be different from billing information in an actual situation. You might also have adult students with someone else to bill. BUT, that aside - looking at your requirements, here is one way:

classes: Person, BillingInfo, StudentInfo.

All people are instances of class Person...

class Person:
    # Will have contact fields all people have - or you could split these off into an
    # object.
    parent        # Will be set to None for adults or else point to their parent's
                  # Person object.
    billing_info  # Set to None for non-adults, else to their BillingInfo object.
    student_info  # Set to None for non-student parents, else to their StudentInfo
                  # object.

Checking the fields will allow you to create lists as you desire.

Anon
A: 

In pseudocode, you could do something like this:

Class Student
    Inherits WhateverBase

    Private m_StudentType as EnumStudentTypes 'an enum containing: Adult, Child
    Private m_Billing as Billing
    Private m_Contact as Contact
    Private m_Parent as Parent

    Public Sub Constructor(studentType, billing, contact, parent)
        ...logic to make sure we have the right combination depending on studentType.
        ...throw an exception if we try to assign a a parent to an adult, etc.
        ...maybe you could have seperate constructors, one for each studenttype.             
    End Sub


    Public Property StudentType as EnumStudentTypes
        Get
             Return m_StudentType
        End Get
    End Sub

    Public Property Parent 
        Get 
           ...code to make sure we're using a studentType that has a parent,
           ...and throws an exception if not. Otherwise it returns m_Parent
        End Get
    End Sub


    [more properties]
End Class Student

Then you could create a class called StudentManager:

Public Class StudentManager
    Public Function GetAdults(studentCollection(Of Students)) as StudentCollection(Of Students)
        Dim ResultCollection(Of Students)

        ...Loop through studentCollection, adding all students where Student.StudentType=Adult  

        Return ResultCollection
    End Function


    [Other Functions]
End Class

Public Enum StudentType
    Adult=0
    Child=1  
End Enum
Brian MacKay