54

I have read a lot of topics about code refactoring and avoiding of if else statements. Actually, I have a class where I am using a lot of if - else conditions.

More details: I am using the pull parser and on each line of my soap response, I will check if there is a tag I am interested on, if not, check another tag etc:

 if(eventType == XmlPullParser.START_TAG) {
            soapResponse= xpp.getName().toString();
            
            if (soapResponse.equals("EditorialOffice")){  
                eventType = xpp.next();
                if (xpp.getText()!=null){
                editorialOffice += xpp.getText();
                }
            }   
            else if (soapResponse.equals("EditorialBoard")){  
                eventType = xpp.next();
                if (xpp.getText()!=null){
                editorialBoard += xpp.getText();
                }
            }
            else if (soapResponse.equals("AdvisoryBoard")){  
                eventType = xpp.next();
                if (xpp.getText()!=null){
                advisoryBoard += xpp.getText();
                }
            }   
        }
        eventType = xpp.next();
     }

Now, I would like to use something else, instead of those if else conditions, but I don't know what.

Can you please give me an example?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Milos Cuculovic
  • 19,631
  • 51
  • 159
  • 265
  • You could maintain a mapping of strings to enums elsewhere in the program, pull out the enum associated with the returned string from the map (with a default `NO_MATCH` in case the string isn't in the map) and write a switch statement on the enums. It may make this code clearer, at the expense of an additional layer of indirection. You'll have to judge if that is worth it. – Chris Taylor Apr 16 '12 at 14:24
  • 3
    check this: http://stackoverflow.com/questions/519422/what-is-the-best-way-to-replace-or-substitute-if-else-if-else-trees-in-program hope this helpful – Android Stack Apr 16 '12 at 14:27
  • Side note, you should be using a StringBuilder instead of += for concatenating your Strings together, for performance reasons. – Sam Barnum Apr 19 '12 at 14:40
  • Thank you Sam Barnum, how to use a string builder? I will try to check on the internet but an example will be great. – Milos Cuculovic Apr 19 '12 at 14:44
  • `StringBuilder sb = new StringBuilder(); sb.append(editorialBoard); sb.append(xpp.getText); editorialBoard=sb.toString();` – KarlM Apr 20 '12 at 04:27

8 Answers8

61

Try to look at the strategy pattern.

  • Make an interface class for handling the responses (IMyResponse)
    • Use this IMyResponse to create AdvisoryBoardResponse, EditorialBoardResponse classes
  • Create an dictionary with the soapresponse value as key and your strategy as value
  • Then you can use the methods of the IMyResponse class by getting it from the dictionary

Little Example:

// Interface
public interface IResponseHandler {
   public void handleResponse(XmlPullParser xxp);

}

// Concrete class for EditorialOffice response
private class EditorialOfficeHandler implements IResponseHandler {
   public void handleResponse(XmlPullParser xxp) {
       // Do something to handle Editorial Office response
   }
}

// Concrete class for EditorialBoard response
private class EditorialBoardHandler implements IResponseHandler {
   public void handleResponse(XmlPullParser xxp) {
       // Do something to handle Editorial Board response
   }
}

On a place you need to create the handlers:

Map<String, IResponseHandler> strategyHandlers = new HashMap<String,IResponseHandler>();
strategyHandlers.put("EditorialOffice", new EditorialOfficeHandler());
strategyHandlers.put("EditorialBoard", new EditorialBoardHandler());

Where you received the response:

IResponseHandler responseHandler = strategyHandlers.get(soapResponse);
responseHandler.handleResponse(xxp);
hwcverwe
  • 5,287
  • 7
  • 35
  • 63
  • Thank you @hwcverwe, this seems to me as a great idea. Could you please give me a more precise exemple on how to do this? Thanks a lot. – Milos Cuculovic Apr 16 '12 at 14:27
  • 4
    Perhaps this is appropriate for more complex multi-conditional decisions. But doesn't this feel way too heavy for this particular case? Heck, I would even prefer "if, then. else if" over this solution, in this particular case (although other simpler solutions exist). – Kevin Welker Apr 16 '12 at 14:47
  • @KevinWelker this is a little more complex, but if you think about maintainability it is way easier in the future. it is very easy to extend the strategy with other response handlers. Also it is more readable then the if-elseif-else statements – hwcverwe Apr 16 '12 at 14:59
  • Great @hwcverwe, I will try this. I have an other class which has unfear 15 if else, so your solution is great for this. If I need more precisions about this, i will come back and post some questions. Thanks again... – Milos Cuculovic Apr 16 '12 at 15:03
  • 1
    I don't contend Strategy is too complex. It seems in this case the desired result was to append to an ongoing String, chosen via a selector. There are times when Strategy is the clear choice, i.e., when the action changes based on the selector. If the author's situation with 15 if-else conditions is similar, I would hate to implement 15 classes for identical actions and all that differed was the target being operated on. If there is potential for the action to differ based on the selector, then Strategy helps to accommodate for anticipated changes, and this becomes an excellent choice. – Kevin Welker Apr 16 '12 at 18:13
  • 2
    +1: I've done very much as you suggest for a program that processed an application of XML. – Raedwald Apr 17 '12 at 12:44
  • 1
    This seems very convenient. Quick question: I assume that this would perform better than the "lots of `if` statements, as we would be fetching the key directly using `O(1).`? Also do you have any blogs/articles which shows such performance benchmarks? – HurpaDerpa Dec 31 '19 at 09:26
34

In this particular case, since the code is essentially identical for all 3 case except for the String being appended to, I would have a map entry for each of the Strings being built:

Map<String,String> map = new HashMap<String,String>();
map.put("EditorialOffice","");
map.put("EditorialBoard","");
map.put("AdvisoryBoard","");
// could make constants for above Strings, or even an enum

and then change your code to the following

if(eventType == XmlPullParser.START_TAG) {
    soapResponse= xpp.getName().toString();
    String current = map.get(soapResponse);
    if (current != null && xpp.getText()!=null) {
        map.put( soapResponse, current += xpp.getText());
    }
    eventType = xpp.next();
}

No "if... then... else". Not even the added complexity of multiple classes for strategy patterns, etc. Maps are your friend. Strategy is great in some situations, but this one is simple enough to be solved without.

Kevin Welker
  • 7,719
  • 1
  • 40
  • 56
10

In Java 7 you can SWITCH on Strings. You could use that if you could use that ;-)

Peter Perháč
  • 20,434
  • 21
  • 120
  • 152
6

Besides zzzzzzz(etc.)'s comment... keep in mind that you are using XmlPullParser which makes you write ugly code like the one you have. You could register some callbacks that would split your code and make it 'better', but if possible, just use SimpleXML library or similar.

Also, you can refactor your code to make it more readable and less verbose. For instance, why do you call xpp.next() inside each if statement? Why not just calling it outside just once:

if(eventType == XmlPullParser.START_TAG) {
    soapResponse= xpp.getName().toString();
    if (soapResponse.equals("EditorialOffice") && xpp.getText()!=null){  
        editorialOffice += xpp.getText();
    }   
    else if (soapResponse.equals("EditorialBoard") && xpp.getText()!=null){  
        editorialBoard += xpp.getText();
    }
    else if (soapResponse.equals("AdvisoryBoard") && xpp.getText()!=null){  
        advisoryBoard += xpp.getText();
    }   
}
eventType = xpp.next();
Cristian
  • 198,401
  • 62
  • 356
  • 264
  • Thank you @Cristian, In fact, I am getting the xml response form a server, I don't know if i can use something else, which is so cuick as the XML pull parser. I am calling the xpp.next() on each statement, while when the start tag I am looking for is found, then on the next line I will find the variable I need, and put it in my local variable. – Milos Cuculovic Apr 16 '12 at 14:24
  • Also [`XmlPullParser.getName()`](http://www.xmlpull.org/v1/doc/api/org/xmlpull/v1/XmlPullParser.html#getName()) returns `String`, so there is no need to call `.toString()` on it. – Alan Escreet Apr 16 '12 at 14:41
  • @Ana just take a look at the SimpleXML library; it will make your life easier and happier. – Cristian Apr 16 '12 at 15:12
  • @Cristian, thanks again, do you have an example of howw to use this please? – Milos Cuculovic Apr 16 '12 at 15:18
  • Documentation is pretty complete. Once you have setup everything it will be as easy as doing things like: `SomeObject oneObject = Xml.parse(SomeObject.class, xmlPayload);` – Cristian Apr 16 '12 at 15:23
  • You could also pull up the repeated `&& xpp.getText()` to an enclosing if statement over all the cases – Kevin Welker Nov 12 '18 at 17:28
6

You could create a ResponseHandler interface with three implementations, one for each branch of your if/else construct.

Then have either a map mapping the different soapResponses to a handler, or a list with all the handler if it can handle that soapResponse.

You also should be able to move some of the boilerplate code to a common possibly abstract implementation of the response handler class.

As so often there a many variations of this. By utilizing the code duplication one actually needs only one implementation:

class ResponseHandler{
    String stringToBuild = "" // or what ever you need
    private final String matchString

    ResponseHandler(String aMatchString){
        matchString = aMatchString
    }
    void handle(XppsType xpp){
        if (xpp.getName().toString().equals(matchString){
            eventType = xpp.next();
            if (xpp.getText()!=null){
                 editorialOffice += xpp.getText();
            }
        }
    }
}

Your code becomes

List<ResponseHandler> handlers = Arrays.asList(
    new ResponseHandler("EditorialOffice"),
    new ResponseHandler("EditorialBoard"),
    new ResponseHandler("AdvisoryBoard"));
if(eventType == XmlPullParser.START_TAG) {
    for(ResponseHandler h : handlers)
        h.handle(xpp);
}
Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
5

vast question that is this one and there is no real answer. (and I don't use soap very often)

here a just some ideas based on your code:

first you can groupe duplicate code

if (soapResponse.equals("EditorialOffice")
||soapResponse.equals("EditorialBoard")
||soapResponse.equals("AdvisoryBoard")){ 

Another good thing you can do is play around with switch staments like:

switch(soapResponse){
case "EditorialOffice":
case "EditorialBoard":
case "AdvisoryBoard":
eventType = xpp.next();
                if (xpp.getText()!=null){
                advisoryBoard += xpp.getText();
                }
break;

Also you should consider breaking down you test into small functions:

public bool interestingTag(string s){
return (soapResponse.equals("EditorialOffice")
    ||soapResponse.equals("EditorialBoard")
    ||soapResponse.equals("AdvisoryBoard"));
}

    public processData(xpp){
    eventType = xpp.next();
                    if (xpp.getText()!=null){
                    editorialBoard += xpp.getText();
                    }
    ....}

So that you can just process all your answers in a while loop and you super long if else becomes a 5~10 line function

But as I said there are so many good ways of doing the same thing

Jason Rogers
  • 19,194
  • 27
  • 79
  • 112
5

You haven't mentioned if you can or do use Java 7. As of that java version you can use Strings in switch statements.

Other than that, encapsulating the logic for each case is a good idea, for example:

Map<String, Department> strategyMap = new HashMap<String, Department>();
strategyMap.put("EditorialOffice", new EditorialOfficeDepartment());
strategyMap.put("EditorialBoard", new EditorialBoardDepartment());
strategyMap.put("AdvisoryBoard", new AdvisoryBoardDepartment());

Then you can simply select the correct strategy from the Map and use it:

String soapResponse = xpp.getName();
Department department = strategyMap.get(soapResponse);
department.addText(xpp.getText());

Department is of course in interface...

Alan Escreet
  • 3,499
  • 2
  • 22
  • 19
  • After all the interruptions at work, I finally submitted the answer, but @hwcverwe had in the mean time already submitted a practically identical answer (see above). – Alan Escreet Apr 16 '12 at 15:29
4

You can define an enum like the following:

public enum SoapResponseType {
    EditorialOffice(1, "description here") {
        public void handle(XmlPullParser xpp) {
            //do something you want here
            return null;
        }
    },
    EditorialBoard(2, "description here") {
        public void handle(XmlPullParser xpp) {
            //do something you want here
            return null;
        }
    },
    AdvisoryBoard(3, "description here") {
        public void handle(XmlPullParser xpp) {
            //do something you want here
            return null;
        }
    };

    public static SoapResponseType nameOf(String name) {
        for (SoapResponseType type : values()) {
            if (type.getName().equalsIgnoreCase(name)) {
                return type;
            }
        }
        return null;
    }

    public void handle(XmlPullParser xpp) {
        return null;
    }
}

Use above enum like this:

SoapResponseType type = SoapResponseType.nameOf("input string");
if (type != null) {
    type.handle(xpp);
}

It's clean code, isn't it!

lapinkoira
  • 8,320
  • 9
  • 51
  • 94