Example: File Information Class

Previous Table of Contents Next

Example: File Information Class

If you recall from last hour, you built a module to give information on files. After building the module, it had a series of defects. Each of those defects can easily be fixed by using a class instead of a regular module.

The object-oriented version of the file information module is presented in Listing 18.6.

Listing 18.6. The FileInfo Module, as a Class.

1: #!/usr/bin/perl -w


3: package TYPFileInfoOO;

4: use strict;


6: sub new {

7:       my($class,$filename)=@_;

8:       if (! -e $filename) {

9:               die "$filename doesn't exist!";

10:      }

11:      bless { filename => $filename };

12: }

13: sub bytes {  # Returns the size of the file in bytes

14:      my($self)=@_;

15:      return -s $self->{filename};

16: }

17: sub lines {  # Returns the number of text lines in the file

18:      my($self)=@_;

19:      my $count = 0;

20:      open(FH, $self->{filename}) || die "Can't open $self->{filename}: $!";

21:      while(<FH>) {

22:              $count++;

23:      }

24:      close(FH);

25:      return $count; 

26: }

27: sub name {   # Returns just the filename portion of the path

28:      my($self)=@_;

29:      if ($self->{filename} =~ m/([\w\.]+)$/) {

30:              return $1;

31:      }

32:      return $self->{filename};

33: }

34: sub extension {      # Returns just the extension portion of the filename

35:      my($self)=@_;

36:      if ($self->{filename} =~ m/\.(.*?)$/) {

37:              return $1;

38:      }

39:      return "";

40: }

41: sub modified {       # Returns the last modified time, suitable for localtime()

42:      my($self)=@_;

43:      my @stats = stat($self->{filename});

44:      return $stats[9];  # Modified time.

45: }

46: 1;

This listing is virtually unchanged from 17.8. The primary difference surrounds the fact that the global variable $FileInfoName is now gone.

Line 7: This new constructor is a bit different than the last. In order to create a TYPFileInfoOO object, you must pass the filename in to the constructor. (See Listing 18.7 for an example.) The new method is passed the class name as the first argument. You're really not interested in it, so just ignore it.

Lines 810: Because the filename is passed in during the constructor, and can't be changed anywhere else, you can check here to see if it exists. Contrast this to relying on $FileInfoName in the last hour.

Line 9: An anonymous hash reference is created, with one key-value pair to store the filename. This is blessed and returned (implicitly) from the subroutine.

Line 14: Because this is an OOP module, each of the subroutines will be called as methods and the first argument passed to the subroutine is the blessed reference created in the constructorin this case you stash it in $self.

Line 15: To get to the original filename passed into the constructor, use $self->{filename}. This is used to get the size of the file.

The rest of the module continues the pattern of simply retrieving the filename from $self->{filename}. Remember that each instance of the class (each object) will have its own version of $self.

Using the File Information Class

Listing 18.7 shows how the class would be used to write the same program you wrote in Hour 17, "Writing Modules."

Listing 18.7. A Reworking of Listing 17.9 with a Class

1:  #!/usr/bin/perl -w


3:  use strict;

4:  use TYPFileInfoOO;


6:  my $fileInfo = new TYPFileInfoOO("/temp/message.txt");


8:  print "\nFilename: "  . $fileInfo->name();

9:  print "\nExtension: " . $fileInfo->extension();

10: print "\nModified: " . localtime($fileInfo->modified());

11: print "\nBytes: " . $fileInfo->bytes();

12: print "\nLines: " . $fileInfo->lines();

Line 6: Notice that this constructor (the new part) takes a pathname as an argument. From now on, each method call against the resulting object will use whatever path was passed in.

Lines 812: This time around, to call the functions, use the object that you've created with the appropriate method.

Virtually no change, right? Well, like the Car class, the power of this class doesn't come out until you have multiple objects in play simultaneously.

To demonstrate this, Listing 18.8 presents a program to sort a directory by file size.

Listing 18.8. Using Multiple FileInfo Objects at Once

1: #!/usr/bin/perl -w


3: use TYPFileInfoOO;


5: my @fileobjects;

6: foreach my $pathname ( glob("/temp/*.txt") ) {

7:       if (-d $pathname) {

8:               next;

9:       }

10:      push @fileobjects, new TYPFileInfoOO($pathname);

11: }


13: foreach my $fileobj (

14:      sort { $a->bytes() <=> $b->bytes() }

15:      @fileobjects ) {


17:      printf("%20s %9d %9d\n",

18:              $fileobj->name(), $fileobj->bytes(), $fileobj->lines());

19: }

Line 6: This uses the built-in glob function presented in Hour 10, "Files and Directories," to retrieve a list of all the text files in a directory.

Lines 79: Directory names may also be returned by glob. This class isn't designed for directories, so skip them.

Line 10: Each pathname is used to construct a TYPFileInfoOO object. The resulting object is then accumulated in @fileobjects. If there are 100 text files, 100 independent objects will be created and thrown into @fileobjects.

Lines 1315: The array @fileobject is sorted by size (the number of bytes in the file). Because you're sorting an array of objects, the sort subroutine uses $a and $b as objects, calling their byte method to get the size for sorting. The variable $fileobj is set to each object in the sorted list and the loop body is run.

Lines 1718: $fileobj will take on the value of each object in the list. Because it's an object, simply call methods against the object to get the values.

The results (on my system) look something like this:

          chess.txt       135         8

         sample.txt       456        30

       outwords.txt      1031       141

        message.txt      1112        36

         parrot.txt      1152       144

        outfile.txt      3799         1

    NewSchedule.txt      4119        57

     volleyball.txt      5289       440

            log.txt     15529       363

This program would have been much more difficult to write using the old non-OOP version of the FileInfo module.

Let's revisit the list of problems presented at the end of Hour 17, and see if we've solved any of them.

  1. Problem: Namespace cluttering from importing names.

    Solved: An OOP module doesn't need to import any names at all. Each object has its own methods all accessed from the object itself. Objects of different types can all share method names and there are no namespace collisions at all!

  2. Problem: Special-secret variable names to control behavior.

    Solved: The constructor takes the pathname as an argument, and the dirty work is done inside of the class. The only names you have to know are method names to use with the object.

  3. Problem: Can't do concurrent operations with the module.

    Solved: Because each object maintains its own internal state and structures, there's no real limit to how many independent objects you can create.

  4. Problem: With users accessing internals, the behavior becomes unpredictable.

    Mostly Solved: No internal mechanisms are directly visible to the module user. Arguments get passed to the constructor and the methods, and the module can keep its internals private. In Perl, it's still possible to directly change the internals components of a class from outside of the classbut it's not recommended, and sometimes not easy.

    Previous Table of Contents Next
    © 2000- NIV