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

Hi. I’m looking Monk Wisdom on an OO design question.

I have an app that will generate many different matrices. Each cell in a matrix is either a plain scalar, or a scalar with an associated type (link, percentage, money, abbreviation, etc). I want to be able to render these matrices in a different formats: as HTML (thru Template::Toolkit), as an Excel Spreadsheet (thru Spreadsheet::WriteExcel), as flat text file, etc.

How to display a cell will depend on where it is being rendered. For example, a “link” cell should become <a href=FOO>BAR</a> when rendered as HTML, should beome just “BAR” when rendered as an Excel cell or as a text file. For a “percentage”, the scalar should be run thru sprintf for HTML, should be made into real Excel percentage using Excel formats for the spreadsheet, and should be made into a long decimal (0.33333333333) for the flat text file.

I am looking for advice on doing this in an OO way, rather than embedding lots of “if type=bar and outputdest=baz then blah blah” code.

Thanks!

nop

Replies are listed 'Best First'.
Re: OO design question
by clemburg (Curate) on Apr 26, 2001 at 18:02 UTC

    Read the description of the Builder Pattern in the Design Patterns book. This should do what you want.

    Basically:

    • Each Matrix HAS-A CellRenderer (the Builder object)
    • There is an abstract CellRenderer class that defines the interface: render_scalar(), render_link(), render_percentage() ...
    • Concrete CellRenderer classes like HTMLCellRenderer, ExcelCellRenderer, etc., implement the interface of the CellRenderer class.
    • Now you can configure a Matrix with a CellRenderer for the appropriate output type.
    • The Matrix knows which of the CellRenderer interface operations to call for which type of cell item (switch statement).

    Alternatively, and much cooler, you can solve this problem in the CLOS (Common Lisp Object System) way by using Damian Conway's module Class::Multimethod. This module allows you to have dynamic dispatching based on the type of multiple arguments.

    Christian Lemburg
    Brainbench MVP for Perl
    http://www.brainbench.com

Re: OO design question
by Rhandom (Curate) on Apr 26, 2001 at 17:52 UTC
    I would write a base class that that has the methods for getting and setting data. I would then write individual sub classes that inherit from that base class. This way, you would only have to check the type once, and bless the object into the proper class.

    my @a=qw(random brilliant braindead); print $a[rand(@a)];
      Can you explain in more detail, or sketch a toy example?
        How 'bout:
        #!/usr/bin/perl -w ### set up some packages ### this would normally be done using their own files { package Nifty; sub new { my $type = shift; my $self = {@_}; if( $self->{type} ){ # require $self->{type}; # if the Nifty::A packages # were in their own files $type = $self->{type}; } return bless( $self, $type); } sub get { return "Some_Value"; } ### default print method sub print_it { my $self = shift; my $str = shift; return $str; } } { package Nifty::A; @Nifty::A::ISA = qw(Nifty); sub print_it { my $self = shift; my $str = shift; return "<a href=$str>$str</a>"; } } { package Nifty::B; @Nifty::B::ISA = qw(Nifty); sub print_it { my $self = shift; my $str = shift; return uc($str); } } # more types et cetera ### here is the main program use strict; my ($obj,$val); $obj = Nifty->new(); $val = $obj->get(); print $obj->print_it( $val ) ,"\n"; $obj = Nifty->new(type=>'Nifty::A'); $val = $obj->get(); print $obj->print_it( $val ) ,"\n"; $obj = Nifty->new(type=>'Nifty::B'); $val = $obj->get(); print $obj->print_it( $val ) ,"\n"; exit;


        Basically, put the normal functionality in the base class. Anything that changes, put in the sub classes.

        my @a=qw(random brilliant braindead); print $a[rand(@a)];
Re: OO design question
by Masem (Monsignor) on Apr 26, 2001 at 17:51 UTC
    One way, but bad, is to have a special function for each type of method that you want to print out as for each data type. But this is bad as if you add a new way to print out things, you'd have to add that new way for all object types. Icky.

    The better way is to define one output function for each type, with a parameter (a constant) of the method of output that you are generating; it's up to the individual type if it needs to heed that type, with a default output of "plain text". This way, adding a new output type won't break anything to start, and you can then work back to update those types of objects that will work with that data type to do so; those that are unaffected like scalars need no additional updating.


    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
Re: OO design question
by suaveant (Parson) on Apr 26, 2001 at 17:48 UTC
    Well.. I think you have to do a lot of if type= code, but inside the object. basically have a function that outputs or returns your data in the way you want it based on your options...
                    - Ant