Quote:Original post by SMcNeil
In the example above, I have an abstract class called "Isomeclass" and I have two classes that inherit from "Isomeclass". How do I know which class ("myClass" or "myOtherclass") is being called when I execute the following: "shared_ptr<Isomeclass> m_something"? The only explanation I've head is that "The compiler uses a 'V-Table' and is smart enough to know what you’re talking about."
Ah - the mysteries of C++ [smile]. The important thing to note wrt that code snippet is, that it shouldn't matter which of the classes is actually being used.
An interface is kind of a contract between the ones that use the services provided by the interface (the "clients") and the programmer who creates the class that implements the interface. The rationale behind doing so is that the implementor is free in choosing how exactly the interface is implemented, while the client regards the inner workings as a black-box that is none of his/her business.
Once it matters to the client, whether "Isomeclass" is implemented by myClass or by myOtherClass you have done something wrong in your design. Let's be a little more specific here. Suppose we have system that controls a display device.
A primitive interface could look like this:
class IDisplay {public: virtual void clear() = 0 { } virtual void printCharacter( char ) = 0 { } virtual void setCursorPosition( int, int ) = 0 { } virtual void setForegroundColour( Colour const & ) = 0 { } virtual void setBackgroundColour( Colour const & ) = 0 { } virtual void getCursorPosition( int &, int & ) const = 0 { } virtual void getDimensions( int &, int & ) const = 0 { }};
Now let's implement the interface for different devices:
// some primitive ANSI terminal (untested - might not be working at all :)class AnsiTerminalDisplay : public IDisplay { static std::string const EscapeSequence; int dimensionX; int dimensionY; std::string ToAnsiColour( Colour const & ) const; int col, row;public: AnsiTerminalDisplay( int dimensionX = 80, int dimensionY = 40 ) : dimensionX( dimensionX ), dimensionY( dimensionY ), col( 1 ), row( 1 ) { } virtual void clear() { std::cout << EscapeSequence << "J"; } virtual void printCharacter( char character ) { std::cout << character; } virtual void setCursorPosition( int col, int row ) { assert( column > 0 && column <= DimensionX ); assert( row > 0 && row <= DimensionY ); this->row = row; this->col = col; std::cout << EscapeSequence << row << ";" << col << "f"; } virtual void setForegroundColour( Colour const & colour ) { std::cout << EscapeSequence << ToAnsiColour( colour, true ); } virtual void setBackgroundColour( Colour const & colour ) { std::cout << EscapeSequence << ToAnsiColour( colour, false ); } virtual void getCursorPosition( int & col, int & row ) const { col = this->col; row = this->row; } virtual void getDimensions( int & dimensionX, int & dimensionY ) const { dimensionX = this->dimensionX; dimensionY = this->dimensionY; }};std::string const AnsiTerminalDisplay::EscapeSequence = "\033[";// this class uses some touch-screen deviceclass MyTouchScreenDisplay : public IDisplay { ITouchScreenDevice & device;public: MyTouchScreenDisplay( ITouchScreenDevice & device ) : device( device ) { } virtual void clear() { device.clear(); } virtual void printCharacter( char character ) { device.plot( character ); } virtual void setCursorPosition( int col, int row ) { // suppose the device is addressed by means of pixels device.moveTo( col * device.getCharWidth(), row * device.getCharHeight()); } virtual void setForegroundColour( Colour const & colour ) { device.setSecondaryColour( colour ); } virtual void setBackgroundColour( Colour const & colour ) { device.setPrimaryColour( colour ); } virtual void getCursorPosition( int & col, int & row ) const { // suppose the device is addressed by means of pixels col = device.getPositionX() / device.getCharWidth(); row = device.getPositionY() / device.getCharHeight(); } virtual void getDimensions( int & width, int & height ) const { // suppose the device is addressed by means of pixels width = device.getDisplayWidth() / device.getCharWidth(); height = device.getDisplayHeight() / device.getCharHeight(); }};
The 1 million dollar question is - should any client that uses IDisplay care, whether it actually outputs to an ANSI-terminal or to some touch-screen device?
Right! It shouldn't matter, because these are implementation details and the business-logic that uses IDisplay should work on both devices and never care about the black-box that performs the grunt work beneath that layer of abstraction.
Now while this example looks over-simplified and artificial, it sufficiently illustrates the intention (at least I hope so [wink]). Observe how both constuctors take different, implementation-specific arguments and how the TouchScreenDisplay uses yet another level of abstraction (the "TouchScreenDevice) to perform its work.
The real fun starts, if we were to add yet another display device - say one that instead of plotting stuff, redirects the commands to some remote interface for debugging. If IDisplay clients would have to know about the underlying implmenetation, we'd have to refactor the whole thing again, which would be both time-consuming and error-prone.
I hope that makes some sense, with time you'll stumble across many such issues and things will get clearer. Good design is something that is not just founded on theory, it also needs experience and a good deal of intuition (which is why I'm not particularly good at it [lol]).
Cheers,
Pat.