开发者

How to initialize input fields without writing "if statements"?

I have an enum like

public enum Field {
     A, B, C, D, E ....;

     private Field(){
     }
}

I have a class Panel that takes Field array to initialize the fields:

public class Panel {
     TextBox A; 
     TextBox B;
     TextBox C;
     TextBox D;
     TextBox E;
     ...


     public Panel(Field[] fields){
          this.fields = fields;
          init();
     }

     public void initA(){}
     public void initB(){}
     public void initC(){}
     public void initD(){}
     public void initE(){}
}

My question is, how can I initialize the fields that given without writing many if statement?

I can't find any solution and I'm now initializing like this:

public void init(){
      for(int i = 0 ; i < fields.length; i++){
          if(fields[i] == Field.A){
              initA();
          } else if(fields[i] == Field.B){
              initB();
          } else if(fields[i] == Field.C){
              initC();
          } else if(fields[i] == Field.D){
              initD();
          } else if(fields[i] == Fie开发者_开发技巧ld.E){
              initE();
          }  ....
      }
}


Sounds like your design might need to be looked at. A few suggestions:

  • Add the init method to your enum. So then you can iterate around the array of your enums and call the init method on it, so the enum knows how to do its own initialization
  • create a Command object which does the initialization and create a Map of your enum as the key and the Command as the value. Cycle round the map running the Command for each enum.
  • Use reflection - cost wise I wouldn't be too concerned for this, unless your system is after incredibly low latency

For the first bullet, you could change the TextBox to hold a Field type against it e.g.

TextBox A = new TextBox(Field.A);
TextBox B = new TextBox(Field.B);

So if TextBox knows it is A,B,C,D,E then you just need to loop around your Field[] and when it finds its mathing TextBox run the init code (which can be stored against the specific enum instance). Of course you will need to register all your TextBox instances in a data structure somewhere, as you seem very set against using the very widely used reflection API.

In essence there has to be a link between the Field and the TextBox. Java cannot read your mind and know this without you telling it. Well, at least until Google unveil their telepathy API (and that would probably only be for Go...). This can be done based on naming (reflection), hardcoded logic (ifs or switches) or based on state. For the latter this means associating the Field with the TextBox, as I have demonstrated with the Constructor example above.


From a design perspective I'd choose a combination of Factory pattern, Singleton pattern (enum based) and Command pattern. I see a set of commands where each command is specific for a given value. A factory (Singleton) is a common pattern to create such specialized instances. Even though it simply moves the if/switch chain into the factory (but factories are allowed use conditional checks in order to create the instances..).

// the init command
public interface PanelInitializer {
  public init(Panel p);
}

// the factory
public enum PanelInitializerFactory {
  INSTANCE;

  public PanelInitializer create(Field field) {
    switch (field) {
      case A: return new TypeAInitializer();
      case B: return new TypeBInitializer();
      case C: return new TypeCInitializer();
      //..
    }
  }
}

I don't think that we can get rid of all conditional checks without using naming conventions and reflection/instantiation or without introducing the constraint, that all initializers share the same code.


Here's a snippet featuring adding the init method to the enum. In each Field's init method you can call one of your different initX() methods. Making the init method abstract gets the compiler to remind you to define your init method for the enum value.

enum Field
{
  A{public void init(){initA();}},
  B{public void init(){initB();}},
  C{public void init(){initC();}},

  public abstract void init();
}


You can, as @planetjones mentioned, add an init() method to your enum class. The init method should return a reference to the initialised TextBox of its (enum) type. If you need to pass data to the initialisor you can pass this so that it can retrieve any information it needs.

To get around the problem of finding the variable to assign to, you can either declare an array of TextBoxes

public void init(){
  for(int i = 0 ; i < fields.length; i++){
      F[i] = fields[i].init(this);
  }
}

or assign them after you initialised a temporary array.

public void init(){
  TextBox F[5];
  for(int i = 0 ; i < fields.length; i++){
      F[i] = fields[i].init(this);
  }
  A = F[0];
  B = F[1];
  C = F[2];
  D = F[3];
  E = F[4];
}

Of course you should declare constants instead of using magic numbers.


You could use java reflection to loop through your enum, but you really should look into some way to consolidate all your initN() methods.


Why you current implementation is bad? Only because it looks "ugly"? You can use switch instead of bunch of if:

  public void init(){
    for(int i = 0 ; i < fields.length; i++){
        switch(fields(i)){
          case A:
            initA();
            break
          case B:
          ...
        }
    }
  }

Maybe logic in initA, initB... is very similar? If you have 20 different enums, and 20 different init to run, not much space for improvement...


You could do it, for example, by moving initialization logic to the enum. Have there a method initialize that takes a TextBox as the parameter and initializes it.

BTW you would be better having the TextBox variables in an array.

EDIT Another option is, what I often is I use an enum as a sort of archetype storage. There I have a method returning an object which matches a certain enum type.

If you do not want to have initialization in enum you could move it to objects you are going to return. There for each particular object you will have a separate initialization.

I am afraid that you are trying to chase the dragon with this one. Look at it this way. Since the problem of yours is 'conditional', i.e. you have to do a different initialization depending on enum type of a Field, thus at some point you will have to use ifs or switch statement.

Believe there is no magic way around it, how the program should know what you want to do? Even using reflection you will use ifs, etc. to initialize it accordingly.


Your approach is not at all bad if the init methods associated with each TextBox is very different, and the list of Fields is small. Also, if you typically instantiate only one of these Panel instances, the other approaches can actually hurt more than help.

Having said that, consider using a java.util.EnumMap. After that, you have three choices:

  1. register the TextBoxes in some other array as well,
  2. invoke the initA, ... using reflection or
  3. invoke them using some functor construct.

The best choice depends on use case.


Start with this example:

import java.util.HashMap;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class FooPanelMain {

    public static void main(String[] args) {

        FooPanel panel = new FooPanel();
        JFrame frame = new JFrame();
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setVisible(true);
    }

}    

class FooPanel extends JPanel {

    // fields are dynamically created, so we put them into a map
    private Map<PanelField, JLabel> fields = new HashMap<PanelField, JLabel>();

    // enum to configure the fields
    private enum PanelField {
        FIRST("first text"), 
        SECOND("second text"), 
        LAST("last text");

        private String text;

        private PanelField(String text) {
            this.text = text;
        }

        public String getLabelName() {
            return text;
        }

    }
        // constructor uses the enum configuration to create the fields
    public FooPanel() {
        for (PanelField fooPanelField : PanelField.values()) {
            createLabel(fooPanelField);
        }
    }

    private void createLabel(PanelField field) {

        JLabel label = new JLabel(field.getLabelName());
        this.add(label);
        fields.put(field, label);
    }
}    

This example can be easily turned into a abstract solution by defining an interface for PanelField, that is implemented by enums. FooPanel can be used as a base class for Panels.


best u move the init into the enum, just like:

public enum Example{ 
  value1(1), 
  value2(2), 
  value3(66);

  private final int internalValue;

  Example(int value){ 
       this.internalValue = value; 
  }

  public int getInternalValue(){ 
       return this.internalValue; 
  }
}

Although this is really simple example, you can add any code to the constructor later on and have more complex decisions based on the actual object itself.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜