5. Using Interfaces to Minimize Code Modification
As described, the process of creating a distributed implementation from a local implementation requires code changes. While these are few, the fact is that one must change and recompile the code. This is a drawback, because it means that one has to worry about two versions of an application and not just one. By investing a little more time in the local implementation, one can make it so that the conversion to the distributed version requires no re-compilation; the use of a remote class instance can be enabled at run-time.

The idea is to define an interface class that contains the public methods (excluding constructors and static methods) for the server class. One then has the server class and its associated client implementation (CI) implement this interface. In the client class, one programs using an interface class reference, and then dynamically loads either the server class or its client interface depending upon if one wants a local or a distributed implementation. The structure for the sample two component application involving App and Aclass is the following

As an example, an interface describing the methods of Aclass used in the first example
(Sample1/Aclass.java) has the following form

public interface AclassIf
{
    public int doCalculation(int D);
}

Now, by having Aclass implement this interface, and writing the code in App to only use this interface, one can create an App class that does not need to be recompiled when the distributed implementation is used. (After creating Aclass so that it implements the AclassIf interface, then AclassCI and AclassSI are generated using cam.codegen.CreateCISI. The program cam.codegenCreateCISI will automatically have AclassCI implement the AclassIf interface if Aclass does.)

Sample5/App.java and Sample5/Aclass.java contain the implementations of App and Aclass that utilize this construction.
Of interest in these classes is the mechanism by which either the local or remote instance of Aclass is instantiated by the App class.
The App constructor has the following form, exhibiting how this is accomplished with dynamic loading;

public App(boolean localFlag)
{
    String serverClassName   = "Aclass";
    String serverClassNameCI = "AclassCI";
    String address = "127.0.0.1";
    int    port    = 6789;

    Class  theClass  = null;
    Object theObject = null;
    if(localFlag) // local implementation
    {
      try
      {
      theClass   = Class.forName(serverClassName);
      theObject  = theClass.newInstance();
      }
      catch(Exception ex)
      {System.out.println("Class Not Found : " + ex.getMessage());};
    }
    else // distributed implementation
    {
    try
    {
        theClass   = Class.forName(serverClassNameCI);
        theObject  = theClass.newInstance();
    }
    catch(Exception ex)
    {System.out.println("Class Not Found : " + ex.getMessage());};
    // request the remote instance
    try
    {
    ((cam.netapp.CIinterface)theObject).setVerboseFlag(true);
    ((cam.netapp.CIinterface)theObject).createServerInstance(address,port);
    }
    catch(Exception e){System.out.println(e);};
    }
//
//  Cast the object to type AclassIf
//
    A = (AclassIf)theObject;
}

So, depending on the flag passed into the App constructor, either a local or a distributed implementation is created. It is important to note that the method createServerInstance(...) is accessed via the interface cam.netapp.CIinterface. The use of the cam.netapp.CIinterface interface ensures that the initialization code does not require the any explicit reference to the AclassCI class. (This is important because this ensures that the existence of AclassCI is not necessary for App to compile)

Here are the results obtained with App.main(...)

 Local Results
The result of the calculation = 7 (and should = 7)

 Distributed Results
The result of the calculation = 7 (and should = 7)

So, if one is using the NetApp wrapper constructs, in order to create applications in which the transition from a local to a distributed implementation requires minimal re-coding one needs to

  1. Create both a server class and a server class interface that the server class implements.

  2. Have all client applications utilize the server interface reference for all server method calls

  3. Initialize the server instance by dynamically loading the server class or the server class client (CI) implementation by name.

The cost of carrying out this work is that of keeping the server class and the server class interface synchronized. This is a source of errors, but, fortunately, the compiler typically lets you know if they are not synchonized.