Converting many 'if else' statements to a cleaner approach [duplicate] - java

This question already has answers here:
Long list of if statements in Java
(15 answers)
Closed 4 years ago.
My code here detects if the mimeType is equals to some MIME type, if it is, it will do a certain conversion
public void convertToMp3(File src, File target,String mimeType){
if(mimeType.equals("audio/mpeg")){
...
}else if(mimeType.equals("audio/wav")){
mp3ToWav();
}else if(mimeType.equals("audio/ogg")){
...
}else if(...){
... //More if and else here
}
I have shortened my code, because it has a lot of else if statements, What design pattern is suitable for removing many if and else or else if statements?

You could have a Converter interface. Then you could create a class for each Mimetype like:
public interface Converter {
public void convertToMp3();
public void convertToOgg();
}
public class MpegConverter implements Converter {
public void convertToMp3() {
//Code here
}
public void convertToOgg() {
//Code here
}
}
You would need a class like this for each converter. Then you could set up a map like this:
Map<String, Converter> mimeTypeMap = new HashMap<String, Converter>();
mimeTypeMap.put("audio/mpeg", new MpegConverter());
Then your convertToMp3 method becomes like this:
Converter converter = mimeTypeMap.get(mimeType);
converter.convertToMp3();
Using this approach you could easily add different converters in the future.
All untested, probably doesn't compile, but you get the idea

If you use pre-JDK7, you may add an enum for all MIME types:
public static enum MimeTypes {
MP3, WAV, OGG
}
public class Stuff {
...
switch (MimeTypes.valueOf(mimeType)) {
case MP3: handleMP3(); break;
case WAV: handleWAV(); break;
case OGG: handleOGG(); break;
}
}
And have a look at the Stack Overflow question Java - Convert String to enum on how to convert Strings to enums.

Consider using the Strategy design pattern and a Map to dispatch to the appropriate strategy. Particularly useful if you you will need additional functionality, in addition to a conversion for a particular mimeType, or the convertors are large and complicated code and you would want to place each convertor in its own .java file.
interface Convertor {
void convert(File src, File target);
}
private static void convertWav(File src, File target) {
...
}
...
private static final Map< String, Convertor > convertors = new ...;
static {
convertors.put("audio/wav", new Convertor {
void convert(File src, File target) {
convertWav(src, target);
}
});
convertors.put("audio/ogg", new Convertor {
void convert(File src, File target) {
convertOgg(src, target);
}
});
...
}
public void convertToMp3(File src, File target, String mimeType){
final Convertor convertor = convertors.get(mimeType);
if (convertor == null ) {
...
} else {
convertor.convert(src, target);
}
}

If you run the same methods for each case you should check State pattern

If you are using JDK 7, you can use switch-case construct:
See: Why can't I switch on a String?
For prior versions, if-else is the only choice.

It's definitely a Strategy design pattern. But you have a big problem in your general design. It's not a good programming habit to use String to identify a type. Simply because it's easily editable and you can make a grammar mistake and spend all the afternoon looking for a programming mistake. You can avoid using map<>.
I suggest the following:
Extend class File. The new class adds a new attribute FileType and a new method convertTo(FileType) to class File. This attribute holds its type: “audio” , “wav”... and again don't use String, Use Enum. In this case I called it FileType. Extend File as much as you want: WavFile, AudioFile...
Use a Strategy dp to create your converters.
Use a Factory dp to initialize the converters.
Since every File knows its own type and the target type (use convertTo() method to specify the target type) it will call the factory to get the correct Converter automatically!!!
This design is scalable and you can add as much as you need FileType and converters.
The answer you vote for is misleading!!!!
There is a big difference between coding and hacking.

If you are not using Java 7 you could create an enum and use that value with a switch case. You then only need to pass the enum value (rather than a file, I don't why you are doing that). It would look neater too.
These should help with what you want to do:
[Java Enum Examples][1] -
[Java Switch Case examples][2]

Related

How to replace an if-based logic with depends on boolean values using a better strategy? [duplicate]

This question already has answers here:
Long list of if statements in Java
(15 answers)
Closed 4 years ago.
My code here detects if the mimeType is equals to some MIME type, if it is, it will do a certain conversion
public void convertToMp3(File src, File target,String mimeType){
if(mimeType.equals("audio/mpeg")){
...
}else if(mimeType.equals("audio/wav")){
mp3ToWav();
}else if(mimeType.equals("audio/ogg")){
...
}else if(...){
... //More if and else here
}
I have shortened my code, because it has a lot of else if statements, What design pattern is suitable for removing many if and else or else if statements?
You could have a Converter interface. Then you could create a class for each Mimetype like:
public interface Converter {
public void convertToMp3();
public void convertToOgg();
}
public class MpegConverter implements Converter {
public void convertToMp3() {
//Code here
}
public void convertToOgg() {
//Code here
}
}
You would need a class like this for each converter. Then you could set up a map like this:
Map<String, Converter> mimeTypeMap = new HashMap<String, Converter>();
mimeTypeMap.put("audio/mpeg", new MpegConverter());
Then your convertToMp3 method becomes like this:
Converter converter = mimeTypeMap.get(mimeType);
converter.convertToMp3();
Using this approach you could easily add different converters in the future.
All untested, probably doesn't compile, but you get the idea
If you use pre-JDK7, you may add an enum for all MIME types:
public static enum MimeTypes {
MP3, WAV, OGG
}
public class Stuff {
...
switch (MimeTypes.valueOf(mimeType)) {
case MP3: handleMP3(); break;
case WAV: handleWAV(); break;
case OGG: handleOGG(); break;
}
}
And have a look at the Stack Overflow question Java - Convert String to enum on how to convert Strings to enums.
Consider using the Strategy design pattern and a Map to dispatch to the appropriate strategy. Particularly useful if you you will need additional functionality, in addition to a conversion for a particular mimeType, or the convertors are large and complicated code and you would want to place each convertor in its own .java file.
interface Convertor {
void convert(File src, File target);
}
private static void convertWav(File src, File target) {
...
}
...
private static final Map< String, Convertor > convertors = new ...;
static {
convertors.put("audio/wav", new Convertor {
void convert(File src, File target) {
convertWav(src, target);
}
});
convertors.put("audio/ogg", new Convertor {
void convert(File src, File target) {
convertOgg(src, target);
}
});
...
}
public void convertToMp3(File src, File target, String mimeType){
final Convertor convertor = convertors.get(mimeType);
if (convertor == null ) {
...
} else {
convertor.convert(src, target);
}
}
If you run the same methods for each case you should check State pattern
If you are using JDK 7, you can use switch-case construct:
See: Why can't I switch on a String?
For prior versions, if-else is the only choice.
It's definitely a Strategy design pattern. But you have a big problem in your general design. It's not a good programming habit to use String to identify a type. Simply because it's easily editable and you can make a grammar mistake and spend all the afternoon looking for a programming mistake. You can avoid using map<>.
I suggest the following:
Extend class File. The new class adds a new attribute FileType and a new method convertTo(FileType) to class File. This attribute holds its type: “audio” , “wav”... and again don't use String, Use Enum. In this case I called it FileType. Extend File as much as you want: WavFile, AudioFile...
Use a Strategy dp to create your converters.
Use a Factory dp to initialize the converters.
Since every File knows its own type and the target type (use convertTo() method to specify the target type) it will call the factory to get the correct Converter automatically!!!
This design is scalable and you can add as much as you need FileType and converters.
The answer you vote for is misleading!!!!
There is a big difference between coding and hacking.
If you are not using Java 7 you could create an enum and use that value with a switch case. You then only need to pass the enum value (rather than a file, I don't why you are doing that). It would look neater too.
These should help with what you want to do:
[Java Enum Examples][1] -
[Java Switch Case examples][2]

Invoke Java object constant using a variable

I'm very new to Java so it makes it hard for me to explain what I'm trying to do.
I have an abstract class that invokes several object constants like this:
public abstract class Enchantment implements Keyed {
/**
* Provides protection against environmental damage
*/
public static final Enchantment PROTECTION_ENVIRONMENTAL = new EnchantmentWrapper("protection");
In a different file I can access this perfectly fine with Enchantment value = Enchantment.PROTECTION_ENVIRONMENTAL;
However, I'm trying to use a string variable for this instead. Something like this:
String str = "PROTECTION_ENVIRONMENTAL";
Enchantment value = Enchantment.str;
Obviously that won't work. So I did a bunch of research and learned I need to use reflection for this. Using this source code's docs I figured I was looking for field data. So I tried both:
Field fld = Enchantment.class.getField("PROTECTION_ENVIRONMENTAL");
Field fld = Enchantment.class.getDeclaredField("PROTECTION_ENVIRONMENTAL");
But these returned me a NoSuchFieldException. As I was on it, I've tried both getMethod() and getDeclaredMethod() just as well equally with no luck.
I'm now at the point that these are probably "object constants"? I'm not sure how to call them. But I'm definitely at a loss on how to get this to work now and after everything I've tried myself, I figured it was time to ask for some help here.
That one comment is spot on: you absolutely do not use reflection here.
There are only two valid reasons to use reflection:
you are creating a framework that has to deal with classes it doesn't know about
you have for some other reason to deal with classes you don't know about at compile time
But your code perfectly knows about that Enchantment class, its capabilities, and so on. Therefore reflection is the wrong approach. You figured it yourself: it is damn hard to get right, and damn right to get it wrong in some subtle ways. And when you get it wrong, it always blows up at runtime. Reflection code compiling means nothing. It always waits for you to run it to throw up in your face.
So to answer your question by not answering it: use a Map. Like:
Map<String, Enchantment> enchantmentsByConstantName = new HashMap<>();
enchantmentsByConstantName.put("PROTECTION_ENVIRONMENTAL", PROTECTION_ENVIRONMENTAL);
Alternatively, these constants could go into an enum, as outlined in the other answer, but in a sightly different way:
enum EnchantmentHolder {
PROTECTION_ENVIRONMENTAL(new EnchantmentWrapper("protection")),
ANOTHER_ENCHANTMENT(...)
A_THIRD_ENCHANTMENT(...)
...;
private Enchantment enchantment;
private EnchantmentHolder(Enchantment enchantment) {
this.entchantment = entchantment;
}
public Enchantment getEntchantment() { return entchantment; }
You may want to look into enumerations if you know they're going to be constant values;
public enum Enchantment {
PROTECTION_ENVIRONMENTAL {
public void cast() {
// do enum-specific stuff here
}
},
ANOTHER_ENCHANTMENT {
public void cast() {
// do enum-specific stuff here
}
},
A_THIRD_ENCHANTMENT{
public void cast() {
// do enum-specific stuff here
}
};
public abstract void cast();
}
enums can be treated like classes and have methods and properties. You can also convert to and from strings Enchantment.valueOf("PROTECTION_ENVIRONMENTAL") but that's generally if you are reading from a configuration file - in code you'd reference the value directly.
Once you have the Field, you need to call Field.get(Object) with an instance (in this case the class). Something like,
Class<?> cls = Enchantment.class;
try {
Field f = cls.getField("PROTECTION_ENVIRONMENTAL");
System.out.println(f.get(cls));
} catch (Exception e) {
e.printStackTrace();
}
Since you want the Enchantment, you could then test that the instance you get is assignable to Enchantment. Something like,
Class<? extends Enchantment> cls = Enchantment.class;
try {
Field f = cls.getField("PROTECTION_ENVIRONMENTAL");
Object obj = f.get(cls);
if (cls.isAssignableFrom(obj.getClass())) {
Enchantment e = cls.cast(obj);
System.out.println(e);
}
} catch (Exception e) {
e.printStackTrace();
}
But the enum approach is better.

Design pattern for IF - Else refactoring code [duplicate]

This question already has answers here:
Long list of if statements in Java
(15 answers)
Closed 4 years ago.
My code here detects if the mimeType is equals to some MIME type, if it is, it will do a certain conversion
public void convertToMp3(File src, File target,String mimeType){
if(mimeType.equals("audio/mpeg")){
...
}else if(mimeType.equals("audio/wav")){
mp3ToWav();
}else if(mimeType.equals("audio/ogg")){
...
}else if(...){
... //More if and else here
}
I have shortened my code, because it has a lot of else if statements, What design pattern is suitable for removing many if and else or else if statements?
You could have a Converter interface. Then you could create a class for each Mimetype like:
public interface Converter {
public void convertToMp3();
public void convertToOgg();
}
public class MpegConverter implements Converter {
public void convertToMp3() {
//Code here
}
public void convertToOgg() {
//Code here
}
}
You would need a class like this for each converter. Then you could set up a map like this:
Map<String, Converter> mimeTypeMap = new HashMap<String, Converter>();
mimeTypeMap.put("audio/mpeg", new MpegConverter());
Then your convertToMp3 method becomes like this:
Converter converter = mimeTypeMap.get(mimeType);
converter.convertToMp3();
Using this approach you could easily add different converters in the future.
All untested, probably doesn't compile, but you get the idea
If you use pre-JDK7, you may add an enum for all MIME types:
public static enum MimeTypes {
MP3, WAV, OGG
}
public class Stuff {
...
switch (MimeTypes.valueOf(mimeType)) {
case MP3: handleMP3(); break;
case WAV: handleWAV(); break;
case OGG: handleOGG(); break;
}
}
And have a look at the Stack Overflow question Java - Convert String to enum on how to convert Strings to enums.
Consider using the Strategy design pattern and a Map to dispatch to the appropriate strategy. Particularly useful if you you will need additional functionality, in addition to a conversion for a particular mimeType, or the convertors are large and complicated code and you would want to place each convertor in its own .java file.
interface Convertor {
void convert(File src, File target);
}
private static void convertWav(File src, File target) {
...
}
...
private static final Map< String, Convertor > convertors = new ...;
static {
convertors.put("audio/wav", new Convertor {
void convert(File src, File target) {
convertWav(src, target);
}
});
convertors.put("audio/ogg", new Convertor {
void convert(File src, File target) {
convertOgg(src, target);
}
});
...
}
public void convertToMp3(File src, File target, String mimeType){
final Convertor convertor = convertors.get(mimeType);
if (convertor == null ) {
...
} else {
convertor.convert(src, target);
}
}
If you run the same methods for each case you should check State pattern
If you are using JDK 7, you can use switch-case construct:
See: Why can't I switch on a String?
For prior versions, if-else is the only choice.
It's definitely a Strategy design pattern. But you have a big problem in your general design. It's not a good programming habit to use String to identify a type. Simply because it's easily editable and you can make a grammar mistake and spend all the afternoon looking for a programming mistake. You can avoid using map<>.
I suggest the following:
Extend class File. The new class adds a new attribute FileType and a new method convertTo(FileType) to class File. This attribute holds its type: “audio” , “wav”... and again don't use String, Use Enum. In this case I called it FileType. Extend File as much as you want: WavFile, AudioFile...
Use a Strategy dp to create your converters.
Use a Factory dp to initialize the converters.
Since every File knows its own type and the target type (use convertTo() method to specify the target type) it will call the factory to get the correct Converter automatically!!!
This design is scalable and you can add as much as you need FileType and converters.
The answer you vote for is misleading!!!!
There is a big difference between coding and hacking.
If you are not using Java 7 you could create an enum and use that value with a switch case. You then only need to pass the enum value (rather than a file, I don't why you are doing that). It would look neater too.
These should help with what you want to do:
[Java Enum Examples][1] -
[Java Switch Case examples][2]

String input to specify which function to call [Java] [Best Practice]

The Application
I am writing an application that executes certain functions depending on user input.
E.g. if the user input were to be
"1 2 add" the output would be "3".
I aim to implement many such methods (div, modulo, etc.). As my Scanner recognizes a function name like "add" the function "add()" should be called.
My Way
My way to do this is to let a FunctionHandler class evaluate the input.
Main:
String inputCommand = sc.nextCommand();
functionHandler.handle(inputCommand);
Function Handler:
public class FunctionHandler {
public void handle (String functionName) {
if (functionName.equals("add")) {
add();
} else if (functionName.equals("div") {
div();
}
}
private void add() {
.......
}
....
}
The Problem with that
As I am adding more and more functions the if statement gets very large, and of course the FunctionHandler class too. Also, whenever I add a new function, I have to change code in two places: I have to define the function, and then add the else if clause in handle() to call the function. Which means two pieces of information that should be encapsulated are "stored" completely independent from each other.
I was wondering what the best practice was to solve this kind of situation?
My Ideas
I was thinking about using enums, but they don't seem to fit well in this case.
Another idea I had was creating an interface Function, and then a class for each function that implements Function. The interface would have two methods:
getName()
execute()
Then I could create an array (manually) of Functions in the FunctionHandler, through which I could loop to see if the command the user enters matches getName().
However, having a different class for each function is not very clean either, and it also does not get rid of the problem that for each function I am adding I have to do it in two places: the class and the array.
This question is only about finding out how to solve this problem cleanly. A pointer in the right direction would be appreciated!
Thanks a lot!
Another option would be to keep a Map of handlers. If you're using Java 8, they can even be method references.
// InputType and ResultType are types you define
Map<String, Function<InputType, ResultType>> operations = new HashMap<>();
operations.put("add", MathClass::add);
// ...
ResultType result = operations.get(userInput).apply(inputObject);
One downside to doing it this way is that your input and output types must be the same for all operations.
You could create a custom annotation for the various functions. Then you could employ your array idea, but have it use reflection to discover which functions have your new annotation and what their names are.
As background, take a look at http://www.oracle.com/technetwork/articles/hunter-meta-2-098036.html and http://www.oracle.com/technetwork/articles/hunter-meta-3-092019.html. They're a bit old, but seem to address the necessary ideas.
You can always use reflection if you want a short solution.
In your handle method you could do something like this:
Method m = this.getClass().getMethod(functionName, new Class[]{});
m.invoke(this, new Object[]{});
Assuming you do not have a lot of functions that you want to do this way, and do not want to expose yourself to the security risks caused by reflection, you could use a string switch, like this:
void handleFunction(String function) {
switch (function) {
case "foo":
foo();
break;
case "bar":
bar();
break;
default:
throw new IllegalArgumentException("Unknown function " + function);
break;
}
}
Starting Java 7, you can use Strings in a switch statement and the compiler will make something reasonable out of it
I would do something like this:
public class FunctionTest {
private static final Map<String, Runnable> FUNCTIONS = new HashMap<String, Runnable>() {{
put("add", () -> System.out.println("I'm adding something!"));
put("div", () -> System.out.println("I'm dividing something!"));
}};
public void handle(String functionName) {
if (!FUNCTIONS.containsKey(functionName)) {
throw new IllegalArgumentException("No function with this name: " + functionName);
}
FUNCTIONS.get(functionName).run();
}
}
You basically can use any functional interface in place of Runnable, I used it, because it matches your add() method. You can map the names of the functions to their actual executable instance, get them by name from the Map and execute them.
You could also create an enum with the desired executable blocks:
public class FunctionsAsEnumsTest {
private static enum MyFunction {
ADD {
#Override public void execute() {
System.out.println("I'm adding something");
}
},
DIV {
#Override public void execute() {
System.out.println("I'm dividing something");
}
};
public abstract void execute();
}
public void handle(String functionName) {
// #toUpperCase() might not be the best idea,
// you could name your enums as you would the methods.
MyFunction fn = MyFunction.valueOf(functionName.toUpperCase());
fn.execute();
}
}

Letting the code try different things until it succeeds, neatly

This is the second time I found myself writing this kind of code, and decided that there must be a more readable way to accomplish this:
My code tries to figure something out, that's not exactly well defined, or there are many ways to accomplish it. I want my code to try out several ways to figure it out, until it succeeds, or it runs out of strategies. But I haven't found a way to make this neat and readable.
My particular case: I need to find a particular type of method from an interface. It can be annotated for explicitness, but it can also be the only suitable method around (per its arguments).
So, my code currently reads like so:
Method candidateMethod = getMethodByAnnotation(clazz);
if (candidateMethod == null) {
candidateMethod = getMethodByBeingOnlyMethod(clazz);
}
if (candidateMethod == null) {
candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
}
if (candidateMethod == null) {
throw new NoSuitableMethodFoundException(clazz);
}
There must be a better way…
Edit: The methods return a method if found, null otherwise. I could switch that to try/catch logic, but that hardly makes it more readable.
Edit2: Unfortunately, I can accept only one answer :(
To me it is readable and understandable. I'd simply extract the ugly part of the code to a separate method (following some basic principles from "Robert C.Martin: Clean Code") and add some javadoc (and apologies, if necessary) like that:
//...
try {
Method method = MethodFinder.findMethodIn(clazz);
catch (NoSuitableMethodException oops) {
// handle exception
}
and later on in MethodFinder.java
/**
* Will find the most suitable method in the given class or throw an exception if
* no such method exists (...)
*/
public static Method findMethodIn(Class<?> clazz) throws NoSuitableMethodException {
// all your effort to get a method is hidden here,
// protected with unit tests and no need for anyone to read it
// in order to understand the 'main' part of the algorithm.
}
I think for a small set of methods what you're doing is fine.
For a larger set, I might be inclined to build a Chain of Responsibility, which captures the base concept of trying a sequence of things until one works.
I don't think that this is such a bad way of doing it. It is a bit verbose, but it clearly conveys what you are doing, and is easy to change.
Still, if you want to make it more concise, you can wrap the methods getMethod* into a class which implements an interface ("IMethodFinder") or similar:
public interface IMethodFinder{
public Method findMethod(...);
}
Then you can create instances of you class, put them into a collection and loop over it:
...
Method candidateMethod;
findLoop:
for (IMethodFinder mf: myMethodFinders){
candidateMethod = mf.findMethod(clazz);
if (candidateMethod!=null){
break findLoop;
}
}
if (candidateMethod!=null){
// method found
} else {
// not found :-(
}
While arguably somewhat more complicated, this will be easier to handle if you e.g. need to do more work between calling the findMethods* methods (such as more verification that the method is appropriate), or if the list of ways to find methods is configurable at runtime...
Still, your approach is probably OK as well.
I'm sorry to say, but the method you use seems to be the widely accepted one. I see a lot of code like that in the code base of large libraries like Spring, Maven etc.
However, an alternative would be to introduce a helper interface that can convert from a given input to a given output. Something like this:
public interface Converter<I, O> {
boolean canConvert(I input);
O convert(I input);
}
and a helper method
public static <I, O> O getDataFromConverters(
final I input,
final Converter<I, O>... converters
){
O result = null;
for(final Converter<I, O> converter : converters){
if(converter.canConvert(input)){
result = converter.convert(input);
break;
}
}
return result;
}
So then you could write reusable converters that implement your logic. Each of the converters would have to implement the canConvert(input) method to decide whether it's conversion routines will be used.
Actually: what your request reminds me of is the Try.these(a,b,c) method in Prototype (Javascript).
Usage example for your case:
Let's say you have some beans that have validation methods. There are several strategies to find these validation methods. First we'll check whether this annotation is present on the type:
// retention, target etc. stripped
public #interface ValidationMethod {
String value();
}
Then we'll check whether there's a method called "validate". To make things easier I assume, that all methods define a single parameter of type Object. You may choose a different pattern. Anyway, here's sample code:
// converter using the annotation
public static final class ValidationMethodAnnotationConverter implements
Converter<Class<?>, Method>{
#Override
public boolean canConvert(final Class<?> input){
return input.isAnnotationPresent(ValidationMethod.class);
}
#Override
public Method convert(final Class<?> input){
final String methodName =
input.getAnnotation(ValidationMethod.class).value();
try{
return input.getDeclaredMethod(methodName, Object.class);
} catch(final Exception e){
throw new IllegalStateException(e);
}
}
}
// converter using the method name convention
public static class MethodNameConventionConverter implements
Converter<Class<?>, Method>{
private static final String METHOD_NAME = "validate";
#Override
public boolean canConvert(final Class<?> input){
return findMethod(input) != null;
}
private Method findMethod(final Class<?> input){
try{
return input.getDeclaredMethod(METHOD_NAME, Object.class);
} catch(final SecurityException e){
throw new IllegalStateException(e);
} catch(final NoSuchMethodException e){
return null;
}
}
#Override
public Method convert(final Class<?> input){
return findMethod(input);
}
}
// find the validation method on a class using the two above converters
public static Method findValidationMethod(final Class<?> beanClass){
return getDataFromConverters(beanClass,
new ValidationMethodAnnotationConverter(),
new MethodNameConventionConverter()
);
}
// example bean class with validation method found by annotation
#ValidationMethod("doValidate")
public class BeanA{
public void doValidate(final Object input){
}
}
// example bean class with validation method found by convention
public class BeanB{
public void validate(final Object input){
}
}
You may use Decorator Design Pattern to accomplish different ways of finding out how to find something.
public interface FindMethod
{
public Method get(Class clazz);
}
public class FindMethodByAnnotation implements FindMethod
{
private final FindMethod findMethod;
public FindMethodByAnnotation(FindMethod findMethod)
{
this.findMethod = findMethod;
}
private Method findByAnnotation(Class clazz)
{
return getMethodByAnnotation(clazz);
}
public Method get(Class clazz)
{
Method r = null == findMethod ? null : findMethod.get(clazz);
return r == null ? findByAnnotation(clazz) : r;
}
}
public class FindMethodByOnlyMethod implements FindMethod
{
private final FindMethod findMethod;
public FindMethodByOnlyMethod(FindMethod findMethod)
{
this.findMethod = findMethod;
}
private Method findByOnlyMethod(Class clazz)
{
return getMethodOnlyMethod(clazz);
}
public Method get(Class clazz)
{
Method r = null == findMethod ? null : findMethod.get(clazz);
return r == null ? findByOnlyMethod(clazz) : r;
}
}
Usage is quite simple
FindMethod finder = new FindMethodByOnlyMethod(new FindMethodByAnnotation(null));
finder.get(clazz);
... I could switch that to try/catch logic, but that hardly makes it more readable.
Changing the signature of the get... methods so you can use try / catch would be a really bad idea. Exceptions are expensive and should only be used for "exceptional" conditions. And as you say, the code would be less readable.
What is bothering you is the repeating pattern used for flow control--and it should bother you--but there isn't too much to be done about it in Java.
I get really annoyed at repeated code & patterns like this, so for me it would probably be worth it to extract the repeated copy & paste control code and put it in it's own method:
public Method findMethod(Class clazz)
int i=0;
Method candidateMethod = null;
while(candidateMethod == null) {
switch(i++) {
case 0:
candidateMethod = getMethodByAnnotation(clazz);
break;
case 1:
candidateMethod = getMethodByBeingOnlyMethod(clazz);
break;
case 2:
candidateMethod = getMethodByBeingOnlySuitableMethod(clazz);
break;
default:
throw new NoSuitableMethodFoundException(clazz);
}
return clazz;
}
Which has the disadvantage of being unconventional and possibly more verbose, but the advantage of not having as much repeated code (less typos) and reads easier because of there being a little less clutter in the "Meat".
Besides, once the logic has been extracted into it's own class, verbose doesn't matter at all, it's clarity for reading/editing and for me this gives that (once you understand what the while loop is doing)
I do have this nasty desire to do this:
case 0: candidateMethod = getMethodByAnnotation(clazz); break;
case 1: candidateMethod = getMethodByBeingOnlyMethod(clazz); break;
case 2: candidateMethod = getMethodByBeingOnlySuitableMethod(clazz); break;
default: throw new NoSuitableMethodFoundException(clazz);
To highlight what's actually being done (in order), but in Java this is completely unacceptable--you'd actually find it common or preferred in some other languages.
PS. This would be downright elegant (damn I hate that word) in groovy:
actualMethod = getMethodByAnnotation(clazz) ?:
getMethodByBeingOnlyMethod(clazz) ?:
getMethodByBeingOnlySuitableMethod(clazz) ?:
throw new NoSuitableMethodFoundException(clazz) ;
The elvis operator rules. Note, the last line may not actually work, but it would be a trivial patch if it doesn't.

Categories