If
statements are probably the most common thing that makes your code spaghetti. I’d like to share a few techniques that can help you make your code more readable.
Let’s examine the following example:
class Foo {
String getName(String input) {
if ( "a".equals(input) ) {
return "1";
} else if ( "b".equals(input) ) {
return "2";
} else if ( "c".equals(input) ) {
return "3";
} else if ( "d".equals(input) ) {
return "4";
}
}
}
Maps
One of the techniques is to use map instead of if statements.
class Foo {
private static final Map<String, String> NAMES = Map.of(
"a", "1",
"b", "2",
"c", "3",
"d", "4"
);
String getName(String input) {
return NAMES.get(input);
}
}
The above example is simply key-value pair mapping, but with help of basic functional interfaces you can use this also for a business logic.
@Slf4j
class Foo {
private static final Map<String, Consumer<String>> NAMES = Map.of(
"a", input- > log.info("business 1"),
"b", input -> log.info("business 2"),
"c", input -> log.info("business 3",
"d", nput -> log.info("business 4"
);
void doSomeStuff(String input) {
NAMES.get(input).accept(input);
}
}
“If Applicable” pattern - aka IF inversion of control
A really nice one. You should consider using it if you expect that “if” logic will grow up. It looks like this:
// (1)
interface LogicResolver {
void doLogic(Object... input);
// (2)
boolean isApplicable(Request input);
}
// (3)
class Foo implements LogicResolver {
@Override
public void doLogic(String input) {
// do some Foo stuff
}
@Override
public isApplicable(Request input) {
return request.getURI().startsWith("/foo")
}
}
class Bar implements LogicResolver {
@Override
public void doLogic(String input) {
// do some Bar stuff
}
@Override
public isApplicable(Request input) {
return request.getURI().startsWith("/bar")
}
}
// (4)
class SpringBootBean {
private final List<LogicResolver> resolvers;
SpringBootBean(List<LogicResolver> resolvers) {
this.resolvers = resolvers;
}
void handleRequest(Request request) {
LogicResolver resolver = resolvers.stream()
.filter(resolver -> resolver.isApplicable(request))
.findFirst()
.orElseThrow(IllegalStateException::new);
resolver.doLogic(request);
}
}
(1) -> you need common interface for a logic. Like "if body block"
(2) -> condition. Like "if condition statement"
(3) -> a few implementations. Like next "else ifs"
(4) -> example how you can use it in spring boot application
Above example is a Java example, but this pattern can be easily implement in Typescript, or Javascript. I’m using it in Angular as well.
Angular tip
A tricky part for angular is that you cannot inject list of “interfaces”. The solution is to use InjectionToken
.
export interface LogicResolver {
doLogic(Request request): void;
isApplicable(Request request): boolean;
}
export const LOGIC_RESOLVERS = new InjectionToken<LogicResolver[]>('LOGIC_RESOLVERS');
@NgModule({
declarations: [],
providers: [
{
provide: LOGIC_RESOLVERS,
useFactory: () => {
return [
new FooLogicResolver(inject(HttpClient)),
new BarLogicResolver(inject(HttpClient)),
];
},
},
],
imports: [],
exports: [],
})
export class FooModule {}
@Injectable()
export class WhateverService {
constructor(@Inject(LOGIC_RESOLVERS) private readonly resolvers: LogicResolver[]) {
}
handleRequest(request: Request): void {
const resolver = this.resolvers.filter(resolver => resolver.isApplicable(request));
if ( !resolver ) throw new Error(`Cannot find logic resolver for request ${request}`);
resolver.doLogic(request);
}
}