What true OOP is all about?
They can’t stop us. Let ’em try. For true OOP we will die.
What true OOP is all about? Learning Java and reading many tutorials we think that Java is OOP language and thus every program you write will be OOP (I’m talking about not syntax, but about architecture). Is this program OOP:
public class Main {
public static void main(String[] args) {
System.out.println(getGreetingString());
}
private static String getGreetingString() {
return "Hello world";
}
}
It’s not because we have only procedure and static function. Obviously it’s procedural one. And is this program OOP:
public class Main {
public static void main(String[] args) {
System.out.println(new Main().getGreetingString());
}
private String getGreetingString() {
return "Hello world";
}
}
In this case we have created object of class Main, but still it’s a procedural program, since Main is just a structure not an object with it’s own behavior. So let’s try to write our TRUE OOP program.
<note> Please, remember, that this article created mostly for fun like this one, so just in case do not take it to heart ;) let’s try to make some fun and think how we can build more OOP code. </note>
In this article we will get code from test task of my friend, who graciously accepted to torment his code and then we will transform it to TRUE OOP code. Then in the end of article I will try to explain how it is correlate with Android development.
Point
We have backend service which can handle one of 2 requests: first one is interpretation of Fizz buzz game that replaces numbers that are dividable to 3 or 5 with words Fizz, or buzz, or Fizz buzz; and second one is calculator that gets string expression as input, parses it, compute result and finally give it back to caller. So entire project is not interesting for us. We will look closer to the calculator.
Currently it have very simple and straightforward implementation. There are one interface that defines operation in calculator and there are class-implementer that provides implementations for plus and mult operators. Actually it an enum, but it’s the details. And also we have RndCalculator.class that do complete work for parsing string and then generating answer for it. So first of all we need to admit that last class has too wide duties and we can split it into several parts. Of course a result can affect to performance. But:
- Our goal is make small restricted classes
- As it is said “Make it work, make it right, make it fast” (this and this)
This part of service made to be working. Now we need to make it right. In our concrete case it means to split big functions into small one, to split wide classes into narrow with small amount of work each.
Give me my tokens
First we need to realize that we build a calculator that can transform string expression into set of tokens and then somehow interact with them according to special vocabulary and set of rules. When we have any of errors like unfamiliar token or illegal set of brackets, calculator will notify us about that by stopping its execution and throwing appropriate exception.
Thus for our need (if we will reread previous sentence and analyze it) we need to declare next interfaces:
public interface Token {
}
public interface Expression {
int compute();
}
public interface Lexer {
Collection<Token> parse(String expression);
}
public interface ExpressionTreeBuilder {
Expression buildExpr();
}
Token interface is a main type for our mechanism. It means every single unit of information: operation, number, brackets and e.t.c. Currently it is a marker interface, but, please, read few paragraphs and you will see, it will be updated. Thus, tokens are atoms of our system.
Our first step is to convert String to tokens. Lexer interface is defined for that. It takes string as a parameter and simply splits it into collection of tokens. It depends on implementation, how it will be done and how we will determine tokens in string, but interface we have.
Then we need somehow build an expression. Expression is an interface that defines formula, that is needed to be calculated so it have only one method which computes it. To build that expression we need to formulate instance of type ExpressionTreeBuilder, that will build expression for us. Thus we have these 4 main interfaces.
If we think ahead, we will realize one problem. With these 4 interfaces we can’t create a flexible mechanism for “making decisions” based on concrete token (for example what to do with operation and what to do with number). Of course we can take token instance and then through many if-then-else statements check token instanceof <TokenInheritor> but it is not TRUE OOP way so we introduce new interface:
public interface TokenVisitor {
//overloading for concrete tokens
}
And change Token interface:
public interface Token {
void accept(TokenVisitor visitor);
}
OK, I know that it can be a pain. Especially when you have many implementations of tokens and for each you need to define visit() overloading method in TokenVisitor, but first of all it is object way to workaround if-then-else statements and, come on, we have Java 8 so we can reduce number overloads by more specific interfaces and overriding visit method in sub interfaces declaring them default.
Types of tokens
We will define one token’s subinterface for each type of token. Currently we need to support 3 types of tokens: Number, Operator and Bracket. Let’s define each (for Bracket we also need to override accept method, but I forgot):
public interface Bracket extends Token {
boolean isOpen();
boolean isPairFor(Token another);
}
public interface Number extends Token {
int get();
@Override
default void accept(TokenVisitor visitor) {
visitor.visit(this);
}
default Expression toExpression() {
return this::get;
}
}
public interface BinaryOperator extends Token {
int priority();
Expression toExpression(Expression left, Expression right);
@Override
default void accept(TokenVisitor visitor) {
visitor.visit(this);
}
}
Bracket: is an interface that defines brackets in expression so it have two additional methods. First one says is it open of close bracket and second one answers does it pair for another bracket. For example “(” is a pair for “)”, but not for “]”
Operation: as I too weak and didn’t wanted to support unary operators and right associative operators, I defined only one interface BinaryOperator that are used for left associative binary operators (+,-,/,* and e.t.c). So as operators are connectors for expressions to build more global expressions, this interface have method toExpression that takes two expressions (left operand and right operand) as parameters and build operation expression as result. Next it has method to determine priority operator which is helpful for bracket placement and expression computing order. And finally as all three interfaces it implements accept method as default interface method (to avoid many overriding in each concrete implementation)
Number: is a token to represent a natural number (since we don’t support unary operations yet) so it have method get to receive actual natural number. Then we need to define toExpression parameterless method which uses just method reference to get and also here we implement visit method.
How will look calculator with these interfaces
First of all assume that users will enter expression in user-friendly format: infix notation. An we will build implementation, that will use shunting yard algorithm for converting infix notation to reverse polish notation.
To do that we need to define one additional interface Notation that will convert infix to it implementation. In our case it will have only one implementation ReversePolishNotation.
For building expression tree we will use rearranged tokens in a reverse polish notation. There is pretty simple algorithm to do that.
Get all together
So we have defined all interfaces and reveal some realizations, now we can construct class that will do all this stuff for us. It will be called Calculator and will have only one method public int calculate(String exprStr). Thus, it will be our facade, since it provides simpler API. Now instead of writing something like that everytime:
Lexer lexer = new TrimStringLexer(new ExpressionLexer(grammarVocabulary));
Collection<Token> tokens = lexer.parse(exprStr);
Collection<Token> rndTokens = new ReversePolishNotation()
.fromInfix(tokens);
return new RndExpressionBuilder(rndTokens)
.buildExpr()
.compute();
We will write:
calculator.calculate(exprStr)
In fact our calculator uses grammar to parse and calculate expression from String, so in realization we must define a vocabulary: number of reserved tokens (operations and brackets) that will be defined by Lexer as valid tokens. With numbers they will formulate our grammar. To define it in a user-friendly manner we will create builder for our calculator:
public static final class Builder {
private Collection<BinaryOperator> operators = emptyList();
private Collection<Token> brackets = new ArrayList<>();
public Builder operators(BinaryOperator... operators) {
this.operators = asList(operators);
return this;
}
public Builder withBrackets(Bracket open, Bracket close) {
this.brackets.addAll(asList(open, close));
return this;
}
public Calculator build() {
Collection<Token> vocabulary =
Stream.concat(operators.stream(), brackets.stream())
.collect(Collectors.toSet());
return new Calculator(vocabulary);
}
}
Additionally you can implement bunch of static factory methods to provide set of predefined calculators. For example:
public static Calculator elementaryMathematicalOperationCalculator() {
return new Calculator.Builder()
.withBrackets(
RoundBracket.OPEN,
RoundBracket.CLOSE)
.operators(
new SumOperator(),
new MinusOperator(),
new MultOperator(),
new DivideOperator())
.build();
}
public static binaryBooleanOperationCalculator() {
Return new Calculator.Builder()
.withBrackets(
RoundBracket.OPEN,
RoundBracket.CLOSE)
.operators(
new AndOperator(),
new OrOperator(),
new ImpliesOperator(),
new EqualOperator(),
new XorOperator())
.build();
}
There is still something to work
Full code you can see if you go through this link.
Despite the fact that we turn 3 classes into more than 10, there are still much things we can improve:
- Provide validation of “evaluating” tokens. Suppose I want to use only booleans in expression, or booleans and numbers, or my symbols with numeric analogy (for example, roman numerals)
- Provide a way to mixing “evaluating” tokens. Suppose I want to use booleans and numbers, but some expressions can use only numbers and some only booleans as their arguments. And some can use both.
- Provide a way to make working with notations more flexible. Currently we rely on the fact that the user enters expression in infix notation. But user can writes expressions also in prefix or suffix notations. We need to handle them to, and as a result get rid of hard coded algorithm. (Also “auto-validation” of expression in one of each notations will be provided automatically)
If you can add ideas to this list feel free to write them in comments below.
Wait wait wait! What does Android do with it now?
Although Android applications mostly procedure and android performance tips remind us to not use much objects, we need to keep in mind that we working on Java and we can use many advantages of OOP.
Still we need to remember Joshua Bloch phrase from Java Concurrency in Practice book:
First make it right, then make it fast — if it is not already fast enough
Few last words
If you think it is interesting experiment to grow small class into big system for providing many cases of calculating mechanism, then feel free to add comments with your ideas or like this post. And I will write part 2.