On reusability (Part 2) ...Misconceptions
/**
* An interface defining a single method you want to reuse
*/
public interface MathOperation
{
public double op(double number);
}
/**
* All available operators
*/
public class MathOperations
{
/*
* In an OO world an Object is a first class citizen.
* To have an object you need a class
*/
private static final class Multiplication implements MathOperation
{
private final double multiplier;
private Multiplication(double multiplier)
{
this.multiplier = multiplier;
}
public double op(double number){
return number * this.multiplier;
}
}
private static final class Addition implements MathOperation
{
private final double amount;
private Addition(double amount)
{
this.amount = amount;
}
public double op(double number){
return number + this.amount;
}
}
/*
* Reusable! operations
*/
public static final MathOperation MULTIPLY_BY_2 =
new Multiplication(2);
public static final MathOperation MULTIPLY_BY_PI =
new Multiplication(Math.PI);
public static Addition addition(double amount){
return new Addition(amount);
}
public static MathOperation multiplication(double multiplier) {
return new Multiplication(multiplier);
}
}
/**
* All available formulas for your perusal
*/
public class Formulas
{
public static final Formula AREA_OF_CIRCLE = Formula.newFormula(
MathOperations.MULTIPLY_BY_2,
MathOperations.MULTIPLY_BY_PI
);
}
/**
* Describes a list of operations
*/
public final class Formula
{
/**
* Create a new formula which applies the operators in the order
* specified
*
* @param operators the operators to apply in sequence
* @return a new formula for the spcified operators
* @see MathOperations
*/
public static final Formula newFormula(MathOperation... operators){
return new Formula( Lists.newArrayList(operators) );
}
private final List<MathOperation> operations;
private Formula(List<MathOperation> operators)
{
this.operations = operators;
}
public double apply(double number)
{
for (MathOperation operation : this.operations){
number = operation.op(number);
}
return number;
}
}
/**
* Code that is fun to use
*/
public class FormulaTest
{
@Test
public void areaOfCircle() throws Exception
{
double r = 1;
Assert.assertEquals(
2 * Math.PI,
Formulas.AREA_OF_CIRCLE.apply(r));
}
@Test
public void formula() throws Exception
{
int n = 9;
Formula formula =
Formula.newFormula(
MathOperations.addition(1),
MathOperations.multiplication(n),
MathOperations.division(2)
);
Assert.assertEquals(45.0, formula.apply(n));
}
@Test
public void areaOfTriangle() throws Exception
{
int base = 10;
int height = 10;
Formula areaOfTriangle =
Formula.newFormula(
MathOperations.multiplication(height),
MathOperations.division(2)
);
Assert.assertEquals(50.0, areaOfTriangle.apply(base));
}
}
Methods aren’t reusable. Objects are.
public static final MathOperation MULTIPLY_BY_2 =
new Multiplication(2);
public static final MathOperation MULTIPLY_BY_PI =
new Multiplication(Math.PI);
Notice how
public double multiplyBy(double number, double multiplier){
return number * multiplier;
}
turned into a class
public final class Multiplication
{
private final double multiplier;
public Multiplication(double multiplier)
{
this.multiplier = multiplier;
}
public double multiply(double number){
return number * this.multiplier;
}
}
At first you might gasp that a single method has turned into a class. Misko Hevery actually has something to say about this
Recently I got an email from someone pointing out that doing proper Single Responsibility Principle, often resulted in classes which had exactly one method. Odd? Well, actually, it is a function in disguise.
There isn’t such a thing as a class that does too little. If there is a reason for a class to exist it will manifest in your code whether you choose to ignore it or not. Worse it won’t stay hidden but will plague the design of the rest of your code.
Classes not only aid in your design but also sed light on the problem domain you are currently trying to solve. They allow you to see further. If you don’t have classes, you can’t think of relationships.
Adding methods, whether static or not, keep your code from evolving. They are not extensible since you need to add a new one every time and don’t allow your code to be treated uniformly creating unnecessary complexity which could have been avoided otherwise.
for (MathOperation operation : this.operations){
number = operation.op(number);
}
So the next time you are tempted to add a new method, stop. Think whether it really makes sense or is a mere illusion to feed your appetite for code.
Notice how you can easily introduce more Operations effectively extending Formula through composition
Disclaimer This might look like a trivial example that rarely applies in a real life scenario. In my experience this is not the case. Think of bytes and sockets, sql and jdbc connections, queries and services, http requests and clients, messages and message dispatchers.