Wednesday, September 2, 2009

Refactoring Immediately

I had to make lots of changes almost immediately in my project.

(See my original article on the Hebrew Conjugator project.)

Whenever I had formulated a way to output the correct finite verb form for a given tense/person/gender combination and after implementing exceptions, I found that the formula could be simplified again. So I had to do that in order to avoid that newly discovered exceptions during testing would not make the code ridiculous complicated.

I also noticed that object-orientation is ideal for this, so I implemented another set of subclasses for conjugations of a certain type and for certain exceptions.

The program now has a class "Conjugation" which implements a few helper methods and four conjugation methods:

function GetInfinitive(root: String): String;
function GetPresentTense(root: String; person: Int32; male: Boolean): String;
function GetPastTense(root: String; person: Int32; male: Boolean): String;
function GetFutureTense(root: String; person: Int32; male: Boolean): String;

Each method simply returns an empty string for all input values. This ensures two things. First, any unsupported conjugation can still be called (see note 1) and will not crash the program or return false results. Second, any non-implemented features in a supported conjugation will not return false results either (see note 2).

Note 1: If a subclass of Conjugation does not implement an override for a method, the original method will be called and, in this case, return an empty string.

Note 2: Subclasses of specific conjugations that do not implement an override for a method, will call the specific conjugation's method and return a false result. Hence all subclasses of subclasses of Conjugation will have to implement override methods if the original override method would return false results.

The program then defines subclasses of Conjugation representing specific conjugations (Hebrew: binyanim = buildings), for example ConjugationPa3al.

The subclasses define their own override versions of the four methods.

Orginially, the subclasses also defined new methods for exceptions (like GetPresentTenseHollow for hollow roots), but I found that the code is easier to read if each method follows the same structure in name and content. Hence were born subclasses of the subclasses, including ConjugationPa3alHollow (for hollow roots, like "gar" = "he dwelled" or "ba" = "he came"; "pa3al" is one of the conjugations in Hebrew)and ConjugationPa3alHe (for roots ending on He, like "qanah" = "he bought" and "panah" = "he turned").

Exceptions that only affect the infinitive are handled by the original subclass.

For the infinitive of root ending on He, I found a particularly clever (in my opinion) solution. ConjugationPi3el actually returns the correct infinitive for both roots ending on He ("qanah" -> "liqnoth") and normal roots ("katav" -> "likhtov") and ConjugationPi3elHe does not implement an override for the GetInfinitive() method. This constitutes a combination of not rewriting existing code but still keeping different things in different modules, logically.

Current class tree:

- Conjugator (GUI program uses Conjugation.dll)

- HebrewWord (used by Conjugator)

- Conjugation (all used by HebrewWord)
-- ConjationPa3al
--- ConjugationPa3alHollow
-- ConjugationPi3el
--- ConjugationPi3elHe

Other classes are not implemented yet.

Each subclass of Conjugation and their descendants contain implementations of GetInfinitive(), GetPresentTense(), GetPastTense(), and GetFutureTense(), if needed and nothing else.