views:

168

answers:

8

I'm attempting to write a java program that initializes certain layouts based on what the user selects. What I want to do is try to avoid writing a bunch of if-statements so the code can be scalable for future use if more layouts need to be added. I heard the best way to implement this is using polymorphism but my understanding of polymorphism is still a little fuzzy.

Say I want to implement this case:

if (user choose layoutA) { initialize layoutA }
if (user choose layoutB) { initialize layoutB }
if (user choose layoutC) {initialize layoutC }

I was thinking of creating an interface for classes to implement. What confuses me is how it works in main(), won't I still need a conditional if or switch statement to figure out which class to instantiate?

interface LayoutHandler {
    public void initializeLayout();
}

class layoutA implements LayoutHandler { 
    public void initialize Layout {initialize layout A}
}
class layoutB implements LayoutHandler { 
    public void initialize Layout {initialize layout B}
}
class layoutC implements LayoutHandler { 
    public void initialize Layout {initialize layout C}
}

Then somewhere out in main:

public static void main() {
   getlayoutselectionfromuser()
   if (user choose layoutA) { LayoutHandler layout = new layoutA(); }
   if (user choose layoutB) { LayoutHandler layout = new layoutB(); }
   if (user choose layoutC) { LayoutHandler layout = new layoutC(); }

}

Wouldn't I still require a switch or if-statement in the main program to figure out which layout the user has chosen during runtime?

Thanks!

+2  A: 

In short, yes. You'll need ifs or some mapping mechanic.

You will need some way to convert the user's input into the class you want. The ifs you have in your code will work just fine and be clear and readable. You could use a switch, too.

It is possible to avoid this, but you'll end up obfuscating your code and in the end, there will probably still be something like an if. You must define the mapping from user input to object; that cannot be circumvented. The clearest most maintainable way to do that is what you already have (though I'd put else ifs in there. :) )

The idea you have of a different class for each layout isn't bad. This is much like the concept of a traits class. Look into that, too. It will be helpful for your design.

JoshD
In this case, I was wondering what are the benefits of creating an interface as oppose to using a bunch of if-statements like I initially had? Is it primarily for readability? In the end, I will still be using if-statements as a controller.
Having a common interface to all your layouts means you don't have to rewrite your code for each new interface. The main code will still use the new layout in the same way it used the old layouts (via the same interface). This is great for maintenance. As for the ifs, hvgotcodes suggestion of a map would end up being better if you know you'll add a bunch of new layouts in the future. It's not a huge difference (add an if vs. add a map element), but it's generally cleaner.
JoshD
You'll start see the benefits of Polymorphism the first time you have to replicate that structure of if-else statements in a second location for a different function.
James Schek
+1  A: 

No. Use a Map. When you get the choice just lookup the handler in the map and off you go.

psuedocode

 Map handlers = new Map()
 handlers.put(layoutA, HandlerForA)
 // more handlers, possibly use dependency injection

 Layout chosen = user choose layout // get a ref to the selected layout
 Handler handler = handlers.get(chosen)
 if (handler == null) { // No HandlerException }
 handler.go()

only one if statement.

hvgotcodes
Makes sense, but we don't really know his use cases yet. A map may or may not be appropriate, despite the cleanliness of the solution.
San Jacinto
+1  A: 

Something, somewhere, needs to specify the implementation. That might be a chain of if statements in source code -- but it could also be something else; e.g., a classname specified in an external data source, instantiated via reflection.

For some interfaces, there may be many more calls to the interface than instantiations. Polymorphism can reduce coupling to specific implementations.

This reduced coupling can help make code more maintainable. You can add a new implementation without modifying potentially many callers.

Andy Thomas-Cramer
+6  A: 

Generally, it will be difficult to avoid some kind of conditional statement at some point to create an instance of the appropriate class.

The benefit of polymorphism comes when you have multiple if-else statements in multiple places. The polymorphism encapsulates the conditional logic for you. See this question for some other discussions on this topic.

This sort of scattered logic:

void initLayout() {
   if (user choose layoutA) { initialize layoutA }
   if (user choose layoutB) { initialize layoutB }
   if (user choose layoutC) {initialize layoutC }
}

void refreshLayout() {
   if (user choose layoutA) { refresh layoutA }
   if (user choose layoutB) { refresh layoutB }
   if (user choose layoutC) { refresh layoutC }
}

void cleanupLayout() {
   if (user choose layoutA) { cleanup layoutA }
   if (user choose layoutB) { cleanup layoutB }
   if (user choose layoutC) { cleanup layoutC }
}

Gets replaced with something simpler:

   layout = getLayout(user choice);

   layout.initLayout();
   layout.refreshLayout();
   layout.cleanupLayout();
James Schek
+1  A: 

I'm a "little fuzzy" on your code architecture but the idea is that you only have the switch in one place rather than having multiple switches strewn throughout your code that have the same exact function signatures except the object it is operating on.

The exact implementation depends on what your goals are, but I would think that you would have one class that implements LayoutHander and has a member reference to a Layout. When the user picks his layout, the polymorphism takes hold HERE, not the level above as you have it now. That is to say, if the object that defines the different behavior is the "Layout" then you need to make Layout polymorphic, not LayoutHander.

When you think polymorhism, think code reuse. "What common set of functions do these relateded yet different objects share?"

San Jacinto
My overall goal is to create a sort of wrapper class that encapsulates each type of layout. Every layout will also have its own set of functionality. For example, layoutA is capable of printing to HTML, while layoutB is capable of printing to XML. For this reason, each layout is instantiated in different ways. I wanted to be able to implement my main() in such a way that it is easy-to-read and maintainable. Also, it would allow the addition of future layout classes somewhere down the line.
+2  A: 

Since java doesn't have first class functions, you can use interfaces to wrap around.

LayoutHandler ~> Interface

LayoutHandlerA, LayoutHandlerB, etc implements LayoutHandler

Map<String, LayoutHandler> handlers = new HashMap<...>();

LayoutHandler handler = handlers.get(userSelectedLayout);

handler.handle();
letronje
+1  A: 

I think using if-statements for initialization is fine. You're trying to avoid the repeated use of if-statements to "select" behavior throughout the program. Using if-statements once for initialization is fine.

To be sure, there are certainly ways to avoid even those initializer if-statements, but you have to decide what level of complexity and possible loss of readability is appropriate for your app.

For example, here are some approaches to this problem from simple to more complex:

  • use those initializer if-statements
    • uses hard-coded references to implementing classes directly in the program logic (harder to find)
  • initialize some data structure (e.g. Map) with the possible implementing classes
    • still hard-coded references to implementing classes
    • generally easier to add more implementing classes
    • code is a little more complicated and abstract to understand and debug
  • use dynamic registration
    • no hard-coded references to the implementing classes in the app
    • more setup is required
    • code is harder to understand without knowing how the registration is setup to work

A good example of the last method (dynamic registration) is to look at how JDBC works. JDBC drivers are registered in the app using Class.forName() and then a specific JDBC driver is selected using a JDBC URL. Here's a typical workflow:

  1. Potential target JDBC drivers are added classpath.

  2. The app is configured with a list of these drivers, e.g. by listing the JDBC drivers in a property file.

  3. The app initializes by calling Class.forName() on each driver in the list. Each driver knows to register itself with the DriverManager when it gets loaded by Class.forName()

  4. The app is determines what target database resource to use and resolving that to a JDBC URL, e.g. in a configuration dialog or user prompt.

  5. The app asks the DriverManager for a connection based on the target JDBC URL. The DriverManager polls each registered driver to see if it can handle the target JDBC URL until the DriverManager finds one that works.

This would be the opposite extreme to avoid those if-statements.

Bert F
+1  A: 

"..for future use if more layouts need to be added"

I'd also suggest you look into the factory pattern. If you contain this conditional logic within a factory, it should help down the line with maintenance and readability.

P. Deters