Sorry if this has been discussed before (I'm almost certain it has), but
I didn't know what to google for.
My problem is, I have a class, a gtkmm widget, and I want it to serve as
a base class now, but I'm not sure if I'm taking the proper steps in
order to not break the whole class.
Are there any guidelines what I have to watch out for?
One of the few general principles I can think of is to make sure
objects have the appropriate polymorphic behavior. What happens if a
Descendant* is used as a Base*, or a Descendant& is used as a Base&?
Most everything else depends too much on the design specifics;
consequently, some of my suggestions below may not work for your case.
One question would e.g. be:
Some (or many) members which have been private so far, probably need to
be protected now. One problem is that I can't really tell in advance
which of those member should stay private and which ones should become
protected, because that mostly depends on what people intend to do with
their class when deriving from my widget.
But I suppose it's a bad idea to take this as an argument for making ALL
private members protected now?
C++ FAQ section 19, answers 7-9 deal with public/protected interfaces:
http://new-brunswick.net/workshop/c++/faq/basics-of-inheritance.html#faq-19.7
Just remember that when the author talks about return on investment,
investment in the protected interface will reduce costs for
descendants associated with interface maintenance and upgrade.
What to make protected and what to make private is a difficult design
issue to fully address in the general case. My first bit of advice is
to transfer few private members to the protected interface at the
start. You can always add to the protected interface in later
versions.
Some of the same design considerations for public interfaces will
apply to protected interfaces. For instance, divorce implementation
from an object's more abstract properties as much as possible. You
won't get as much of a separation in the protected interface as in the
public, but you will get some. This can be as simple as typedef-ing
types for protected fields. The separation protects descendants from
changes in implementation. Another consideration is speed versus
stability. Private methods can be unstable in that they don't perform
many safety checks. Public methods should provide more in terms of
stability, perhaps at a speed cost. Protected methods could be
somewhere in between, checking subtle errors but leaving more obvious
ones to the developers of the descendants. Just don't forget to
document what future developers need to handle.
If you've got the time, another tool which sometimes applies is to
define invariants on the data stored in an object. All public
operations on the object must not invalidate the invariant. If a
private method breaks an invariant, don't make it protected. Similar
to what you later suggest, you can add protected methods which
accomplish the affects of particular sequences of private methods.
You can define relaxed versions of the public invariants for the
protected interface. Each protected method respects the protected
invariants, and particular sequences of protected methods will
preserve the public invariants. In the documentation for the
protected interface, require all public methods of descendants which
use a protected method must use it as part of such a sequence. This
isn't as safe as the public invariants, but is sometimes all you can
achieve.
Related to invariants are preconditions and postconditions,
specifically of the private methods. For safety's sake, sometimes
you'll want to create protected methods which check the preconditions
and then call a private method only if the check succeeds, or create
protected methods which clean-up after calling some private method.
Another question:
Is it sufficient to make the members in question protected, or should
they stay private and protected accessor methods introduced?
Creating protected accessors to private members is an intermediate
step (and a final one for some private fields). A more final solution
would be to define protected methods which "partially expose" the
private interface. These protected methods hide implementation
details, perform aggregates of the private operations (as when dealing
with invariants) and define operations on private fields (which may be
unneeded for the base's member functions).
For example, suppose a base class has a set of files. The set is
invisible publically, but it makes sense for subclasses to add to,
remove from or query what's in the set. The set is implemented as a
priority queue (and has no method corresponding to "member of"), but
this detail is to be hidden from subclasses. Thus the base is given
protected methods which treat the priority queue as a set. Not a
great example, as it makes sense for a priority queue to support the
interface of a set, but still illustrative.
Kanenas