// Drawable shapes // // Handling a collection of polymorphic objects in a non-traditional style. // BRules are in effect: no virtual functions, no assignments, no // overriding. // Yet this file achieves the _full_ separation of interface and // implementation. Even if Shape is extended, the code that deals with // abstract shapes does not even have to be recompiled! Compare to // Shapes-oop.cc file in this directory. Another advantage of the // present implementation is that _both_ Square and Rectangle objects // can be passed to a function set_dim(). Depending on the new // dimensions, a square can *become* a rectangle or a rectangle square. // Also compare this file to a similar code Shapes-h.hs, which is written // in a pure functional language Haskell. The similarity is uncanny! // // See ../No-problem/Readme.txt for details and explanations, section // "Existential types with BRules" // $Id: Shapes-no-oop.cc,v 1.2 2003/01/27 21:30:50 oleg Exp oleg $ #include using std::ostream; using std::cerr; using std::endl; //------------------------------------------------------------------------ // Public interfaces // An existential datatype class Shape; // There is _nothing_ known about its structure ostream& draw(ostream& os, const Shape& shape); const Shape * offset_by(const Shape& shape, const float x, const float y); const Shape * set_dim(const Shape& shape, const float width, const float height); // This is supposed to be a list of shapes to draw // For simplicity, we'll hold three shapes only // For a functional implementation of a list (with garbage collection), // see ../No-problem/Bag.cc class Shapes { const Shape& s1; const Shape& s2; const Shape& s3; public: Shapes(const Shape * const _s1, const Shape * const _s2, const Shape * const _s3) : s1(*_s1), s2(*_s2), s3(*_s3) {} friend ostream& draw(const Shapes& shapes, ostream& os); friend Shapes pack(const Shapes& shapes); friend Shapes resize(const Shapes& shapes); }; ostream& draw(const Shapes& shapes, ostream& os); Shapes pack(const Shapes& shapes); Shapes resize(const Shapes& shapes); //------------------------------------------------------------------------ // Implementation // Note that the following three functions on Shapes are implemented // _before_ the Shape class is revealed. Therefore Shapes-based // functions do not depend on an implementation of a Shape _at all_. ostream& draw(const Shapes& shapes, ostream& os) { return draw(draw(draw(os,shapes.s1),shapes.s2),shapes.s3); } Shapes pack(const Shapes& shapes) { return Shapes( offset_by(shapes.s1,0,1), offset_by(shapes.s2,2,3), offset_by(shapes.s3,4,5)); } Shapes resize(const Shapes& shapes) { return Shapes( set_dim(shapes.s1,10,10), set_dim(shapes.s2,10,11), set_dim(shapes.s3,11,12)); } // Finally, the details of the Shape are revealed. Shape may appear // very similar to the Abstract base class in file // Shapes-oop.cc. There is an important difference however. Unlike // virtual functions, the three function pointers in the body of the // Shape are _constant_ pointers. Once set, they may _not_ be // overridden. // // Note a similarity to newbus or vnode interfaces, see e.g., // http://people.freebsd.org/~asmodai/newbus-draft.txt // (search the latter file for 'INTERFACE FOO') class Shape { ostream& (* const draw)(ostream& os, const Shape& shape); const Shape * (* const offset_by)(const Shape& shape, const float x, const float y); const Shape * (* const set_dim)(const Shape& shape, const float width, const float height); protected: Shape(ostream& (* const _draw)(ostream& os, const Shape& shape), const Shape * (* const _offset_by)(const Shape& shape, const float x, const float y), const Shape * (* const _set_dim)(const Shape& shape, const float width, const float height)) : draw(_draw), offset_by(_offset_by), set_dim(_set_dim) {} friend ostream& draw(ostream& os, const Shape& shape); friend const Shape * offset_by(const Shape& shape, const float x, const float y); friend const Shape * set_dim(const Shape& shape, const float width, const float height); }; // Note the lack of public constructors in the above class (and the // lack of any public interface for that matter) ostream& draw(ostream& os, const Shape& shape) { return shape.draw(os,shape); } const Shape * offset_by(const Shape& shape, const float x, const float y) { return shape.offset_by(shape,x,y); } const Shape * set_dim(const Shape& shape, const float width, const float height) { return shape.set_dim(shape,width,height); } // Two concrete instances of the existential Shape class. // A static_cast<> below may appear a hack. Yet it is safe. It's merely // a consequence of difficulties in expressing function closures in C++. // The similar Shapes-h.hs code, written in a _strongly_ statically-typed // language Haskell, typechecks without a single error or warning. class Rectangle : public Shape { const float x, y; const float width, height; static ostream& draw(ostream& os, const Shape& _s) { const Rectangle & s = static_cast(_s); return os << "Drawing a rectangle [" << s.x << ", " << s.y << "] - [" << (s.x+s.width) << ", " << (s.y+s.height) << "]" << endl; } static const Shape * offset_by(const Shape& _s, const float x, const float y) { const Rectangle & s = static_cast(_s); return new Rectangle(s.x+x,s.y+y,s.width,s.height); } static const Shape * set_dim(const Shape& _s, const float width, const float height) { const Rectangle & s = static_cast(_s); return make(s.x,s.y,width,height); } // A private constructor Rectangle(const float _x, const float _y, const float _width, const float _height) : Shape(&draw,&offset_by,&set_dim), x(_x), y(_y), width(_width), height(_height) {} public: static const Shape * make(const float width, const float height) { return make (0,0,width,height); } static const Shape * make(const float x, const float y, const float width, const float height); // for the implementation, see below. }; class Square : public Shape { const float x, y; const float size; static ostream& draw(ostream& os, const Shape& _s) { const Square & s = static_cast(_s); return os << "Drawing a square [" << s.x << ", " << s.y << "] of size " << (s.size) << endl; } static const Shape * offset_by(const Shape& _s, const float x, const float y) { const Square & s = static_cast(_s); return new Square(s.x+x,s.y+y,s.size); } static const Shape * set_dim(const Shape& _s, const float width, const float height) { const Square & s = static_cast(_s); return Rectangle::make(s.x,s.y,width,height); } // A private constructor Square(const float _x, const float _y, const float _size) : Shape(&draw,&offset_by,&set_dim), x(_x), y(_y), size(_size) {} public: static const Shape * make(const float size) { return new Square(0,0,size); } static const Shape * make(const float x, const float y, const float size) { return new Square(x,y,size); } }; const Shape * Rectangle::make(const float x, const float y, const float width, const float height) { return width == height ? Square::make(x,y,width) : new Rectangle(x,y,width,height); } //------------------------------------------------------------------------ // Test int main(void) { cerr << "Instantiating shapes..." << endl; Shapes shapes(Rectangle::make(1,2), Rectangle::make(5,5), Rectangle::make(7,7)); cerr << "Drawing ... some rectangles are actually squares" << endl; draw(shapes,cerr); cerr << "Packing ..." << endl; Shapes shapes_packed = pack(shapes); draw(shapes_packed,cerr); cerr << "Resizing ... rectangles turn squares and vice versa" << endl; Shapes shapes_resized = resize(shapes_packed); draw(shapes_resized,cerr); cerr << "\nAll done" << endl; return 0; }