Assume the following class inheritance tree:
/-----------\ | Company | \-----------/ / \ / \ /-----------\ /-------------------\ | AcmeLtd | | Company::Division | \-----------/ \-------------------/ \ / \ / /-------------\ | AcmeLtd::IT | \-------------/
And this scenario:

We have some incoming data that needs to be dealt with by AcmeLtd::IT. All we know at first however, is that it is related to some company. We also have some configuration data stored in the system, and with this we can map the incoming data as far as AcmeLtd. Having got this far however, we can now find out from AcmeLtd what class the data maps onto, and with that knowledge we can process the data.


Now, how to implement this in code? To start with let's have a ground-rule: that all the knowledge and code to do this must lie within the above classes. we must be able to trigger off the entire process with one call from the outside.

Suggestion 1:

Company->new->run \/ (inside Company::run) create new instance of AcmeLtd. use this to a) get name of AcmeLtd subclass that should process the data, and create an instance of this class or b) get an instance of the AcmeLtd subclass that should process the data \/ (inside Company::run) call the processing method of the object instantiated above

Suggestion 2:

Company->new->run \/ (inside Company::run) AcmeLtd->new->run \/ (inside AcmeLtd::run) AcmeLtd::IT->new->run

Suggestion 3:

Company->new->run \/ (inside Company::run) rebless Company object as AcmeLtd, and alter @ISA appropriately \/ (inside same, reblessed object) use newly available methods to get the name of the AcmeLtd subclass that should process the data, then rebless ourselves again in +to this new class and alter @ISA again \/ (inside same, reblessed object) call newly available processing method


So, curious here. Are any of these suggested methods preferred over the others? Are there any reasons (technical or otherwise) not to use any of them? Are there any other techniques that might be preferred? Anything else that might be relevant?

Replies are listed 'Best First'.
•Re: inheritance and object creation
by merlyn (Sage) on Feb 23, 2004 at 22:19 UTC
    Assume the following class inheritance tree
    Why should I assume that, unless I'm also assuming that someone doesn't get "ISA" vs "HAS-A" for design?

    Acme ISA company. Acme HASA It department. Acme-IT ISA company division. So, fix your design, and the problems go away. Acme is an instance of company, and Acme-IT is an instance of a company division. And there's no other inheritance along the other axes.

    If the answer to a question is "multiple inheritance", you've probably asked the wrong question.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Ah, yes. I knew i was missing something there, but couldn't kick my brain into gear enough to see it. Ok, so sorting out the inheritance (and adding in a class i forgot to include) gives us (?):
      /-----------\ /-------------------\ | Company | | Company::Division | \-----------/ \-------------------/ /|\ /|\ | | (is-a) | | | /-------------------\ | (is-a) | AcmeLtd::Division | | \-------------------/ | /|\ | | (is-a) | | /-----------\ /-------------\ | AcmeLtd |------>------| AcmeLtd::IT | \-----------/ (has-a) \-------------/
      With that we straight away dump #3. I'm still undecided between #1 and #2 though; what are you thoughts on the matter?

        something else to keep in mind, is the distinction between "IS-A" and "is-instance-of"

        When designing a class hierarchy, the concepts of IS-A and HAS-A get thrown arround alot, but you have to always keep in mind that you are talking about "classes of things" -- not hte things themselves.

        All i know about your data and your application is what you've posted in this thread, so maybe there's more going on then i understand, but it seems to me that "AcmeLtd" is an instance-of "Company", and "AcmeLtd IT" is an instance-of "Company::Division". Unless of course, you have some reason to instantiate many different instances of AcmeLtd ?

        The nature of your data confuses me, so instead let's Imaging you are dealing with Animals. you've got the class "Animal" and hte subclasses Dog and Cat (a Dog IS-A Animal). now based on the nature of your data, do you want lots of instances of "Dog" representig a breed as an attribute, or do you want a seperate subclass for each breed, with each instance representing a physical dog with a name?

        This...

        # Dog ISA Animal my $german_sheperd = new Dog(BIG, FURRY); my $collie = new Dog(MEDIUM, SHORT_HAIRED); my $poodle = new Dog(SMALL, GOOFY);
        ...verses, this...
        # Dog ISA Animal # Dog::Poodle ISA Dog # Dog::Collie ISA Dog my $fluffy = new Dog::Poodle('fluffy'); my $spot = new Dog::Poodle('spot'); my $fido = new Dog::Collie('fido'); my $stray = new Dog();

        /-----------\ /-------------------\ | Company |--@(has-N)-> | Company::Division | \-----------/ \-------------------/ /|\ /|\ | | (is-a) | | | /- - - - - - - - - -\ | (is-a) AcmeLtd::Division ? | \- - - - - - - - - -/ | /|\ | | (is-a) | | /-----------\ /-------------\ | AcmeLtd |-----create--> | AcmeLtd::IT | \-----------/ \-------------/

        A company may have more than one division, and the number may change over time, but the distinction between them doesn't really matter. So consider a collection of generic divisions.

        In the AcmeLtd construction, or later in its operation, you may choose to add an AcmeLtd::IT instance to the collection of generic divisions of the company. You may choose to nix it later, or outsource it to some Vendor::IT that has a similar interface.

        If there's nothing particularly distinguishing about all AcmeLtd::Divisions that would not be required of any generic Company::Division, then kill that class.

        (ASCII is not a very good way to draw Booch diagrams.)

        --
        [ e d @ h a l l e y . c c ]

      If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
      Really? Suppose you were to make a class hierarchy of shapes. How would order: parallelogram, rhombus, rectangle and square?

      Abigail

        If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
        Really? Suppose you were to make a class hierarchy of shapes. How would order: parallelogram, rhombus, rectangle and square?
        Poorly.

        The problem is that as soon as we add in multiple inheritance, we add tremendous conceptual complexities in how we figure out what to do when we have defined actions in multiple places in our class hierarchy that might reasonably (under different models of how things work) apply. And it isn't that there is a right or wrong way to do this. In different situations, different ways of resolving the same ambiguity make sense. Furthermore no way of handling multiple inheritance that I have seen will always be appropriate.

        Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...

        The Structure and Interpretation of Computer Programming has some interesting things to say on this. In Inadequacies of hierarchies they offer a more involved version of your example and dryly note, Dealing with large numbers of interrelated types while still preserving modularity in the design of large systems is very difficult, and is an area of much current research. In the footnotes they expand on this with, Developing a useful, general framework for expressing the relations among different types of entities (what philosophers call ``ontology'') seems intractably difficult. The main difference between the confusion that existed ten years ago and the confusion that exists now is that now a variety of inadequate ontological theories have been embodied in a plethora of correspondingly inadequate programming languages.

        This remark seems to me to be dead on. When you decide how to handle multiple inheritance, you are settling on a particular ontological framework. To the extent that your framework does not naturally express the understanding of the programmer, you create confusion. The subtler and more complex the distinctions that you draw, the more readily you will find that slight confusion on the part of the programmer rapidly multiplies into obfuscation, errors, and rapidly escalating design complexity. Particularly when you get to comprehension issues that virtually nobody has decent mental models of.

        Do you deny that people lack good mental models of multiple inheritance? Oh, we can all produce diagrams, and for simple geometrical shapes it is easy enough to say what the inheritance diagram should be. But where is the simple diagram and natural examples that explains how traversing the class hierarchy in one order differs from traversing it in another differs from using Class::MultiMethods to locate a method based on both arguments? You know those three are different, I know they are different, and we can both produce code proving that they are different. But I can't provide an understandable model of the difference beyond, "Here are the rules and here is how the rules will apply in these cases."

        Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so. The resulting design is likely to be easier to come up with and easier for others to understand than multiple inheritance. You also avoid the potential for several kinds of "gotchas". And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.

        Why with Traits of course ;-)

        -stvn

        I think if you have to model the differences in polygons with an object model, you are really taking your object model too far down. The polygon class should be sufficiently advanced to take arbitrary constraints and handle all properties generically. After all, a 71-sided polygon has no name, and we didn't write an object for that yet! Nor should we have to. There shouldn't have to be classes for that, and to a mathematician, rules for a 71-sided polygon with equal angles are scarely different than a quad... the numbers just come out cleaner.

        It's like the canonical OO example of cats and dogs where the user writes canine and feline in there, and writes support in there for the future inclusion of fish. It just going to darn far.

        Moral of the story -- Inheritance is a powerful weapon in the OO arsenal. It is but one of many weapons, and is the most overused hammer for the proverbial nail. Abuse of inheritance and not knowing when to inherit versus encapsulate objects has led to many coding problems I've seen in my work -- folks make this error in all sorts of languages, because the power of inheritance is very alluring. It takes a lot to hold it in check -- to know when it should not be used.

Re: inheritance and object creation
by hardburn (Abbot) on Feb 23, 2004 at 22:24 UTC

    #3 gave me a nervous twitch. Re-blessing objects is really cool, but evil.

    I like #2 because the Company object doesn't have to deal with the lower subclasses at all. #1 is, perhaps, a better use of polymorphism.

    ----
    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

      Re-blessing objects is not cool nor evil. It's stupid. It's just asking for trouble, even when you know what you're doing. Why bother at all with "OO" if you're going to break the rules?

        Re-blessing objects is not cool nor evil. It's stupid. It's just asking for trouble, even when you know what you're doing. Why bother at all with "OO" if you're going to break the rules?

        Ive done this quite a few times. I dont think its so bad depending on the purpose and requirements. For instance, I have a routine that returns a list of objects from a query that can perform a particular function. In fact the internals of those objects are quite complicated and expensive to fetch and more often than not I dont need to use all of the returned objects (but wont know if I do until too late), so I actually use two classes of objects. The first class has as its contents the required information to complete the building process. All of the accessor methods simply cause the object to complete the build, rebless, and then recall whatever it was that was originally called. That way I only need to do a full build on the objects I actually use. As a way of transparently encapsulating such behaviour I think reblessing is a fine way to procede. (I think in a way you could compare it to a "seed" object turning into a "Tree" object.)

        Now there are times and places for everything. I wouldn't encourage a lot of reblessing, but I dont think it should be just automatically rejected. There are times where it makes perfect sense.


        ---
        demerphq

          First they ignore you, then they laugh at you, then they fight you, then you win.
          -- Gandhi


        I tend to agree with this point, if not the spirit of it, but in that OO is generally supposed to be more disciplined and fringe behavior is discouraged...however I am reserving judgement based on the flawed ISA/HASA hierachy given in the OP. If this were fixed, maybe I would understand the examples in #1-#3 better. Nothing wrong with inventing a few new idioms...that is, if they don't introduce design/maintainance problems.

        Given, discipline is very important. But also there is some "evil" in Perl that is also cool, because, yes, deep within us all, we know that sometimes evil is cool. It is for this reason that I like listening to Heavy Metal music sometimes :) No, seriously... sticking to form is important in OO, but OO is oft controversial as to what is 'good form'.

        Over-use of singletons, factories, accessors, and other P.C. methods can be disasterous to an application -- just as 'evil' can. Both 'evil' and 'good' have their uses, and 'good' in the wrong place can have 'evil' results. Thus it is important to know both the good and the evil. It's a Taoist sort of thing. Ok, I just confused myself.... Carry on!