Modern Design Patterns In Java
Design patterns are a toolkit of tried and tested solutions to common problems in software design.
It is a common language for everyone to use to communicate ideas more efficiently.
Optional: Patterns and Anti-patterns
- null is a smell
- return empty collections or arrays, not nulls
What if we have to return a single value?
We can return Optional<T>
public class Sample {
public static Optional<String> getName() {
return Optional.empty();
}
}
How to get the value out from the optional?
public class Application {
Optional<String> result = getName();
log.info(result.orElse("not found"));
}
Avoid doing this. It defeats the purpose of using Optional in the first place, which is to avoid null pointer exceptions.
public class Application {
Optional<String> result = getName();
result.get();
}
Avoid using Optional as a parameter, Use overloading instead
// not easy to use
public void sendEmail(Optional<String> email) {
}
// client code
sendEmail(Optional.empty());
sendEmail(Optional.of("johndoe@gmail.com"));
// easier to use
public void sendEmail() {
}
public void sendEmail(String email) {
}
// client code
sendEmail();
General Guidelines
- If a method always returns a single value, don’t use Optional
- If a method may or may not return a single value, then use Optional
- If the result is a Collection, don’t use Optional, return empty collections
- Don’t use Optional as a parameter to methods. If needed, use overloading instead.
Factory method using default methods
Factory Method
- The factory is a method and does not sit in a class
- Relies on an interface or a derived class to provide the implementation, whereas the base class provides the common behaviour
interface Pet {}
class Dog implements Pet {}
class Cat implements Pet {}
interface Person {
// interface have no state
// private Pet pet;
Pet getPet();
default void play() {
log.info("playing with " + getPet());
}
}
class DogLover implements Person {
private Dog dog = new Dog();
public Pet getPet() {
return dog;
}
}
Execute around method Pattern
Suppose we have some resource and we need to release it after calling some methods on the resource.
class Resource {
public Resource() {
log.info("creating resource");
}
public void doSomething1() {
log.info("doing something 1");
}
public void doSomething2() {
log.info("doing something 2");
}
public void close() {
log.info("release external resource");
}
}
We need to remember to call the close method to avoid memory leaks.
Resource resource = new Resource();
resource.doSomething1();
resource.doSomething2();
resource.close(); // need to call this
What if there is an exception?
We can wrap it in a try, finally block
try {
Resource resource = new Resource();
resource.doSomething1();
resource.doSomething2();
} finally {
resource.close();
}
Or use Automatic Resource Management, but may still forget to wrap in try block
class Resource implements AutoCloseable {
}
try (Resource resource = new Resource()) {
}
We can use execute around method pattern
class Resource {
// make constructor private
private Resource() {
log.info("creating resource");
}
public void doSomething1() {
log.info("doing something 1");
}
public void doSomething2() {
log.info("doing something 2");
}
public void close() {
log.info("release external resource");
}
public static void use(Consumer<Resource> operation) {
Resource resource = new Resource(); // before
try {
operation.accept(resource);
} finally {
resource.close(); // after
}
}
}
class Application {
public static void main(String[] args) {
// client code
Resource.use(resource -> {
resource.doSomething1();
resource.doSomething2();
});
}
}
Real world application
Typically used in a transaction block,
Transaction.runInTransaction(tx -> {
...
});