5

I am reviewing some code(Java) and making changes based on a business logic flow chart. The current code relies on a large amount of if statement's - something I want to try and move away from. I have been reading about Polymorphism and being trying to wrap my head around how to apply it to my situation. I can make it work for a single level of conditionals, but struggling to extend it further across multiple conditional levels. The code will be executed at run time, with this 'Logic' method being passed the variables from a previous step.

Contrived Example: We have 2 Zoo's, 'Zoo A' and 'Zoo B', and 'Home'. Each of these is a 'Place'. In each Zoo we have 4 'locations', 'North', 'South', 'East' and 'West'. 'Home' only has one location. We want to assign a 'destination' to a person on where they should go based on a few variables. These variables are: 'Place', which correlate to our places (Zoo A, Zoo B, Home). 'Direction', which correlate to our locations, (N,S,E,W). Flow Chart:

                |----- | 'HOME'
                |Place?| ----- >  *destination = 'home'*
                |----- |
     Zoo A          |                               Zoo B
    |---------------|----------------------------------------|
|----------|                                        |----------| 
|Direction?|                                        |Direction?| 
|----------|                                        |----------|
    |    North                                          |    North
    ----------- *destination = 'Zoo A North'            ----------- *destination = 'Zoo B North'
    |    East                                           |    East
    ----------- *destination = 'Zoo A East'             ----------- *destination = 'Zoo B East'
    |    South                                          |    South
    ----------- *destination = 'Zoo A South'            ----------- *destination = 'Zoo B South'
    |    West                                           |    West
    ----------- *destination = 'Zoo A West'             ----------- *destination = 'Zoo B West'

So If Person X has a Place of Zoo A and a Direction of South they should have a Destination of 'Zoo A South'

I have code that is currently pretty ugly using If statements:

if(Place = 'HOME')
    destination = 'HOME'
if(Place = 'Zoo A')
    if(Direction = North)
        destination = 'Zoo A North')
    if(Direct = East)
        destination = 'Zoo A East')
    ...
if(Place = 'Zoo B')
    if(Direction = North)
        destination = 'Zoo B North')
    if(Direct = East)
        destination = 'Zoo B East')
    ...

I could turn this in to nested switches with the variables as ENUMs. But I am trying to avoid the if - else / switch reliance as I have a bad habit of falling into it. I experimented with using a Factory Design to generate Place classes, then used Polymorphism on each location and destination but it started to get overly complicated in my head. Is it even worth moving away from if/switches? Am I just trying to over engineer it?

Any suggestions on how to tackle logic flows like this? Thanks

2
  • I am not entirely clear on what you are trying to do in your example. Are "Place" and "Direction" Strings or some other kind of object? Either way, a simplistic example might be to create goNorth(), etc. methods in your Zoo class. I'm not entirely sure that this will solve your problem, though, because I'm not entirely clear on what you are trying to do. Commented Jan 10, 2013 at 1:04
  • @Code-Guru - Let's say they are all strings for simplicity. What I am trying to do is Start at the top of the flow and based on the variables (Direction and Place) end up with a final 'destination'. But in the most manageable and easy to maintain way Commented Jan 10, 2013 at 3:51

8 Answers 8

3

This could be modelled like this:

  1. Use a root class Place, with method calculateDestination(Person). Place can consist of other places inside of it.
  2. Create Place subclasses for Zoo and ZooQuadrant (naturally, as these are actual places).
  3. The Person object has values for currentPlace and currentDirection

Now you'd instantiate objects of these classes to represent your situation:

zooA = new Zoo("ZooA");
zooA.addChild(new ZooQuadrant(Direction.SOUTH));
... so on for every quadrant ...
... same for zooB ...
home = new Place("Home");
world = new Place("World");
world.addChild(home);
world.addChild(zooA);
world.addChild(zooB);

When you want to get the destination, you would call world.calculateDestination(myPerson)

calculateDestination(Person) is the polymorphic method. Each level in the inheritance hierarchy will override it according to the specific semantics of that class.

  1. Place will have a generic implementation that will test if the Person instance is currently at that node (by testing against the Person's value for currentPlace) and if not, it will call calculateDestination on each of its children and return that.
  2. Zoos will need to check if currentPlace == this, and if so, call calculateDestination on each of its quadrants and combine any positive result with its own to return this.name + quadrantResult.
  3. Each ZooQuadrant just needs to check if the currentDirection is equivalent to its own direction, and return a value accordingly.

Note: this is just to illustrate how polymorphism may work, there may be better implementations. Also, here we're using both polymorphism and recursion, the two are independent.


EDIT:

As for whether the added complexity is warranted, that depends! Here we're working with a simple example with an object graph that is quite small. As soon as you have tens of zoos, have to add more quadrants in those zoos, or extra levels of decisions need to be made (if for example each quadrant has subquadrants), the nested if-else-if method (procedural) gets really hairy really quickly, whereas the object oriented approach remains maintainable and understandable.

Like everything, if you foresee that decisions are going to get that complex, go with the OO approach. Otherwise, keeping it simple trumps beauty every time: use the right tools for the right problems.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the suggestion, I have built a working model of this(its too long to post :( ). Looking at it I think I'm just trying to over complicate what I am doing. It does work well though! Hmmm..
0

One approach is you can create a parent asbtract class / interface 'Place' with a method getNorthDestination(), getEastDestination() and so on.

Then you create a subclass / implementation of 'Place' called 'ZooA' and 'ZooB', and override / implement the getXXXDestination() method to return corresponding location

2 Comments

would be more work to maintain, for say adding direction SouthEast, Up, Down you would wind up having to add methods in a lot of places.
That's what I want to avoid, is future changes that might add a new Zoo, or a new Direction that don't break the logic. @gerrytan - That's a good idea, it would limit the if block to if(Place place = Zoo A) return Place = new Zoo A ... then just call a switch on the Direction, case North: place.goNorth();. Is that right?
0

From this I see at least three classes you want being Place, Direction and Destination.

Place would have a name property and a getName() method the name being set to Zoo A, Zoo B, Home.

You would make Home and Zoo subclasses of Place if they have different behaviour. In this instance you could do it because Home does not have a direction but Zoo does.

Direction could be an enum (which is just a special type of class) containing North, East, West, South.

Destination would have two properties being Place and Direction. It would also have a method getDestination()

public String getDestination(){
    if (this.direction == null){
        result = this.place.getName();
    } else {
        result = this.place.getName() + " " + this.direction.getName();
    }
    return result;
}

Comments

0

Go with gerrytan's answer. For every piece of information, you should ask yourself, "do I really need a class for this?". Many times, the answer is no. Simple string/numerical variables suffice. Now, you want to couple these variables with getter/setter methods, as this is what java puts emphasis on, as opposed to referncing public variables directly (such as C++). Referencing simple methods is a lot easier than testing for instance.

Comments

0

you can make a new method with Zoo A and Zoo B in it...... you can call it like directionForZoo(), and if try using while loops

Comments

0

If you don't want to over engineer it, the following workaround would be a simple solution to get rid of if/else. But it is not an elegant approach.

You can have a map in which the keys are (Place+Direction) and values are the corresponding Destinations. This is fine only if the Place and Direction values are more like static at the moment in your program and it is unlikely to change much.

E.g.: storing your places and corresponding destination in a map

Map<String, String> destMap = new HashMap<String, String>();
destMap.put("HOME","HOME");
destMap.put("Zoo A+North","Zoo A North");
destMap.put("Zoo A+East","Zoo A East");
destMap.put("Zoo B+North","Zoo B North");

Retrieving the destination according to the Place and Direction:

destMap.get(Place + "+" + Direction);

Comments

0

One possible solution to your example is to create a (possibly abstract) Location class. This can contain a data structure for your destinations. One possibility for this data structure is a perhaps a Map<Direction, Location>) where Direction is an enum which can be used as the key which maps to a Location object which is the destination for going that direction. You can either subclass Location to create Home, Zoo, etc. classes or you can provide a name field which differentiates different Locations. Or you can do some combination of both of these.

Note that this is a half-baked design which may or may not serve your needs. Good OO design takes experience and detailed knowledge of the exact requirements for the problem you are trying to solve. I have some of the former but very little understanding of the later.

Comments

0

I tried a variations on a few suggested answers.

I ended up using a nested switch case block. Not the most ideal and what I wanted to avoid but for my purposes its more maintainable ( its not going to change or be extended).

I'm going to mark @Ezequiel Muns method as correct as my version of this worked very well - its just not what is needed for the problem.

Thanks for all the help.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.