Jump to content
  • Advertisement
Sign in to follow this  
GenuineXP

Encapsulation Question

This topic is 3610 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm wondering about how to approach an encapsulation problem with a simple template I'm writing called region. region is nothing more than a representation of a rectangular... well, region! Many project use this construct, often named something like rect or area. Anyway, region contains four simple pieces of data: a pair of x/y coordinates, and a width and height. From this, the top, bottom, left, right, area, etc. are derived, and can both read and written. I'm wondering about which approach I should take to implement this, given that the four pieces of data mentioned above are all exposed via a public interface. APPROACH A: Internalized Class Template The approach that came naturally to me was to just put together a simple class template that contained all region-related functions (e.g., there are no or very few non-member functions that work with regions).
template<typename T>
class region {
public:
    region(): x_(0), y_(0), width_(0), height_(0) {}
    template<typename U>
    region(const region<U>& other): x_(static_cast<T>(other.x_), ... {}
    ...
    // Data members are directly exposed.
    const T x() const { ... }
    const T y() const { ... }
    const T width() const { ... }
    const T height() const { ... }
    ...
    // Additional functions provide some more abstraction.
    const T bottom() const { ... }
    void bottom(const T& value) { ... }
    ...
    const T area() const { ... }
    ...
private:
    T x_, y_, width_, height_;
};
This seems all well and good, except that in my reading I've learned that if everything I need to know to perform an action on an object is already exposed via its public interface, than using non-member functions actually benefits/increases encapsulation. In this case, the ideas of left, right, top, and bottom can be extrapolated from the public exposure of a region's x/y coordinates and dimensions, which leads to the next approach. APPROACH B: Class Template with Non-Member Functions This approach is very similar to the previous one, but changes some member functions into non-member functions (because these member functions can work just as well by using a region's public interface in the first place).
template<typename T>
class region {
public:
    region(): x_(0), y_(0), width_(0), height_(0) {}
    template<typename U>
    region(const region<U>& other): x_(static_cast<T>(other.x_), ... {}
    ...
    // Data members are directly exposed.
    const T x() const { ... }
    const T y() const { ... }
    const T width() const { ... }
    const T height() const { ... }
    ...
private:
    T x_, y_, width_, height_;
};
// Additional (non-member) functions provide some more abstraction.
template<typename T>
const T bottom(const region<T>& r) { ... }
template<typename T>
void bottom(const region<T>& r, const T& value) { ... }
...
template<typename T>
const T area(const region<T>& r) { ... }
By using the x, y, width, and height accessors, the now non-member functions bottom, bottom, and area and still work properly without having direct access to private data. Scott Meyers points out in some of his writings that this is favorable over member functions that don't really need to be member functions (with the only cost being some inconsistent usage, i.e., obj->func() versus func(obj)). APPROACH C: Struct Template with No Member Functions The last approach that I've thought of is a bit different. Since region exposes all of its member anyway (and I can't see a situation where its members are likely to change), it could be written as a simple struct template that users operate on exclusively via non-member functions (or direct member access).
template<typename T>
struct region {
    ...
    T x, y, width, height;
};
// No need for basic accessors any longer; just work with x, y, width, and height directly.
// Additional functions provide some more abstraction.
template<typename T>
const T bottom(const region<T>& r) { ... }
template<typename T>
void bottom(const region<T>& r, const T& value) { ... }
...
template<typename T>
const T area(const region<T>& r) { ... }
This is a bit dangerous, I suppose, since any changes to region's implementation or data members will break a lot of code. However, being quite simple, this is probably a case where those kinds of changes are very unlikely. So, which approach should be favored, A, B, or C? I'm leaning towards B, because it provides typical object-oriented style of encapsulation (e.g., private data members) as well as tight encapsulation by leveraging region's public interface without creating unnecessary member functions. Any thoughts or ideas here? Discuss? Thanks!

Share this post


Link to post
Share on other sites
Advertisement
How many ways are there to represent a rectangle? Only two: top-left-width-height or top-left-bottom-right. Once you choose one, the only consideration for changing it would be performance, which would possibly allow using a high-performance type only in critical code instead of using it everywhere.

I would go with version C: a simple POD structure with four members.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!