Computing - Object Oriented Design

Object-oriented design is a technique where data in a program is represented by a collection of objects that have some relationship to the reality of the data being modelled.

For example, in a payroll application, each employee would be represented by an object. An employee object contains data that is pertinent to the employee, such as employee number, date of employment, position, department number, manager, and salary. Normally, in an object-oriented design, the data is not accessed directly, but is read and changed using procedure calls, or methods.

To carry the example further, let's consider a special type of employee, the manager. A manager is an employee, but with additional characteristics, such as a list of employees reporting to her.

In an object-oriented design, there are two basic relationships between various objects: the "IsA" and "HasA" relationships. In this example, a manager is an employee and an employee has an employee number, date of employment, position, department number, manager, and salary.

In a nutshell, object-oriented design is determining the objects in an application and their "IsA" and "HasA" relationships with each other.

Definitions

Class
A template for multiple objects with similar characteristics. For example, employee and manager.
Instance
An object. For example, one instance would describe one particular employee.
Instance Data
Data associated with a particular instance. For example, instance data for an employee object would be the employee number, date of employment, etc.
Class Data
Data associated with the class. For example, an employment class might contain the next available employee number for new employees.
Method
A function defined in a class that operates on instances. For example, class employee might have methods such as getSalary and getManager.
Instance Method
A method associated with an instance.
Class Method
A method associated with a class.
Inheritance
The is a relationship between two classes. For example, class manager inherits all of the attributes of class employee, and adds characteristics of its own.
Subclass
A class that inherits from another class.
Superclass
The class that a subclass inherits from. Note that when defining a subclass, it is not necessary to define the methods of the superclass again. If a method is not overridden in the subclass, the method of the superclass is used.
Polymorphism
Different classes may have the same method name defined. The particular method actually called is based on the class of the object.

 

Genealogy

Object-oriented design is an obvious choice for genealogy. But then again, with a bit of thought, most application domains can fall nicely into an object-oriented design.

Here are some of the obvious objects in a genealogy application:

  • Individual
  • Family
  • Event
  • Birth
  • Marriage
  • Sex
  • Name
  • Date
  • Place
  • Age

The key trick in object-oriented design is defining the objects and the "HasA" and "IsA" relationships between them. What do all of the objects above have in common? Well, for one thing, they are all records in a gedcom file. We'll cheat here and define our objects using the structure of gedcom files. In other words, one genealogy object can be modelled by one gedcom record.

So, the base class for gedcom records can be defined as containing the following elements of a gedcom record:

  • Tag name
  • Text
  • Label (for 0-level records)
  • List of subsidiary records


All of the Gedcom record types can be considered subclasses of gedcom record. In other words, an individual is a gedcom record, a family is a gedcom record, an event is a gedcom record, etc. We can take this deeper: Births, deaths, and marriages are events.

The "HasA" relationships are modelled by the list of subsidiary gedcom records. For example, an individual has a name, a birth family, spouse families, and events, such as birth, baptism, and death. A family has a husband, a wife, zero or more children, and events, such as marriage and divorce.

Object-Oriented Perl Example

I won't describe fully how to do object-oriented programming in Perl, since there are better documents at the Perl Institute. But to briefly summarize, an object in Perl is defined by a package. An object instance is a reference, typically to an anonymous hash, where the instance data is named by particular hash keys. The Perl statement that associates the reference to the class is bless.

Here's an example of a Perl program that uses object-oriented programming. This program reads a gedcom file and creates the objects that represent the data, and then writes out the data to another file.

In a real application, there would be additional processing between these two steps of reading and writing the gedcom file. In addition, a real object-oriented application would contain subclasses derived from GedcomRecord that describe particular types of gedcom records.

#!/usr/bin/perl -w

# Get names of input and output files
($infile, $outfile, @rest) = @ARGV;
if (!$infile || $infile eq '')
{
   die "No filenames are specified";
}

# Create gedcom object
$ged = Gedcom->new();
$ged->read($infile);

# Note:  Insert additional processing here!

# Write out gedcom file
$ged->write($outfile);

exit;


########################################################################
# package Gedcom    -- Gedcom object.                                  #
#----------------------------------------------------------------------#
# Methods:                                                             #
#                                                                      #
# new() - create new gedcom file object                                #
# data() - return gedcom records in this object                        #
# read(filename) - read gedcom records from specified file             #
# write(filename) - write gedcom records to the specified file         #
########################################################################

package Gedcom;

sub new
{
   my $that = shift;
   my $class = ref($that) || $that;
   $self = { };
   $self->{'INDEX'} = { };
   bless $self, $class;
   return $self;
}

sub data
{
   my $self = shift;
   if (@_) { $self->{'DATA'} = shift }
   return $self->{'DATA'};
}

sub read
{
   my $self = shift;
   my $filename = shift;
   my ($node, $label, $level, $tag, $text);
   my $lastlevel = -1;

   my $newged = GedcomRecord->new('_BASE_');

   my $lastitem = $newged;
   my $parentitem;

   # open file for input
   open INFILE, $filename or die "Can't open file $filename";

   # read records of file
   while (<INFILE>)
   {
      # Get rid of CR/LF chars
      chomp;
      s/[\cM\cJ]//;

      # Parse record
      $label = '';
      /^(\d+)\s+(@\S+@)?\s*(\S+)\s*(.*)/;
      $level = $1;
      $label = $2 if ($2);
      $tag   = uc($3);
      $text  = $4;

      # Where do we put this record?
      if ($level > $lastlevel)
      {
         $parentitem = $lastitem;
      }
      else
      {
         $i = $lastlevel;
         $parentitem = $lastitem->parent;
         while ($i != $level)
         {
            $parentitem = $parentitem->parent;
            $i--;
         }
      }

      if ($tag eq 'CONT')
      {
         $lastitem->text ($lastitem->text . "\n" . $text);
      }
      elsif ($tag eq 'CONC')
      {
         $lastitem->text ($lastitem->text . $text);
      }
      else
      {
         # create new gedcom record
         $node = GedcomRecord->new($tag,$text,$label);

         # Link record into current
         $parentitem->additem ($node);

         # Setup for next record
         $lastitem = $node;
         $lastlevel = $level;
      }

      # Add top level item to index
      if ($level == 0
      &&  $label
      &&  $label ne '')
      {
         ${$self->{'INDEX'}}{$label} = $lastitem;
      }
   }

   # Set node in gedcom object
   $self->{'DATA'} = $newged;

   # Close input file
   close INFILE;
}

sub write
{
   my $self = shift;
   my $filename = shift;

   my $level = 0;

   # open file for input
   open OUTFILE, ">$filename" or die "Can't open file $filename";

   # Loop through items of base item
   $base = $self->{'DATA'};
   foreach $rec (@{$base->{'ITEMS'}})
   {
      $rec->print (\*OUTFILE, 0);
   }

   # Close file
   close OUTFILE;
}

########################################################################
# package GedcomRecord -- Gedcom record object.                        #
#----------------------------------------------------------------------#
# Methods:                                                             #
#                                                                      #
# new(tagname, text, label) - create new gedcom record                 #
# tag(tagname) - get or set tag name in gedcom record                  #
# text(text) - get or set text                                         #
# label(label) - get or set label                                      #
# parent() - get parent of this record                                 #
# additem(gedcom-record) - add a gedcom record to this record          #
# print() - print this record and subsidiary records                   #
# record() - return string representing this record                    #
########################################################################

package GedcomRecord;

sub new
{
   my $that = shift;
   my $class = ref($that) || $that;
   $self = { };
   $self->{'ITEMS'} = [];

   # Initialize object based on parameters to constructors
   if (@_) { $self->{'TAG'} = shift }
   if (@_) { $self->{'TEXT'} = shift }
   if (@_) { $self->{'LABEL'} = shift }

   # bless object
   bless $self, $class;
   return $self;
}

sub tag
{
   my $self = shift;
   if (@_) { $self->{'TAG'} = shift }
   return $self->{'TAG'};
}

sub label
{
   my $self = shift;
   if (@_) { $self->{'LABEL'} = shift }
   return $self->{'LABEL'};
}

sub text
{
   my $self = shift;
   if (@_) { $self->{'TEXT'} = shift }
   return $self->{'TEXT'};
}

sub parent
{
   my $self = shift;
   if (@_) { $self->{'PARENT'} = shift }
   return $self->{'PARENT'};
}

sub additem
{
   my $self = shift;
   my $item = shift;
   push @{$self->{'ITEMS'}}, $item;
   $item->parent($self);
}

sub print
{
   my $self = shift;
   my $file = shift;
   my $lvl  = shift;

   my $str = $self->record;
   my $tag = $self->{'TAG'};
   if ($tag eq 'NOTE'
   ||  $tag eq 'AUTH'
   ||  $tag eq 'TITL'
   ||  $tag eq 'PUBL'
   ||  $tag eq 'TEXT'
   ||  $tag eq 'ADDR'
   ||  $tag eq 'SOUR')
   {
      # We need to split records at newlines and long lines
      # (Note: splitting long lines into CONC records is not
      # yet implemented)
      @reclist = split /\n/, $str;
      print $file $lvl . ' ' . shift(@reclist) . "\n";
      foreach $rec (@reclist)
      {
         print $file $lvl+1 . ' CONT ' . $rec . "\n";
      }
   }
   else
   {
      print $file $lvl . ' ' . $self->record . "\n";
   }

   # Print out sub-items:
   foreach $rec (@{$self->{'ITEMS'}})
   {
      $rec->print ($file, $lvl+1);
   }
}

sub record
{
   my $self = shift;
   my $str = '';
   if ($self->{'LABEL'})
   {
      $str .= $self->{'LABEL'} . ' ';
   }
   $str .= $self->{'TAG'} . ' ' . $self->{'TEXT'};

   return $str;
}