If you're already in any way familiar with object-oriented
programming, you'll know that I have been saving the best for last. The
relationship between classes and the dynamic objects they generate
allows for much flexibility in a system. Individual
objects encapsulate distinct sets of translation data, for example, yet
the model for these varying entities is defined in the single
Sometimes, though, you need to inscribe variation down at the class level. Remember the
DictionaryIO class? To recap, it takes data from a
Dictionary object, writes it to the file system, takes data from a file, and merges it back into a
Dictionary object. Listing 12 shows a quick implementation that uses serialization to save and load
Listing 12. A quick implementation using serialization
This example introduces a couple of simple
Dictionary methods -- in particular,
asArray(), which returns a copy of the
$translations array. The
implementation has the virtue of simplicity. As is usual in example
code, error checking has been omitted, but even so, this is a quick and
easy way of saving data to file.
Once you have deployed a library of this sort, you soon become committed to supporting its save format. Making a format obsolete risks the goodwill of your users who may store backups in this way. But requirements change, and you may also get complaints that the output format is not easily user-editable. Such users may wish to send export files to third parties in XML format.
You now face a problem. How do you support both formats behind the
One solution would be to use a conditional statement inside the
import() methods that tests a type flag, as shown in Listing 13.
Listing 13. Using a conditional statement inside the export() and import() methods
This kind of structure is an example of a bad "code smell" in that it relies upon duplication. When a change in one place (adding a new type test, for example) requires a set of parallel changes in other places (bringing other type tests into line), code can quickly become error-prone and hard to read.
Inheritance offers a much more elegant solution. You can create a new class
XmlDictionaryIO that inherits the interface laid down by
DictionaryIO, but overrides some of its functionality.
You create a child class using the extends keyword. Here is a minimal implementation of the
XmlDictionaryIO is now functionally identical to
DictionaryIO. Because it inherits all public (and protected) attributes from
DictionaryIO, you can do all the same things with an
XmlDictionaryIO object that you can do with a
DictionaryIO object. This relationship extends to object type. An
XmlDictionaryIO object is obviously an instance of the
XmlDictionaryIO class, but it is also an instance of
-- in the same way that a person is a human, a mammal, and an animal
all at the same time and in that order of generalization. You can test
this using the
instanceof operator, which returns true if the object is a member of the indicated class, as shown in Listing 14.
Listing 14. Using the instanceof operator to test inheritance
instanceof accepts that
$dictio is a
DictionaryIO object, so, too, will methods accepting these objects as arguments. This means that an
XmlDictionaryIO object can be passed to the
Dictionary class' constructor, even though
DictionaryIO is the type specified by the constructor's signature.
Listing 15 is a quick and dirty
XmlDictionaryIO implementation that uses DOM for its XML functionality.
Listing 15. XmlDictionaryIO implementation
The details of acquiring and generating XML can be taken for
granted. There are plenty of ways of getting this done, including the
excellent SimpleXML extension. In summary, the
import() method takes an XML document and uses it to populate a
Dictionary object. The
export() method takes the data from a
object and writes it to an XML file. (In the real world, you would
probably use an XML-based format called XLIFF, which is suitable for
importing into third-party translation tools.)
Notice that both
export() call the utility method
path(), which doesn't exist in the
XmlDictionaryIO class. This doesn't matter because
path() is implemented in
XmlDictionaryIO implements a method, it is this implementation that is invoked for an
XmlDictionaryIO object when the method is called. When no implementation is present, the call falls through to the parent class.
Figure 2 shows the inheritance relationship between the
XmlDictionaryIO classes. The closed arrow denotes inheritance and points from child to parent.
Figure 2. An inheritance relationship