Eliza 6 - Creating a pattern class hierarchy

by Ravi Bhavnani


Remember how we talked about "separation of concerns" and "information hiding", way back in Step 0?

Well, it seems we've failed to adhere to these principles in the previous step, by forcing Eliza.Initialize() to have to know about the internals of the three patterns (BECAUSE, YES and NO) it uses.

    /// <summary<
    /// Initializes this instance.
    /// </summary>
    private void Initialize()
    {
        // Initialize the list of patterns that Eliza understands
        this._patterns = new List();

        // Create BECAUSE pattern
        List<string> responses = new List<string>();
        responses.Add("IS THAT THE REAL REASON?");
        responses.Add("DON'T ANY OTHER REASONS COME TO MIND?");
        responses.Add("DOES THAT REASON SEEM TO EXPLAIN ANYTHING ELSE?");
        responses.Add("WHAT OTHER REASONS MIGHT THERE BE?");
        this._patterns.Add(new Pattern("BECAUSE", responses));

        ...
    }

Eliza.Initialize() (which is the consumer of patterns) shouldn't have to know what's inside a BECAUSE pattern. It should simply be able to construct one and use it.

Real-world example

Imagine that in order to replace a burned-out lightbulb you had to:

  1. install a filament in the lightbulb
  2. seal the bulb
  3. screw the new lightbulb into the socket

That's silly. You shouldn't have to know anything about the internals of a lightbulb in order to use one. In other words, you should only have to perform step 3.

Similarly, Eliza.Initialize() should only need to construct a BECAUSE pattern and add it to its pattern list. Something like this:

    /// <summary<
    /// Initializes this instance.
    /// </summary>
    private void Initialize()
    {
        // Initialize the list of patterns that Eliza understands
        this._patterns = new List();

        // Create BECAUSE pattern
        BecausePattern bp = new BecausePattern();
        this._patterns.Add(bp);

        // Same with YES and NO patterns
        ...

        ...
    }

Hiding the complexity of the BECAUSE pattern

In order to shield the user from the complexity of the BECAUSE pattern, we'll hide its internals in a BecausePattern class. Since BecausePattern is nothing but a Pattern, we'll make BecausePattern derive from (i.e. be a subclass of) the parent Pattern class.

Pattern will contain code that's common to all patterns, while BecausePattern will contain code that's specific to the BECAUSE pattern. Common stuff belongs to the base ("parent") class, while specific stuff belongs to the derived ("child") class. The collection of parent and child classes is called a "class hierarchy", because some classes derive (i.e. descend) from others.

For now, let's start with this class hierarchy:

    Pattern
        BecausePattern
        YesPattern
        NoPattern

Pattern is the base (parent) class, and BecausePattern, YesPattern and NoPattern are child classes that all derive from Pattern.

What's common and what's specific?

In our class hierarchy, all three patterns are alike in their behavior, in that:

  • They all generate a response if the user's input matches each class's key phrase. Although the phrase that is matched ("BECAUSE" "YES" and "NO") varies from class to class, every class has a phrase to match.

  • And although each pattern generates different responses, they all generate responses.

So as it turns out, the three classes BecausePattern, YesPattern and NoPattern only have common properties and methods, and no specific properties and methods. However, the internals of each property/method vary from class to class.

What can we hide and what can't we hide?

Properties and methods that MUST be accessible to users of the class can't be hidden. Everything else can (and should) be hidden. It's that simple! (And you thought computer science was hard? )

Take a closer look at the public properties and methods of the Pattern class and for each, ask yourself if each can be private or does it have to remain public? At the end of this step, compare your answer with the solution.

Refining the Pattern and BecausePattern classes

Now we're going to do what software engineers do to earn their money. We're going to use our brains and refine (i.e. improve) the Pattern and BecausePattern classes. Our goal is to allow Eliza to construct the BecausePattern class by simply doing:

    BecausePattern bp = new BecausePattern();

  • This should create a fully functional pattern that's capable of recognizing the "BECAUSE" phrase and responding with an appropriate response every time its GenerateResponse() method is called. All the details of this pattern should be hidden from its user.

  • Since BecausePattern derives from Pattern, it can delegate some of its work to the base Pattern class.

In doing so, we'll end up tweaking the Pattern class a bit and making some parts of it accessible to:

  • nobody ("private")
  • only derived classes ("protected")
  • everybody ("public")

Don't remember what the access modifiers private, protected and public mean? Refresh your memory here.

COMING UP WITH A WELL DESIGNED CLASS HIERARCHY IS THE HARDEST (AND PERHAPS MOST REWARDING) PART OF SOFTWARE ENGINEERING. EVENTUALLY, YOU'LL COME TO RECOGNIZE PATTERNS (no pun intended) IN HOW CLASSES INTERACT WITH EACH OTHER, AND CREATING A WELL THOUGHT OUT DESIGN WILL COME NATURALLY TO YOU.

BUT WHEN YOU'RE STARTING OUT, IT CAN FEEL A BIT LIKE TRYING TO FIND YOUR WAY OUT OF A MAZE WITH A BLANKET OVER YOUR HEAD.

When you're done

After you've modified the Pattern class and created the BecausePattern class:

  1. Create the YesPattern and NoPattern classes. This should be easy, since these classes closely resemble BecausePattern.

  2. Simplify Eliza.Initialize() so that it simply constructs each pattern and adds them to its list of patterns.

  3. Organize your project so that the pattern classes reside in their own folder.

  4. Finally, test your changes to verify that Eliza continues to respond as it did in step 5

NOW TAKE A LOOK AT THIS STEP'S SOLUTION AND COMPARE YOUR CODE TO IT.
 

Most of the content at this site is copyright © Ravi Bhavnani.
Questions or comments?  Send mail to ravib@ravib.com