views:

907

answers:

21

I was thinking about object oriented design today, and I was wondering if you should avoid if statements. My thought is that in any case where you require an if statement you can simply create two objects that implement the same method. The two method implementations would simply be the two possible branches of the original if statement.

I realize that this seems extreme, but it seems as though you could try and argue it to some extent. Any thoughts on this?

{EDIT} Wow that didn't take long. I suppose this is way too extreme. Is it possible to say though, that under OOP you should expect way less if statements?

{Second Edit} What about this: An object that determines its method implementation based on its attributes. That is to say you can implement someMethod() in two ways and specify some restrictions. At any point an object will route to the correct method implementation based on its properties. So in the case of if(x > 5) just have two methods that rely on the x attribute

+10  A: 

Explain how to implement the following without an if statement or ternary logic:

if( x < 5 )
{
   x = 0
}
else
{
   print x;
}
Stefan Kendall
(x<5)?(x=0):(print x);This makes a correct perl program, for example. And after programming perl for a while, I started hating C for neglection of such constructs.
Pavel Shved
In some languages you could do it through method overloading. BAM!
George Mauer
Ternary logic is not allowed.
Stefan Kendall
@George: Show me how.
Stefan Kendall
Something like this is legal C#: http://pastebin.com/fdbad189
George Mauer
#iftrue I believe Scala allows you to put conditions on method parameters. So you create two methods (I don't know the exact syntax) one for x< 5, one for x >= 5
George Mauer
+1, I'd like to see a non-trivial example. @Pavel you just changed the syntax to use the ternary operator, and x<5?x=0:printf("%d",x); is valid in C
Graphics Noob
Obviously ternary logic is cheating but Yes you can do this both with method overloading and with a functional reduce
George Mauer
I answered this in a separate answer a bit further down the page.
Thom Smith
If Scala allows method conditionals, then that's probably the strongest argument.
Stefan Kendall
we may also cheat like this (pseudocode): int (* * branch)(); branch[1]={x=0;}; branch[0] = {print x;}; (branch[x<5])();
Pavel Shved
@iftrue, the functional map-reduce-ish approach that I demonstrated is also a way to do it. Don't hate, its used by functional guys world-wide.
George Mauer
@mauer about the overloading : Yeah, and you have to read 2 classes to understand a code a hight school graduate could write. How efficient.
e-satis
It's always possible to come up with contrived examples that are easy in one language but hard in another. Explain how to implement "(~R∊R∘.×R)/R←1↓⍳R" (an actual useful algorithm) in C# -- is this evidence that APL is better than C# at real programs? When's the last time you really had such logic ("zero or print" -- really?) in a program?
Ken
@e-satis the code that iftrue proposed did 2 unrelated things depending on a condition. Yes, unrelated concepts go in separate classes. Easier to test, easier to grasp, easier to substitute alternatives.
George Mauer
rmn
@rmn: Not valid in many languages, and it's not in the spirit of the question. I appreciate the solution, but there were more valid alternatives presented.
Stefan Kendall
+3  A: 

How do you decide which object's method to use without an if statement?

Ilya Biryukov
Viurtual functions, you know. They act like a _switch_ operator of sorts, and each _if_ is a special case of the switch operator.
Pavel Shved
@Pavel: How do you decide which object to instantiate? Right, an `if` statement.
Thorarin
I suspect that what Ori Cohen is suggesting is: Polymorphism and factories are two potential ways that would get rid of the if-statement. No, they wouldn't replace all if and case statements, but they could take the place of some.
ewall
@Thorarin: I can use map<bool, CallableOOPSomething*>, instantiate both objects and after evaluation of the condition, I can call clone() method of the proper object and get the brand new instance of the thingy we need. This instance will have a call() method what lets virtual functions do the rest of the job.
Pavel Shved
A: 

It is quite extreme. Doing what you are suggesting would cause a lot of needless code duplication, unless the entire function was completely different, based on a single surrounding if; and if so, that if should probably have been on the other side of the method invocation.

If-statements certainly have their place in object-orient design.

Williham Totland
A: 

Surely some form of comparison needs to be made regardless of what you do? In the end ... sure you can avoid if statements but you'd be producing code that is IDENTICAL to the code using an if statement.

Someone correct me if im wrong but I can't think of a time where you could get any win form doing this.

Goz
+1  A: 

That's an interesting idea. I think that you could theoretically do this, but it would be an enormous pain in a language not specifically designed to support it. I certainly don't see any reason to.

Thom Smith
+2  A: 

Creating a whole new class for an else, while technically doable, would likely result in code that is hard to read, maintain, or even prove correct.

fbrereto
This is not necessarily true. if(condtion) {do something} else {do something completely different} -you're telling me that those concerns shouldn't be...you know...separated?
George Mauer
A: 

I think what he is saying or what he means to say is that he thinks it is best to avoid over-abuse of "tagging" and adding custom functionality to a class by several if statements when it better makes sense to subclass or rethink the object hierarchy.

GreenieMeanie
+4  A: 

In some ways this can be a good idea. Swiching on a type field inside an object is usually a bad idea when you can use virtual functtions instead. But the virtual function mechanism is in no way intended to replace the if() test in general.

anon
+33  A: 

Oh my god.

First they avoided goto.

Then they avoided switch.

Then they started eliding pointers.

Now they avoid ifs!

Would be nice if they start avoiding writing programs at all and, at last, admit that OOP is art of sorts, which it should be painted, set in a frame and be exhibited in galleries instead of being compiled, run and work.

Pavel Shved
My words exactly.
ldigas
He's right though,its a good rule of thumb.
George Mauer
I would change "exhibited in galleries" to "garbage collected." Otherwise, perfect.
smcameron
Nope, that would still be doing. Doing is wrong, because as long as you do, you break DRY in some sort of way. Stop doing pale, come with us and enjoy real nihilism.
e-satis
Who is "they"? I don't know of anybody who's advocated the removal of `switch`, `if`, or pointers. And most people who advocate against `goto` don't understand why Dijkstra's essay was written, the points it makes, or the solutions it describes.
John Millikin
A: 

I think applying that argument to the idea of every if statement is pretty extreme, but some languages give you the ability to apply that idea in certain scenarios.

Here's a sample Python implementation I wrote in the past for a fixed-sized deque (double-ended queue). Instead of creating a "remove" method and having if statements inside it to see if the list is full or not, you just create two methods and reassign them to the "remove" function as needed.

The following example only lists the "remove" method, but obviously there are "append" methods and the like also.

class StaticDeque(collections.deque):

    def __init__(self, maxSize):

        collections.deque.__init__(self)
        self._maxSize = int(maxSize)
        self._setNotFull()

    def _setFull(self):

        self._full = True
        self.remove = self._full_remove

    def _setNotFull(self):

        self._full = False
        self.remove = self._not_full_remove

    def _not_full_remove(self,value):

        collections.deque.remove(self,value)

    def _full_remove(self,value):

        collections.deque.remove(self,value)
        if len(self) != self._maxSize and self._full:
            self._setNotFull()

In most cases it's not that useful of an idea, but sometimes it can be helpful.

Brent Nash
+2  A: 

It depends on what the original statement is comparing. My rule of thumb is that if it's a switch or if testing equality against an enumeration, then that's a good candidate for a separate method. However, switch and if statements are used for many, many other kinds of tests -- there's no good way to replace the relational operators (<, >, <=, >=) with specialized methods, and some kinds of enumerated tests work much better with standard statements.

So you should only replace ifs if they look like this:

if (obj.Name == "foo" || obj.Name == "bar") { obj.DoSomething(); }
else if (obj.Name == "baz") { obj.DoSomethingElse(); }
else { obj.DoDefault(); }
John Millikin
+4  A: 

Yes its true that often complex conditionals can be simplified with polymorphishm. But its not useful all the time. Go read Fowler's Refactoring book to get an idea of when.

http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

Frank Schwieterman
Fowler's Law: When you have a penetrating insight into object-oriented programming, you realise that Martin Fowler published the idea years ago...From http://nomorehacks.wordpress.com/2009/08/25/fowlers-law/
Dawie Strauss
Oooh, great link +1
George Mauer
A: 

Not to answer a question with a question, but, How much code/data space is used up for multiple instances of a classes and methods vs a simple if statement? Which would execute faster?

KFro
+2  A: 

Completely eliminating if statements is not realistic and I don't think that is what Ori is suggesting. But they can often be replaced using polymorphism. (And so can many switch statements).

Francesco Cirillo started the Anti-If Campaign to raise awareness of this issue. He says:

Knowing how to use objects lets developers eliminate IFs based on type, those that most often compromise software's flexibility and ability to evolve.

You can join the campaign at http://www.antiifcampaign.com/supporters.html

Dawie Strauss
A: 

I will say the answer is vaguely yes-ish. Especially when the language allows some heavy duty functional programming (ie C#, F#, OCaml).

A component that contains 2 if statements strongly couples two business rules so break it up.

Take that as a very general rule of thumb but I would agree. If you have a bunch of if statements, maybe you should think about another approach.

George Mauer
A: 

If-statements are pretty core to programming so, in short, you cannot sensibly avoid them.

However, a key goal in OOP--in fact, one of the "pillars"--is encapsulation. The old "encapsulate what varies" rule helps you remove those troublesome if and case statements where you are trying to account for every option in your object. A better solution to dealing with branches, special cases, etc. is to use something like the "Factory" design pattern (Abstract Factory or Factory Method--depending on needs, of course).

For example, rather than having your main code loop check which OS your using with if statements then branch to create GUI windows with different options for each OS, your main code would create an object from the factory, which use the OS to determine which OS-specific concrete object to make. In doing this you are taking the variations (and the long if-then-else clauses) out of your main code loop and letting the child objects handle it--so the next time you need to make a change such as supporting a new OS, you merely add a new class from the factory interface.

ewall
+2  A: 

In answer to ifTrue's question:

Well, if you have open classes and a sufficiently strong dependent type system, it's easy, if a bit silly. Informally and in no particular language:

class Nat {
    def cond = {
     print this;
     return this;
    }
}

class NatLessThan<5:Nat> { // subclass of Nat
    override cond = {
     return 0;
    }
}

x = x.cond();

(continued...)

Or, with no open classes but assuming multiple dispatch and anonymous classes:

class MyCondFunctor {
 function branch(Nat n) {
  print n;
  return n;
 }

 function branch(n:NatLessThan<5:Nat>) {
  return 0;
 }
}

x = new MyCondFunctor.branch(x);

Or, as before but with anonymous classes:

x = new {
    function branch(Nat n) {
  print n;
  return n;
 }

 function branch(n:NatLessThan<5:Nat>) {
  return 0;
 }
}.branch(x);

You'd have a much easier time if you refactored that logic, of course. Remember that there exist fully Turing-complete type systems.

Thom Smith
A: 

I've been following the anti-if talk lately and it does sound like extreme / hyperbolic rhetoric to me. However I think there is truth in this statement: often the logic of an if statement can be more appropriately implemented via polymorphism. I think it is good to keep that in mind every time you right an if statement. That being said, I think the if statement is still a core logic structure, and it should not be feared or avoided as a tenet.

Daniel Auger
+1  A: 

One of my teacher used to say that. I tend to think that people being so dogmatic about that kind of thing usually don't program for a living.

e-satis
A: 

Have a look at the Anti-If Campaign The idea is not to replace every single if in your application with the Strategy or State Pattern. The idea is that when you have complex branching logic especially based on something like an enumeration, you should look to refactoring to the Strategy Pattern.

And that case you can remove the if all together by using a Factory. Here is a relatively straightforward example. Of course as I said in a real case, the logic in your strategies would be a bit more complex than just printing out "I'm Active".

public enum WorkflowState
{
  Ready,
  Active,
  Complete
}

public interface IWorkflowStrategy
{
  void Execute();
}

public class ActiveWorkflowStrategy:IWorkflowStrategy
{
  public void Execute()
  {
    Console.WriteLine("The Workflow is Active");
  }
}

public class ReadyWorkflowStrategy:IWorkflowStrategy
{
  public void Execute()
  {
    Console.WriteLine("The Workflow is Ready");
  }
}

public class CompleteWorkflowStrategy:IWorkflowStrategy
{
  public void Execute()
  {
    Console.WriteLine("The Workflow is Complete");
  }
}

public class WorkflowStrategyFactory
{
  private static Dictionary<WorkflowState, IWorkflowStrategy> _Strategies= 
    new Dictionary<WorkflowState, IWorkflowStrategy>();
  public WorkflowStrategyFactory()
  {
    _Strategies[WorkflowState.Ready]=new ReadyWorkflowStrategy();
    _Strategies[WorkflowState.Active]= new ActiveWorkflowStrategy();
    _Strategies[WorkflowState.Complete = new CompleteWorkflowStrategy();
  }
  public IWorkflowStrategy GetStrategy(WorkflowState state)
  {
    return _Strategies[state];
  }
}

public class Workflow
{
    public Workflow(WorkflowState state)
    {
        CurrentState = state;
    }
    public WorkflowState CurrentState { get; set; }
}

public class WorkflowEngine
{
    static void Main(string[] args)
    {
        var factory = new WorkflowStrategyFactory();
        var workflows =
            new List<Workflow>
                {
                    new Workflow(WorkflowState.Active),
                    new Workflow(WorkflowState.Complete),
                    new Workflow(WorkflowState.Ready)
                };
        foreach (var workflow in workflows)
        {
            factory.GetStrategy(workflow.CurrentState).
                Execute();
        }
    }
}
Mike Brown
A: 

My two bits here of what I understand of the Object Oriented approach -

First, what objects in a program should be intuitive. That is, I should not try to create a 'Arithmatic' class to provide mathematical functions. This is an abuse of OOD.

Second and this is a very strong opinion of mine. It should not be called Object Oriented design but Object and Method Oriented design! If the method names of the objects are themselves not intuitive then inherited objects might end up reimplementing the methods already available.

Object Oriented approach, according to me, is not a replacement for the Procedural approach. Rather it is mainly for two main reasons for the creators of the language -

  1. Better capability of scoping of variables.

  2. Better capability of garbage collection rather than having too many global variables.

globetrotter