|
|
|
Advertisement
|
class PyRect:
'''Axis aligned rectangle'''
def __init__(self, top, left, width, height)
'''Initialise with the given position/dimensions'''
....
I have no particular reason for using width,
height rather than right, bottom
other than to indicate that there is no canonical way of specifiying an
axis-aligned rectangle; if there were, the bug I'm discussing would have
been less likely to trouble us.
We can now create PyRect objects:
r1 = PyRect(0, 0, 288, 256)
r2 = PyRect(top=0, left=0, width=288, height=256)
r3 = PyRect(left=0, top=0, height=256, width=288)
Here, r1, r2 and r3 are equivalent.
r2 and r3 are created using keyword argument
syntax, which allows us to pass in parameters in the order we chose.
In C++, we can do something which superficially resembles this, allowing us
to create Rectangles without fretting over parameter order.
class Rectangle
{
public:
/**
* Default constructor builds an empty
* rectangle based at (0, 0).
*/
Rectangle()
: top(0), left(0), width(0u), height(0u)
{
}
/**
* Set the rectangle top coordinate.
* @param new_top
* @returns a reference to self (for use
* in method chaining).
*/
Rectangle & top(int new_top)
{
top = new_top;
return *this;
}
Rectangle & left(int coord)
....
private:
int top, left;
unsigned width, height;
};
We can construct objects of this class as follows:
Rectangle r1 = Rectangle()
.height(256)
.width(288);
Rectangle r2 = Rectangle()
.top(0)
.left(0)
.width(288)
.height(256);
Rectangle r3 = Rectangle()
.width(288)
.height(256);
This technique is known as the Named Parameter Idiom
[3].
Rectangle constructor:
class Xcoord
{
public:
explicit Xcoord(int x);
....
};
Having set up similar wrapper classes for Ycoord,
Width, Height, we declare the
Rectangle constructor:
class Rectangle
{
public:
Rectangle(Xcoord x,
Ycoord y,
Width w,
Height h);
....
};
and the C++ type-system is once again working for us:
Rectangle const full_screen(Xcoord(0),
Ycoord(0),
Width(288),
Height(256));
This technique is known as the Whole Value Pattern.
Note that with our strongly type-differentiated arguments we could, if we wanted, overload the constructor, allowing clients to supply parameters in a different order.
Xcoord,
Ycoord, Width and Height classes
all need to implement the same arithmetical operators, and they must all
allow access to the base value; in effect all we have done is replicate
the same class to give it four distinct types.
(Note that if this really does turn out to be the case, then we've probably failed to identify the values used by our program correctly: the Whole Value Pattern isn't just about type-safety, it's also about identifying and properly encapsulating the separate value-based types in our problem domain.)
This problem is discussed in more depth in an article by Mark Radford [5], which identifies three candidate mechanisms for generating the required class families:
The STLSoft library [6] implements
the second mechanism with its true_typedef class template [7], which allows us to define our wrapper
classes as follows:
#include <stlsoft/true_typedef.hpp>
// Tag types
struct Xcoord_t;
struct Ycoord_t;
struct Width_t;
struct Height_t;
typedef stlsoft::true_typedef<int, Xcoord_t> Xcoord;
typedef stlsoft::true_typedef<int, Ycoord_t> Ycoord;
typedef stlsoft::true_typedef<unsigned, Width_t> Width;
typedef stlsoft::true_typedef<unsigned, Height_t> Height;
Here, declaration and use of the Rectangle constructor remain
as before; the STLSoft header takes care of the rest.
I mentioned before that there is no canonical way to construct a rectangle. There is, however, a canonical way to construct a point on a display screen— that is, to supply its X and Y coordinates, in that order. So, if we write:
Point const bottom_right(288, 256);we can be confident our
Point really will be at the bottom right hand corner of the
screen.
Instead of using coordinates and coordinate differences, we might,
then, prefer to construct our
So, then, although there is no one standard way to construct a rectangle, one
of the standard ways is less vulnerable to misuse!
Rectangle from two of its corners:
class Rectangle
{
public Rectangle(Point const & top_left_corner,
Point const & bottom_right_corner);
....
};
Anyone with a basic grasp of geometry will realise that if we wish to
define a rectangular region on-screen from two corners, then those two
corners must be diagonal opposites—just think of creating a
rectangle in a graphical drawing package by anchoring one corner then
dragging the other corner into position. There are eight permutations:
Of these, the first is the outstanding favourite, even without looking at
the class documentation. What's more, a little simple logic in the
Rectangle constructor allows it to make sense of the other
seven, perhaps outputting a warning if it does not receive the expected
pair.
|
Sponsored Links
|