views:

193

answers:

8

I've heard people saying that good design involves using inheritance instead of littering your code with if block. In what situation should we use inheritance, and when condition block is just fine?

A: 

Here is something I ran into the other day

PHP is my language of choice

class Cache {

    public function __construct($resource) {

     // if statement to determine from type if it is a file, url or string. instead, change it to accept a string

    }

}

or... it could of been extended

class cacheURL extends Cache {

    public function __construct($url) {

        $string = file_get_contents($url);

        parent::__construct($string);
    }

}

Rough example, but I hope you catch my drift....

alex
A: 

In my opinion the two are not mutually exclusive. I typically use inheritance when I have 2 or more classes that have common functionality.

JD
+4  A: 

One refactoring like this is called Replace Conditional With Polymorphism.

We actually favor composition over inheritance, but ... it's really too broad of a topic to discuss here, however the book is worth a read and can get you started.

Matt Hinze
+2  A: 

Generally, it isn't good object-oriented programming practice to have code like the following:

var chioce = PAINT_SCREEN_BLUE

if (choice == PAINT_SCREEN_BLUE):
    paintScreen(blue)
else if (choice == PAINT_SCREEN_RED):
    paintScreen(red)
else if (choice == PAINT_SCREEN_GREEN):
    paintScreen(green)

The above can utilize polymorphism to call some method that will perform the action that is abstracted away in related classes with a common ancestor:

interface ScreenPainter:
    function perform()

class BlueScreenPainter implements ScreenPainter:
    function perform():
        paintScreen(blue)

...

// The conditional block can be replaced with the following call:
var choice = PAINT_SCREEN_BLUE

ScreenPainter p = Painters.getPainter(choice)
p.perform()

However, this practice definitely should not apply for all conditionals, however, when it comes to long switch blocks or if-elseif-else blocks, in many cases, use of inheritance will make your code more extensible and more robust.

coobird
+2  A: 

If blocks (or switch blocks) that are used to decide what logic to do based on the type of an argument should be replaced with polymorphism.

If new features in a program will cause you to make additional case or else blocks use polymorphism.

If statements should be used for natural ordering type comparisons ( <, >, ==, etc.) or to check if something is null.

Rob Spieldenner
A: 

Inheritance is better when some condition is checking something intrinsic to your object that you check constantly (like a state or type). In this example, it's an operator for a mathematical expression tree. An operator, like addition, has an expression before and after the plus sign (infix notation) so I call them left and right because it is represented as a tree. There are also unary operators like a hyphen that negates a value, but it only has one child expression.

Using inheritance means the type (simplified to just unary vs. binary) is factored into the inheritance:

class Operator
{
    abstract string ToString();
}
class UnaryOperator : Operator
{
    Expression Expression { get; }
    override string ToString() {...}
}
class BinaryOperator : Operator
{
    Expression LeftExpression { get; }
    Expression RightExpression { get; }
}
class AdditionOperator : BinaryOperator
{
    override string ToString()
    {
        return this.LeftExpression.ToString() + "+" + this.RightExpression.ToString();
    }
}

As opposed to:

class Operator
{
    Expression LeftExpression { get; }
    Expression RightExpression { get; }

    OperatorType Type { get; }

    string ToString()
    {
        switch(this.Type)
        {
            case OperatorType.Addition:
                return this.LeftExpression.ToString() + "+" + this.RightExpression.ToString();
            case OperatorType.Negation:
                return "-" + this.LeftExpression.ToString();
            case ...:
        }
    }
}
enum OperatorType
{
    // Unary operators
    Negation,

    // Binary operators
    Addition,
    Subtraction,
    Multiplication
}

Here your Unary operator doesn't have a Left and a Right expression, just an expression. This means you have to introduce convention that Left is used and Right is null. ToString would have to check OperatorType; so would any other method that needs to act on what operator it is.

The latter approach is most often seen in XML DOM implementations where XmlNode is pretty much everything any XML node could contain...ever. It makes for some very confusing situations:

  • How can an attribute or text node have ChildNodes?
  • How can a document have ChildNodes when it can only have one? (It need not be a collection)
  • How can a document have a ParentNode?
  • How can a document have a Value?
  • Etc.
Colin Burnett
+1  A: 

This is one of those questions that, while others can offer guidelines, the final answer for any particular situation is always, "it depends on the situation."

The best way to be able to develop the ability to answer it for any particular situation is to gain experience: i.e., experiment. Try both ways, where you can do so reasonably. (Obviously, you don't want to be flipping back and forth in situations where you have to change a thousand lines of code to do so.) Which worked better? Which felt better to you? Why?

Do this enough, and you'll soon reach the level of expertise where you no longer need the general advice, and people start asking you how you know what to do in any particular situation. (You won't be able to explain it; that's part of the nature of expertise. See The Dreyfus Model of Skill Acquisition for more details.

Curt Sampson