tags:

views:

629

answers:

3

Premise

I believe that there is a way to objectively define "Good" and "Bad" Object-Oriented design techniques and that, as a community we can determine what these are. This is an academic exercise. If done with seriousness and resolve, I believe it can be of great benefit to the community as a whole. The community will benefit by having a place we can all point to to say, "This technique is 'Good' or 'Bad' and we should or should not use it unless there are special circumstances."

Plan

For this effort, we should focus on Object-Oriented principles (as opposed to Functional, Set-based, or other type of languages).

I'm not planning on accepting one answer, instead I'd like the answers to contribute to the final collection or be a rational debate of the issues.

I realize that this may controversial, but I believe we can iron something out. There are exceptions to most every rule and I believe this is where the disagreement will fall. We should make declarations and then note relevant exceptions and objections from dissenters.

Basis

I'd like to take a stab at defining "Good" and "Bad":

  • "Good" - This technique will work the first time and be a lasting solution. It will be easy to change later and will pay the time investment of its implementation quickly. It can be consistently applied and easily recognized by maintenance programmers in the future. Overall, it contributes to the good function and lowers cost of maintenance over the life of the product.

  • "Bad" - This technique may work in the short term, but soon becomes a liability. It is immediately difficult to change or becomes more difficult over time. The initial investment may be small or large, but it quickly becomes a growing cost, eventually becoming a sunk cost and must be removed or worked around constantly. It is subjectively applied and inconsistent and may be a surprise or not easily recognizable by maintenance programmers in the future. Overall, it contributes to the ultimate increasing cost of maintaining and/or operating the product and inhibits or prevents changes to the product. By inhibiting or preventing change, it becomes not just a direct cost, but an opportunity cost and a significant liability.

Starter

As an example of what I think a good contribution would look like, I'd like to propose a "Good" principle:

Separation of Concerns

[Short description]

Example

[Code or some other type of example]

Goals

[Explanation of what problems this principle prevents]

Applicability

[Why, where, and when would I use this principle?]

Exceptions

[When wouldn't I use this principle, or where might it actually be harmful?]

Objections

[Note any dissenting opinions or objections from the community here]

+2  A: 

Separation of Concerns

Prefer Aggregation to Mixin-style Inheritance

While functionality can be gained by inheriting from a utility class, in many cases it can all be gained using a member of said class.

Example (Boost.Noncopyable):

Boost.Noncopyable is a C++ class that lacks a copy constructor or assignment operator. It can be used as a base class to prevent the subclass from being copied or assigned (this is the common behavior). It can also be used as a direct member

Convert this:

class Foo : private boost::noncopyable { ... };

To this:

class Foo {
    ...
private:
    boost::noncopyable noncopyable_;
};

Example (Lockable object):

Java introduced the synchronized keyword as an idiom to allow any object to be used in a threadsafe manner. This can be mirrored in other languages to provide mutexes to arbitrary objects. A common example is data structures:

class ThreadsafeVector<T> : public Vector<T>, public Mutex { ... };

Instead, the two classes could be aggregated together.

struct ThreadsafeVector<T> {
    Vector<T> vector;
    Mutex mutex;
}

Goals

Inheritance is frequently abused as a code-reuse mechanism. If inheritance is used for anything besides an Is-A relationship, overall code clarity is reduced.

With deeper chains, mixin base classes greatly increase the likelihood of a "Diamond of Death" scenario, wherein a subclass ends up inheriting multiple copies of a mixin class.

Applicability

Any language that supports multiple inheritance.

Exceptions

Any case where the mixin class provides or requires overloading members. In this case, inheritance usually implies an Is-Implemented-In-Terms-Of relationship, and an aggregate will not be sufficient.

Objections

The result of this transformation may lead to public members (e.g. MyThreadSafeDataStructure may have a publicly-accessible Mutex as a component).

Tom
I think what you are explaining here is the principle: "Favour composition over inheritance". Separation of concerns is more related to the OO principe "lousely coupled design" or am I missing something?
Hace
+1  A: 

I think the short answer is that "good" OO designs are robust under change, with the least code breakage for any requirements change. If you consider all the usual rules, they all tend to that same conclusion.

The difficulty is that you can't evaluate the "goodness" of the design without context; it is, I believe, a theorem that for any modularization, there exists a change in requirements that will maximize breakage, causing every class to be touched in each method.

If you want to be rigorous about it, you can develop a collection of "change cases" and order them in probability order, so that you minimize the breakage for the highest probability changes.

On most cases, though, some well-developed intuition helps a lot: device-specific or platform specific things tend to change, business rules and business process tend to change, while the implementations of, say, arithmetic, change very rarely. (Not, as you might imagine, never. Consider, for example, a business system that may or may not be able to make use of platform-supported BCD arithmetic.)

Charlie Martin
+3  A: 

There are some well understood principles that might form a good starting point:

It is also a good idea to study existing design patterns to find principles behind them, the most important one is to (generally) prefer composition over inheritance.

Adam Byrtek