Search code examples
java-8functional-programmingobject-oriented-analysismethod-referencefunctional-interface

Storing functions in instance variables(Java)


I like to store simple functions which can be expressed as one line lambda in private and final instance variables when they are used several times by other methods in the class.

Also I do that sometimes with functions which are not used several times to make a big method shorter and to have a stronger expression of the functionality used in the method.

    public class SomeClass {
    private final UnaryOperator<Something> someMethod = s -> doSomething;

    public void someMethod2(Something s) {
        //some code which goes over several lines and uses someMethod variable
    }

    public void someMethod3(Something s) {
        //some code which goes over several lines and uses someMethod variable
    }
}

Another example in which the input is preapred for diffrent services in a helper class. The two same characters have to be replaced in the String for both services. The function for this is not stored a normal static method, it is stored in a static field which implements the UnaryOperator.

public class PrepareServicesInputHelper {
    private static final UnaryOperator<String> replaceSignsForAllServices = s -> s.replace('+', '-')
            .replace('/', '*');

    public static String transformStringForServiceOne(String s) {
        return "Additional transformation information for service one" + removeUnwantedSigns.apply(s);
    }

    public static String transformStringForServiceTwo(String s) {
        return "Additional transformation information for service two:" + removeUnwantedSigns.apply(s);
    }
}

It looks better and is clearer this way for me.

Is this a good idea or are there some disadvantages which are practical or hurt some paradigms of clean code with this approach and the functionality should be stored traditional in instance methods?


Solution

  • In your example I cannot see any added value of your solution compared with just plain call of a method. Your solution is an overkill.

    Abraham Maslow (https://en.wikipedia.org/wiki/Law_of_the_instrument):

    I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.

    The proper use case for lambdas is where you need to pass a function, or to select a function from multiple possible ones. Here your use case is just a plain re-use of a method, obfuscated by a lambda.

    Study the behavioural design patterns here or here, such as Command, Observer/Listener, Strategy, Visitor. Here you need to pass a behaviour and that's what lambdas are designed for in Java.

    Another quick "rule of thumb" may be this:

    1. Do you need to call a method right now? So call a method.
    2. Do you need to pass a method to be called from inside another method, later, maybe called not once but multiple times, maybe even not at all (if the method in control decides to)? So pass a lambda.

    For your use case the common way is this. It looks even better and is even clearer this way :)

    public class PrepareServicesInputHelper {
        private static String replaceSignsForAllServices(final String s) {
            return s.replace('+', '-').replace('/', '*');
        }
    
        public static String transformStringForServiceOne(final String s) {
            return "Additional transformation information for service one" + removeUnwantedSigns(s);
        }
    
        public static String transformStringForServiceTwo(final String s) {
            return "Additional transformation information for service two:" + removeUnwantedSigns(s);
        }
    }
    

    A good example, similar to the yours but not the same, is this. You have a logger which writes to a file, but only if you turn on the logging. Evaluating the log text might be costly and we want to calculate it only lazily, only when needed.

    public class Logger {
        private boolean logIsOn;
        // constructor, setters, getters etc.
    
        public log(final String message) {
            if (logIsOn) {
                printToFile(message);
            }
        }
    
        public lazyLog(final Supplier<String> message) {
            if (logIsOn) {
                printToFile(message.get());
            }
        }
    }
    
    // Here the expensive calculation occurs always, even if the logging is off
    logger.log("Operation x performed on " + 
        person.getFirstName() + " " +
        person.getLastName() + 
        " with the result " + result.calculate()); 
    
    // Here the expensive calculation occurs only when the logging is on
    logger.lazyLog(() -> "Operation x performed on " + 
        person.getFirstName() + " " +
        person.getLastName() + 
        " with the result " + result.calculate());