4.1 Abstract Bases as Interfaces Approach Contd ...

Extending the current interface

Now consider a scenario where the vendor decides to provide an additional  utility called FindN. This will find a character at N position.

One of the very initial option that one can think of is to extend the current interface. To add one more function in the interface definition.

class IFastString {
public:
// faux version 1.0
virtual void Delete(void) = 0;
virtual int Length(void) = 0;
virtual int Find(const char *psz) = 0;
// faux version 2.0
virtual int FindN(const char *psz, int n)
} ;


This will work as long we do not change the sequence of the earlier definition and keep the new definition at the bottom. The new application will crash with the old DLL, because when it calls the FindN it will crash.



Also, this approach defies the encapsulation by modifying the public interface.



Exposing Multiple Interfaces

The most reasonable way to manage these kind of updates is to expose multiple interfaces. Consider a scenario where you might want to expose Save and Load functionality. You can very well go ahead and add it in the IFastString interface, but that would make much less sense as there might be other interfaces which need Save/Load other than FastString. The best way to do this is by introducing a new interface IPersistent interface.



class IPersistentObject {
public:
virtual void Delete(void) = 0;
virtual bool Load(const char *pszFileName) 0;
virtual bool Save(const char *pszFileName) 0;
} ;


class IFastString {
public:
virtual void Delete(void) = 0;
virtual int Length(void) const = 0;
virtual int Find(const char *psz) const 0;
} ;

class FastString : public IFastString,public IPersistentObject 
{
   int m_cch; // count of characters
   char ";"'m_psz;
public:
   FastString(const char *psz);
   ~FastString(void);
   
   //Common methods
   void Delete(void); II deletes this instance
   
   //IFastString methods
   int Length(void) canst; II returns # of characters
   int Find(const char *psz) canst; II returns offset
   
   //IPersistentObject methods
   bool Load(const char *pszFileName);
   bool Save(const char *pszFileName);
} ;



The FastString will now compromise of two virtual pointer for each interface. We can link to the desired interface using RTTI. Using dynamic_cast we can link to the requested interface of the object.
The dynamic_cast in C++ helps to determine if an object is in-fact inherited by any base class. If the Interface is not supported by the class NULL will be returned.







Now to access the interfaces , client must code something like :


bool SaveString(IFastString *pfs, canst char *pszFN){
   bool bResult = false;
   IPersistentObject *ppo =
                   dynamic_cast<IPersistentObject*>(pfs);
   if (ppo)
       bResult = ppo->Save(pszFN);
   
    return bResult;
}

This will work fine without any issue. BUT the use of RTTI mechanism by Client is a blocking factor because the RTTI is intensely compiler dependent. This brings us back to the same problem again. Each vendor has very complex way of implementing the RTTI.


Simulate RTTI


The most effective alternative is to expose a functionality from each interface which can be then implemented by class to mimic dynamic_cast.


class IExtensibleObject {
public:
   virtual void *Dynamic_Cast(const char* pszType) =0;
   virtual void Delete(void) = 0;
} ;
class IPersistentObject : public IExtensibleObject {
public:
   virtual bool Load(const char *pszFileName) 0;
   virtual bool Save(const char *pszFileName) 0;
} ;
class IFastString : public IExtensibleObject {
public:
   virtual int Length(void) = 0;
   virtual int Find(const char *psz) 0;
} ;

 
It made more sense to move the new function Dynamic_Cast and Delete to another base class , as these functionality is desired both by IFastString and IPersistentObject

The client now perform casting in the following way:

bool SaveString(IFastString *pfs, const char *pszFN){
   bool bResult = false;
   IPersistentObject *ppo = (IPersistentObject*)
                               pfs->Dynamic_Cast("IPersistentObject");
   if (ppo)
       bResult = ppo->Save(pszFN);
   return bResult;
} 



Now in the DLL , since we have the implementing class deriving from all the Interfaces we can use static_cast instead.


void *FastString: :Dynamic_Cast(const char *pszType) {
   
   if (strcmp(pszType, "IFastString") == 0)

      return static_cast<IFastString*>(this);
   else if (strcmp(pszType, "IPersistentObject") 0)
      return static_cast<IPersistentObject*>(this);
   else if (strcmp(pszType, "IExtensibleObject") 0)
      return static_cast<IFastString*>(this);
   else
      return 0; // request for unsupported interface
}




Note: When asked for IExtensibleObject , IFastString is returned because IExtensibleObject is present in both IFastString and IPersistentObject making it ambiguous. 




No comments:

Post a Comment