ITworld.com
  Search  
ITworld Home Page ITworld Webcasts ITworld White Papers ITworld Newsletters ITworld News ITworld Topics Careers ITworld Voices ITwhirled Changing the way you view IT

Repeated interface implementations in Java, C++, and Delphi

ITworld.com 1/4/01

Michael L. Perry, ITworld.com

In recent columns, I've discussed the rule of interface: what it's good for, how to implement it, and what problems can arise when it's not strictly followed. While the rule states that a class implements interfaces, it does not restrict the type or number of those interfaces. Many languages, however, do restrict the ways that classes can implement interfaces. When you use such languages, you may have to do some creative coding to achieve your desired results.

On this topic

Case in point: It is sometimes desirable for an object to provide multiple implementations of a single interface. Yet none of the three languages I routinely examine in this column -- Java, C++, and Delphi -- directly supports repeated interface implementation.

This won't, however, stop a creative coder from doing his or her job. Suppose, for example, we want to write an application that displays a contact list, which it reads from a comma-delimited text file. We can design our program as two separate classes: one that parses comma-separated value (CSV) files in general, and one that records the application-specific contact list.

apd-perry-0104
Structure of contact application

As the parser consumes the raw data file, it passes each value to the contact list via the ICSVColumn interface. Each column in the contact list maps to a different piece of contact information, so each is associated with a different instance of the ICSVColumn interface. The structure is represented in the figure on the right.

Satisfied that our design correctly obeys the rule of interface, we choose our language and move on to code.

Java and adapter classes
Java offers direct interface support through the interface keyword, which we'll use to write the ICSVColumn interface. That support is limited, though; it provides only one way for a class to directly implement an interface.

This limitation is a byproduct of the way Java overrides methods: If a class method's name and parameter signatures match those of a base class's or interface's method, the class method will override the base's method. Since overrides are determined entirely by name and parameter signature, a class cannot directly provide more than one implementation. A class must implement an interface indirectly to implement it repeatedly.

To work around this limitation, use a group of adapter classes. An adapter class, like the similarly named pattern from the GOF (Design Patterns, by Gamma et al., aka "The Gang of Four"), implements a well-known interface, delegating its implementation to an adaptee. The biggest difference between a group of adapter classes and the GOF adapter pattern is intent. The GOF pattern is used to convert one interface to another; a group of adapter classes is used to implement a single interface repeatedly.

In the code that follows, the ContactList class provides repeated implementations of the ICSVColumn interface via anonymous adapter classes.


interface ICSVColumn
{
  public void onRead( String value );
}

class CSVParser
{
  public void addColumn(
    ICSVColumn column )
  {
    // Add the column interface to
    // the list.
  }

  public void readFile(
    String fileName )
  {
    // As the file is read,
    // call each interface
    // in order.
  }
}

public class ContactList
{
  public void onReadFirstName(
    String value ) {}
  public void onReadLastName(
    String value ) {}
  public void onReadPhoneNum(
    String value ) {}
  public void onReadEmail(
    String value ) {}

  public void loadContacts(
    String fileName )
  {
    CSVParser parser = new CSVParser();
    parser.addColumn( new ICSVColumn() {
      public void onRead( String value ) {
        onReadFirstName( value ); } } );
    parser.addColumn( new ICSVColumn() {
      public void onRead( String value ) {
        onReadLastName( value ); } } );
    parser.addColumn( new ICSVColumn() {
      public void onRead( String value ) {
        onReadPhoneNum( value ); } } );
    parser.addColumn( new ICSVColumn() {
      public void onRead( String value ) {
        onReadEmail( value ); } } );

    parser.readFile( fileName );
  }
}

Java programmers may recognize that the AWT (Abstract Windowing Toolkit) uses this technique for event handling. The AWT uses adapter classes for the same purpose. One class implements a single interface repeatedly. In the case of the AWT, the interface would be one of the EventListener derivatives; the class would be an application-defined Container derivative. Such a container is designed to respond to the same type of events fired from different components.

C++ and interface interpreters
Unlike Java, C++ makes no distinction between interfaces and classes. A C++ interface is really an abstract base class, which, like all C++ classes, must use multiple inheritance to directly implement multiple interfaces. Multiple inheritance is fraught with danger and must be used with great care. Scott Meyers enumerates the caveats of multiple inheritance in Effective C++; refer to item 43. C++ and Java use the same name-matching mechanism to resolve method overrides. Therefore, as with Java, we cannot use multiple inheritance to directly implement an interface repeatedly.

Again, however, we can overcome this limitation. As Meyers illustrates (also in item 43), C++ provides a mechanism that safely uses multiple inheritance to effectively "rename" virtual functions. When we apply this mechanism to the problem of repeated interface implementation, we create classes that I like to call interface interpreters.

Like an adapter, an interface interpreter implements an interface by delegation. The difference is the identity of the object that implements the interface. Each adapter class defines a separate object; each interface interpreter defines a new pure virtual function for one object. In essence, this new function provides one interpretation of the interface -- hence the name. In the code below, the ContactList class uses multiple inheritance of interface interpreters to repeatedly implement the ICSVColumn interface:


class ICSVColumn
{
public:
  virtual void OnRead(
    LPCTSTR strValue ) = 0;
};

class CSVParser
{
public:
  void AddColumn( ICSVColumn *pColumn );
  void ReadFile( LPCTSTR strFileName );
};

class ICSVColumnFirstName: public ICSVColumn
{
public:
  void OnRead( LPCTSTR strValue )
    { OnReadFirstName( strValue ); }
  virtual void OnReadFirstName(
    LPCTSTR strValue ) = 0;
};

class ICSVColumnLastName: public ICSVColumn
{
public:
  void OnRead( LPCTSTR strValue )
    { OnReadLastName( strValue ); }
  virtual void OnReadLastName(
    LPCTSTR strValue ) = 0;
};

class ICSVColumnPhoneNum: public ICSVColumn
{
public:
  void OnRead( LPCTSTR strValue )
    { OnReadPhoneNum( strValue ); }
  virtual void OnReadPhoneNum(
    LPCTSTR strValue ) = 0;
};

class ICSVColumnEmail: public ICSVColumn
{
public:
  void OnRead( LPCTSTR strValue )
    { OnReadEmail( strValue ); }
  virtual void OnReadEmail(
    LPCTSTR strValue ) = 0;
};

class ContactList :
  ICSVColumnFirstName,
  ICSVColumnLastName,
  ICSVColumnPhoneNum,
  ICSVColumnEmail
{
public:
  void OnReadFirstName( LPCTSTR strValue );
  void OnReadLastName( LPCTSTR strValue );
  void OnReadPhoneNum( LPCTSTR strValue );
  void OnReadEmail( LPCTSTR strValue );

  void LoadContacts( LPCTSTR strFileName )
  {
    CSVParser parser;

    parser.AddColumn(
      (ICSVColumnFirstName *)this );
    parser.AddColumn(
      (ICSVColumnLastName *)this );
    parser.AddColumn(
      (ICSVColumnPhoneNum *)this );
    parser.AddColumn(
      (ICSVColumnEmail *)this );

    parser.ReadFile( strFileName );
  }
};

Delphi and procedural types
Like Java, Delphi treats interfaces and classes differently. As you might expect, adapter classes that work well in Java will work well in Delphi. Unfortunately, Delphi does not offer the convenience of anonymous class declaration, so similar code is a bit bulkier. However, Delphi also offers a completely different kind of support for repeated interface implementation. If the interface in question has only one method, Delphi allows you to declare that method as a procedural type.

Because ICSVColumn has only the OnRead method, we need not declare it with the interface or class keyword. Instead, we can simply declare it a procedure of object.

When declaring the procedural type, we must declare the parameters that the method expects without giving the method a name. Because procedural types have no method names, the implementing class may assign the method any name.

Furthermore, a single class may provide multiple interface implementations for the same procedural type. The procedural type still conforms to the rule of interface -- it identifies the object, defines a protocol, and hides the implementation -- but does so completely differently than adapter classes or interface interpreters.

Observe how the following code uses procedural types to achieve repeated interface implementation:


type
  TCSVColumn = procedure( strValue: string ) of object;

  TCSVParser = class(TObject)
  public
    constructor Create;

    procedure AddColumn( column: TCSVColumn );
    procedure ReadFile( strFileName: string );
  end;

  TContactList = class(TObject)
  public
    procedure OnReadFirstName( strValue: string );
    procedure OnReadLastName( strValue: string );
    procedure OnReadPhoneNum( strValue: string );
    procedure OnReadEmail( strValue: string );

    procedure LoadContacts( strFileName: string );
  end;

procedure TContactList.LoadContacts(strFileName: string);
var
  parser: TCSVParser;
begin
  parser := TCSVParser.Create;

  parser.AddColumn( OnReadFirstName );
  parser.AddColumn( OnReadLastName );
  parser.AddColumn( OnReadPhoneNum );
  parser.AddColumn( OnReadEmail );

  parser.ReadFile( strFileName );
end;

Java, C++, and Delphi each have their own mechanism for repeated interface implementation. Since none can claim to be the best solution, however, you can base your language selection on other factors. You won't need to select a target language until after you've designed your software and identified those other factors. Software designs that follow the four rules of modeling can be implemented in almost any modern object-oriented language.

Michael L. Perry has been a professional Windows developer for over six years and maintains expertise in COM+, Java, XML, and other technologies currently shaping the programming landscape. He formed Mallard Software Designs in 1998, where he applies the mathematical rigor of proof -- establishing the correctness of a solution before implementing it -- to software design. He is the moderator of ITworld.com's Windows Application Development discussion.




Sponsored Links

Dashboards & KPI Reporting for Business People
PivotLink provides a new perspective on your business with drillable dashboards & reports.
IP Networks Boost Secure Health Communications
AT&T provides secure communication to keep health care moving forward.
New Webcast: How to PROFIT WITH REMOTE SUPPORT
Discover how REMOTE SUPPORT can fuel your IT business in ways you've never thought of before.
FREE SECURITY AUDIT RESOURCES
Take a Risk Assessment, get White Papers on the Latest Threats, listen to Malware Expert Webcasts.
TAKE CONTROL OF REMOTE COMPUTERS
Support, configure and install applications and updates remotely for greater efficiency.
» Buy a link now

Advertisements
Sponsored links
Locate Hidden Software on business PCs with this free tool
Top 5 Reasons to Combine App Performance and Security
Bring harmony to your mix of UNIX-Linux-Windows computing environments
KODAK i1400 Series Scanners stand up to the challenge
 Home   Application Development  Programming tools  Programming languages  Java  Java Native Directory Interface
www.itworld.com    open.itworld.com     security.itworld.com     smallbusiness.itworld.com
storage.itworld.com     utilitycomputing.itworld.com     wireless.itworld.com

 
Contact Us   About Us   Privacy Policy    Terms of Service   Reprints  

CIO   Computerworld   CSO   GamePro   Games.net   IDG Connect   IDG World Expo   Industry Standard   Infoworld   ITworld   JavaWorld   LinuxWorld  MacUser   Macworld   Network World   PC World   Playlist  

Copyright © Computerworld, Inc. All rights reserved

Reproduction in whole or in part in any form or medium without express written permission of Computerworld Inc. is prohibited. Computerworld and Computerworld.com and the respective logos are trademarks of International Data Group Inc.