Click here to Skip to main content
15,898,222 members
Articles / Desktop Programming / MFC

Meta Class & Interface

Rate me:
Please Sign up or sign in to vote.
2.19/5 (13 votes)
30 Apr 20033 min read 46.6K   23   1
Use abstract class as interface incoporated with meta class as factory

Introduction

Designing a C++ application involves designing a good interface. As the application grows, from small to medium and large size, there exists a need to break the core into separate modules. As Stroustrupt observes: "What is important is the interface. Each module should expose a skinny interface." The main objective of this article is to illustrate a technique to separate interface from implementation so any change in implementation does not enforce the client to recompile. Please note that this technique is not the best, but "applicable". Of course, there do exist many more patterns. I stress "applicable", so you should consider whether this is suitable for your design before deciding to follow the pattern.

Requirement

  • C++ Object-Oriented Concepts
  • Booch Method Understanding

Problem

To keep things simple, let's assume we want to develop an address book application which holds entries of persons. Each entry composes of name, address, e-mail and telephone. All information is stored as string--no data checking. The application must:

  • provide a way to query entry
  • display all entries in a table

Analysis

The requirement demands a kind of query--database which is out of the scope of this article. We will focus on the entry design. Since each entry holds information of one person, including name, address, e-mail and telephone, we can create a class Person to represent this object. The class Person holds many strings for its data. But soon, we realize that they want us to add more information like nickname, birth date, home telephone, office telephone, ... the data string of our Person class will grow larger, and you think this is not a flexible solution. So, you adapt an Array class to hold those strings.

Design

The trick comes with the class Array. A simple solution should look like this:

Sample screenshot

But soon you realize that you want to move Array to collection module. We can still use the same Array class. But each change in implementation of Array will affect Person to recompile. And here comes our technique:

Sample screenshot

C++
// class interfaces 
class Interface { 
public: 
  virtual void kill()=0; 
};
// metaclass interfaces 
class IClassDesc { 
public: 
  virtual const char* name()=0; 
  virtual const char* category()=0; 
  virtual void* create()=0; 
}; 
// array interfaces 
class IArray { 
public: 
  enum ValueType {STRING, INTEGER, FLOAT }; 
  virtual void add(const char* text)=0; 
  virtual void add(int value)=0; 
  virtual void add(float value)=0; 
  virtual void insert(const char* text)=0; 
  virtual void insert(int value)=0; 
  virtual void insert(float value)=0; 
  virtual void remove(const char* text)=0; 
  virtual void remove(int value)=0; 
  virtual void remove(float value)=0; 
  virtual void clear(ValueType valueType)=0; 
  virtual void clear()=0; 
}; 
// array implementation 
class Array : public IArray { 
public: 
  // If the class is dynamic and ArrayDesc
  // use new to create, we must use delete. 
  // If the class is static and ArrayDesc
  // return reference, do nothing. 
  virtual void kill() { delete this; } 
  virtual void add(const char* text); 
  ...
 ...
private:
 vector<int> intArray;
 vector<float> floatArray;
 vector<string> stringArray; 
}; 
// array descriptor 
class ArrayDesc : public IClassDesc { 
public:
 virtual const char* name() { return "Array"; }
 virtual const char* category() { return "Collection"; }
 virtual void* create() { return new Array; } 
};
static ArrayDesc theArrayDesc; 
IClassDesc* getArrayDesc() { return &theArrayDesc; } 
// Person class which uses IArray and IClassDesc 
class Person { 
public:
  enum { NAME, ADDRESS, EMAIL, TEL };
  IArray *array;
  Person() { array = (IArray*)getArrayDesc()->create(); } 
  ~Person() { array->kill(); }
  ...
  ... 
};

Conclusion

A good design reflects the ability to separate interface from implementation. As we have seen, the Interface & Metaclass pattern fit well and makes it possible. But we come to a trade-off:

  • Logical design: The number of classes grows 3x which means every class requires one interface, one descriptor, and one implementor.
  • Physical design: The system becomes fatter: files grow 2x which means interface.h, interface.cpp, implement.h, implement.cpp which leads to hard maintenance. Stroustrupt also recognizes this as "an overkill for the good interface".

Here we come to the end of this article. Thank you for reading this article. I hope that it would help you simplify your module in some way. If you have any recommendations, feel free to e-mail me at any time. :-)

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Japan Japan
Smile | :) Smile | :) Smile | :)

Comments and Discussions

 
GeneralPrivate destructor to prevent stack instantiation and better encapsulation etc. Pin
Rompa4-May-03 2:26
Rompa4-May-03 2:26 
Perhaps it would be a good idea to make the destructors private so that you cannot accidentally create one of these on the stack which will result in kill() deleting an object that wasn't newed?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.