views:

128

answers:

5

I have an example like this

class A {

   A() {}
   public C createC () {
      ...
   }

} 

class B {

   B() {}
   public C createC () {

   }
}

Objects for A and B are created based an an

public enum D { ii, jj };

And I see code all over the place like

D d; 

switch (d) {
    case ii: (new A()).createC(); break;
    case jj: (new B()).createC(); break;
 };

How can I avoid the switch cases all over the place? I understand the code is not so clear.

+3  A: 

You could just create a Factory class:

public class ClassFactory {

     public static C createC(D type) {
         C c = null;
         switch (type) {
              case ii: 
                  c = new A(); 
                  break;
              case jj: 
                  c = new B(); 
                  break;
         };

         return c;

     }
}

Then in your code just do:

C c = ClassFactory.createC(type);

Very similar to what the Wiki entry for Factory method pattern is doing with their PizzaFactory.

tkeE2036
Another clean first step would be to just make the createC() functions static so you aren't creating useless objects all the time. But yes, the factory method would be best imo too.
mrnye
One downside to this though is that if you add a new type D, you must be sure to update ClassFactory, though there wouldn't be any compile time errors. There should at the very least be a unit test to ensure all values of D are handled properly.
Jeff Storey
@Jeff Story: True but at least this way you only have to update it in one spot, with the current implementation this would need to change everywhere he had his old switch statement.
tkeE2036
`C ret = null;`...`return c;` Is that supposed to be `C c = null;`...`return c;`?
Tony Ennis
@Tony Ennis: Good catch, fixed it.
tkeE2036
+1 specifically for the fact that it keeps all the creation code in one spot.
edwardTheGreat
+1  A: 

I don't see a refactoring opportunity here, as such.

I see the need for a factory. Look up the "factory" design pattern.

And I see code all over the place like... (followed by a conditional wherein a specific instance is created)_ is a huge tip-off.

Basically, what you want to do it push all the ugliness of deciding which class to instantiate into one place. First, in your example, A and B should share a superclass or better yet implement an interface in which public C createC() is defined.

The factory is nothing more than a class with a static method that makes the decision on which A or B to instantiate.

    public class SomeFactory {
        public static YourInterface make(int someNumber) {
            switch (someNumber) {
                case 1: return new A();
                case 2: return new B();
                default:
                    throw new RuntimeException("unknown type");
                }
            }
        }

So what you do is:

YourInterface yi = SomeFactory.make(1);
C c = yi.createC();

Or

C c = SomeFactory.make(1).createC();

And now your code is pretty clean. Create a new implementation of SomeInterface? No problem, add its creation to the factory and your code still works. If for your application make() is costly, no problem, just make one and use it repeatedly.

Tony Ennis
I think this isn't the correct way to do it. You are creating objects which aren't needed if you are just calling .createC() on them then discarding them. Your factory method is on the right track, just needs a little fine tuning. Also, it may have just been the way we were taught it, but afaik it is "abstract factory" or "factory method" patterns. I've never heard of a raw "factory" pattern.
mrnye
What am I discarding? They instances are being returned. So you downvote because I called it a "factory pattern" instead of a "factory method" pattern?
Tony Ennis
Another downvote. I have no idea why.
Tony Ennis
A: 

Add the createC method to the enum D.

public enum D {
  ii,
  jj;

  public C createC() {
    switch (this) {
      case ii:
        return new A().createC();
      case jj:
        return new B().createC();
    }
    return null;
  }
}

Then just call D.createC().

Erick Robertson
@Erick, you've made one step forward here by moving the ugliness into one place, but @Michael's approach of using polymorphism is better, because the language does the switch.
Dilum Ranatunga
Whatever. This is just a matter of taste.
Erick Robertson
+3  A: 

I would be inclined to add the create code to the enum.

enum D {
    ii {
        public void createC() { new A().createC(); }
    },
    jj {
        public void createC() { new B().createC(); }
    };

    public abstract void createC();
}

You can then replace the switch with

d.createC();

Having said that I would also look at making the createC method static or moving the createC code to the enum rather than leaving it in the A and B classes.

If you make the createC method in A and B static the enum would look like

enum D {
    ii {
        public void createC() { A.createC(); }
    },
    jj {
        public void createC() { B.createC(); }
    };

    public abstract void createC();
}
Michael Rutherfurd
_moving the createC code to the enum rather than leaving it in the A and B classes_ Does that not make the assumption that `a.createC()` and `b.createC()` methods are the same?
Tony Ennis
@Tony: no, as has been shown an enum can have abstract methods that each element can implement independently. I'm not sure I like the approach though. I'd need more context to agree that it's a good idea.
Mark Peters
Strictly speaking, given the original code, I should declare the method as *public abstract void createC();*. This allows you to avoid the problem switches. I'll edit my answer to reflect this.
Michael Rutherfurd
Shouldn't the createC() method have a return type of C?
Erick Robertson
@Erick: Normally yes, but the usage of the method in the sample switch indicates no return value.
Michael Rutherfurd
But the method signatures of A::createC() and B::createC() in the question indicate a return value. Something's fishy here.
Erick Robertson
@Erick: I suspect the example switch is *supposed* to return something but given that it currently doesn't a void return should be correct. Making it return a C which is then ignored should also be valid, but I was trying to stay as close to the example as I could.
Michael Rutherfurd
A: 

You give a switch statement as an example, but as the other conditionals related to the switch are not shown I think its premature to suggest a specific solution. Here's a link though if you want to consider a range of responses to "switch" statements: http://books.google.com/books?id=1MsETFPD3I0C&lpg=PP1&dq=refactoring&pg=PA82#v=onepage&q&f=false

If the other conditionals are really just selecting on the same criteria to construct one of the objects, use extract method.

Frank Schwieterman