busunsl has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks!

I'm writing a small program to monitor several Sybase servers.
These servers are of different types like Replication Server, Adaptive Server and so on.
The types have serveral things in common, like 'show overall status' and the like, but do also several things different.

This looks pretty much like an object hierarchy with a Server object as parent class and an ASE/RS/anything object inheriting from Server.

So far no problem. But upon creation time the server object does not know which type it is.
Only if it can connect to the real server, it can determine it's type.

So at the moment I don't have a hierarchy but one object (server) and some files with the funktions used by the various types.
I create a Server object. Let it determine it's own type by connecting to the real server. Import the necessary functions and use it's own dispatch table.

It works, but it's not as object oriented as I would like and it's hard to follow, what's really happening.

So, finally, here my question: Is it possible to create an object of a parent class, let it determine it's type and become an object that is further down the hierarchy?

Replies are listed 'Best First'.
Re: change object class during runtime
by Masem (Monsignor) on Jun 19, 2001 at 15:09 UTC
    You probably want to readjust this object pattern to a Factory method; that is, besides your base Server object and the various items that inherit from it, you want to create a class that could be called ServerFactory which you cannot make an instance of, but has 'static' functions that create Server objects for you (that is, the first argument to these functions is NOT the blessed ref). You can connect to the database and create the appropriate server object from these functions, and not have to change class type during run time.

    One possible modification is that once you've determined the connection type, you can further call static functions within the individual database types as to keep the class specific code within the class itself.


    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      This would be similar to connecting to the real server and determine the type in the main program and create the objects from the correct class.

      The problem here is that I need a working object even when it doesn't know it's type, due to the server beeing down, and report the failure to connect.

        Well, one thing you could do is have a subclass of Server called "InvalidServer" (or some better name), that would be returned by the Factory object in case the server is unreachable which you can fill with whatever error information that you need; optionally, you can stick connection error info into field in the ServerFactory; if there is a problem creating the connection, you can return undef, and then use the error fields to determine why and respond appropriately.

        Note that the Factory method is quite common as an object pattern, and while it's the same as doing stuff in main, it's considered to be much safer (namespace collisions), so there's nothing wrong with using such a method.


        Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
Re: change object class during runtime
by bikeNomad (Priest) on Jun 19, 2001 at 20:10 UTC
    Sure, just use bless to change the class.

    I don't know why some people seem to be so horrified by this; sometimes it makes sense to do. Specifically, you might want to change the class to implement a conversion method, or upon getting enough information to rebless from a parent, abstract class into a concrete subclass. Depending on your semantics, you may want to maintain the same reference after a conversion, so you don't invalidate references held on to by your clients.

    Let me give you an example from my CPAN module Archive::Zip. Here's the inheritance hierarchy, first:

    Exporter Archive::Zip Common base class, has defs. Archive::Zip::Archive A Zip archive. Archive::Zip::Member Abstract superclass for all memb +ers. Archive::Zip::StringMember Member made from a string Archive::Zip::FileMember Member made from an external file Archive::Zip::ZipFileMember Member that lives in a zip file Archive::Zip::NewFileMember Member whose data is in a file Archive::Zip::DirectoryMember Member that is a directory

    So far so good. I re-bless objects in a couple of instances. To do this, I have an inherited method called _become:

    # Morph into given class (do whatever cleanup I need to do) sub _become # Archive::Zip::Member { return bless( $_[0], $_[1] ); }

    This is overridden in the various member classes to add cleanup (that is, remove subclass-specific data, seek file handles to safe places, and call the SUPER::_become). For instance:

    sub _become # Archive::Zip::FileMember { my $self = shift; my $newClass = shift; return $self if ref( $self ) eq $newClass; delete( $self->{'externalFileName'} ); delete( $self->{'fh'} ); return $self->SUPER::_become( $newClass ); }
    Where is this used? One of the places is in the routine that reads a Zip file. There is a loop that reads members from the file. These are initially created as Archive::Zip::ZipFileMember. But in the case of directories, there's no actual data, and we want an Archive::Zip::DirectoryMember instead. So the members that represent directories get changed to the DirectoryMember class.

    The other place where the re-blessing happens is when someone changes the contents of a member by passing in a string. Since most of the data in the object is correct, and since clients may have references to the existing member, I just change the member (which may be of any member type) to an Archive::Zip::StringMember (if it isn't already).

    Anyway, depending on the use of your objects, it may make sense. But I wouldn't do it before I did a pretty rigorous analysis of my object model.

Re: change object class during runtime
by busunsl (Vicar) on Jun 19, 2001 at 16:59 UTC
    Ok, an answer to my own question and a new question :-)

    I can change the class of an object during runtime by reblessing it into another package.
    Yepp, it's that easy.

    The question that arises is:
    Is it ok to do so?
    I don't know the mechanisms in depth, but all my tests worked fine.
    Am I missing something?

      Yes it works. Yes it is okay. But is it really the abstraction that you want? Masem's solution is much more clean in terms of future expansion. You have to think about why you want to use OO and how you anticipate this will change in the future. In this case you pretty clearly want to be able to add new DB types to the tool. So you should minimize the number of places that need to know about the mapping between DB types and object types. You certainly don't want to have each object know about all of the types.

      Whether you can do the detection dynamically and try to load the appropriate code at run time (do an eval based on some identifying attribute like DBI does when loading DBDs) or whether you have a hard coded mapping in some file that needs to be changed each time there is a new DB type is up to you (and the requirements of the problem). Also remember who your target is. If this is for internal use and only you and whomever comes after you need to use it you may want to use a static mapping since it is unlikely that the DB classes will need to be released independent of the main code. If you are going to write a generic tool to release to CPAN that may not be valid.

      -ben

(tye)Re: change object class during runtime
by tye (Sage) on Jun 19, 2001 at 19:02 UTC

    Inheritance should really be a last resort. Have a "connection" object that contains an object that is server-specific. When there is no valid connect, the connection object contains an undef for the server-specific object. When the connection comes up, create a new contained object specific to the type of server found.

    Then have the connection object dispatch to the server-specific code. Don't let the connection object hand out references to the server-specific object otherwise you have problems trying to change it out from under people.

    Now, if Perl had (native) interface inheritance, then it might be worth making a virtual class to define the interface of these server-specific objects so that they'd be forced to share the same core interface.

    If there is likely to be shared code among several of the server-specific objects, then inheritance probably makes sense. Also, you could have the base class for the server-specific objects be the "no server" object that just pukes (very nicely, of course) if you try to do something that can only be done when the server is up.

    I'd be very reluctant to write production code that reblesses objects. It just seems sloppy and so likely to run into some nasty problems down the road. Especially in this case where you have connection methods that don't change (can't change) based on the server type and other methods that must change. Those groups of methods should be separated into different classes in the hopes of making maintenance easier.

    For example, you want to be able to make the object constructor different for each server-specific object in case some server types require different member data to be tracked. If you go reblessing an object after it is created, then you can't rely on the constructor to have set up the object properly any more and have to duplicate code to handle any changes required during reblessing. So instead of N constructors, you could end up with N constructors and N*(N-1) reblessing cases to maintain! Ugly!

            - tye (but my friends call me "Tye")
      I would not be at all reluctant to write production code that reblesses objects.

      I wouldn't even consider doing so unless a good chunk of the component was designed around that idea. But if, for instance, a good chunk of my system was a "state diagram" with the current state indicated by the current class, then I would be perfectly willing to proceed.

      In the abstract I think that would be perfectly maintainable. Certainly it is a design pattern that fits with a circle of ideas which I know are both natural and maintainable for certain classes of problems. The practical implementation may be a different matter, but my sense is not against it.

      That said, I have never actually needed that design. Nor would I encourage casually experimenting with it in production code just to see how it works - most times it would be a horrible fit for sanity and in this case I would definitely go with your proxy idea.

      But still, unless the concept of "state transitions" is utterly essential to how you are thinking about your functioning system, I wouldn't rebless.