Java — good practices and recommendations: Design patterns

Java — good practices and recommendations: Design patternsMartin JonovskiBlockedUnblockFollowFollowingFeb 22Originally published on culture.

singular.

ukDesign patterns are common solutions to problems that occur frequently during software development.

These solutions offer elegant and in most cases, the most effective way of solving different problems related with object creation, resource allocation, simplifying code etc.

The context in which they are given needs to be maintained, while the solution itself needs to be customised, according to the business logic.

Design patterns are separated in three categories:creational, offer solutions for solving different problems that occur during object creationstructural, offer solution to instantiation problems by finding ways how the classes can be composed in larger structuresbehavioral, offer solutions to problems that happen in the communication between separate parts of the code.

Some of the design patterns can actually be used as guidelines during the design of the architecture like it’s the case with the DAO design pattern.

Software architectures usually have three layers: the endpoints for the app, the service layer i.

e.

the business logic and the data layer.

The data layer is implemented by using DAO design pattern (Data Access Object) which separates the part that communicates with the database from the rest of the application.

The DAO pattern defines the CRUD (create,read,update,delete) operations for all of the entities.

By adding named/native queries, that will be used frequently for the entity itself, the persistence layer can be completely separated.

public interface DAO<T,E extends Serializable>{ public T save(T object); public Boolean delete(T object); public T update(T object); public T find(E id);}The interface for the DAO itself defines only the operations that need to be specified in the implementation.

The implementation itself uses generic types with provided entity manager.

The entity manager is a class that takes care of all the persistence operations in the app and can be obtained using the application context.

public abstract class GenericDAO<T,E> implements DAO<T,E>{ @PersistenceContext private EntityManager entityManager; public T save(T object){ return entityManager.

persist(object); }public T find(E id){ return entityManager.

find(T.

class,id); }public Boolean delete(T object){ return entityManager.

remove(object); }public T update(T object){ return entityManager.

merge(object); }}The example provided requires basic understanding of Hibernate and persistence with Java.

Hibernate is an ORM tool (object relational mapping) which creates tables from java code and uses HQL (hibernate query language) for query typing and execution.

@Entity@Table(name="person")@NamedQueries( { @NamedQuery(name=Person.

GET_PERSON_BY_AGE,query="Select * from User u where u.

age>:age") })public class Person{ public static final String GET_PERSON_BY_AGE = "Person.

getPersonByAge"; @Id @GeneratedValue( strategy = GenerationType.

IDENTITY) @Column(name="id",unique="true") public int id; @Column(name="name") public String name; public Person(String name){ this.

name=name; } //getters and setters.

}The DAO class which will be used for the entity extends the generic DAO in which are implemented the basic CRUD operations, so we only need to add the specific queries that will be used.

public PersonDAO extends GenericDAO<Person,Integer>{public List<Person> getPersonByAge(int age){ Query q=entityManager.

createNamedQuery(Person.

GET_PERSON_BY_AGE, Person.

class); q.

setParameter("age",5); return (List<Person>)q.

getResultList(); }}Pros:offers both logical and physical separation of the code from the business logic, which is easy to implement;the DAO classes can be expanded easily with cache strategies, that can be implemented in the methodsi;If the DAO class is declared as an EJB, each method can specify the transactional attribute in order to control the scope of the underlying transaction;Cons:it creates an overhead in the connection with the database, because DAO objects generally handle the whole object.

This is an advantage when it comes to the save operation because the whole object is stored at once but the read can be an expensive operation;to avoid this, native or named queries can be used in order to retrieve smaller portions of the object, according to the business needs;DAO pattern should not be used in small apps, because its advantages would be minor and the code will become more complex;Design patterns are often used to simplify big chunks of code or even to hide specific implementations from the application flow.

The perfect example for these kind of problems is the factory design pattern, which is a creational design pattern that offers object creation without having to specify the exact class of the objects.

It proposes using a super class and multiple sub-classes, which inherit from the super class.

During execution only the super class is used and its value varies depending on the factory class.

public class Car{ private String model; private int numberOfDoors; public Car(){ } public String getModel(){ return this.

model; } public int getNumberOfDoors(){ return this.

numberOfDoors; } public void setModel(String model){ this.

model = model; } public void setNumberOfDoors(int n){ this.

numberOfDoors = n; }}public class Jeep extends Car{ private boolean land; public Jeep(){ } public void setLand(boolean land){ this.

land=land; } public boolean getLand(){ return this.

land; }}public class Truck extends Car{ private float capacity; public Truck(){ } public void setCapacity(float capacity){ this.

capacity=capacity; } public float getCapacity(){ return this.

capacity; }}In order to use this pattern, we need to implement a factory class, which will return the correct sub-class for a given input.

The java classes above specify one super class (Car.

java) and two sub-classes (Truck.

java and Jeep.

java).

In our implementation we instantiate an object of the Car class and depending on the arguments, the factory class will decide whether it’s a Jeep or a Truck.

public class CarFactory{ public Car getCarType(int numberOfDoors, String model,Float capacity, Boolean land){ Car car=null; if(capacity!=null){ car=new Jeep(); //implement setters }else{ car=new Truck(); //implement setters } return car; }}During runtime, the factory class instantiates the correct sub-class, considering the input.

public static void main(String[] args){ Car c=null; CarFactory carFactory=new CarFactory(); c = carFactory.

getCarType(2,"BMW",null, true);}Abstract factory design pattern works in the same way but instead of a regular class, the parent class is an abstract class.

Abstract classes are generally faster and easier to instantiate, because they are basically empty.

The implementation is the same only the parent class is declared as abstract with all its methods and the sub-classes need to implement the behaviour of the methods declared in the abstract class.

The example for the Abstract factory is created using interfaces.

The same can be done by simply replacing the interface with an abstract class and instead of implementing the interface, the sub-classes will extend the abstract class.

public interface Car { public String getModel(); public Integer getNumberOfDoors(); public String getType();}public class Jeep implements Car{ private String model; private Integer numberOfDoors; private Boolean isLand; public Jeep() {} public Jeep(String model, Integer numberOfDoors, Boolean isLand){ this.

model = model; this.

numberOfDoors = numberOfDoors; this.

isLand = isLand; } public String getModel(){ return model; } public Integer getNumberOfDoors() { return numberOfDoors; } public Boolean isLand() { return isLand; } public void setLand(Boolean isLand) { this.

isLand = isLand; } public void setModel(String model) { this.

model = model; } public void setNumberOfDoors(Integer numberOfDoors){ this.

numberOfDoors = numberOfDoors; } public String getType(){ return "jeep"; }public class Truck implements Car{ private String model; private Integer numberOfDoors; private Integer numberOfWheels; public Truck(String model, Integer numberOfDoors, Integer numberOfWheels) { this.

model = model; this.

numberOfDoors = numberOfDoors; this.

numberOfWheels = numberOfWheels; } public Truck() {} public String getModel() { return model; } public Integer getNumberOfDoors() { return numberOfDoors; } public Integer getNumberOfWheels() { return numberOfWheels; } public void setNumberOfWheels(Integer numberOfWheels) { this.

numberOfWheels = numberOfWheels; } public void setModel(String model) { this.

model = model; } public void setNumberOfDoors(Integer numberOfDoors) { this.

numberOfDoors = numberOfDoors; } public String getType(){ return "truck"; }}public class CarFactory { public CarFactory(){} public Car getCarType(String model,Integer numberOfDoors, Integer numberOfWheels, Boolean isLand){ if(numberOfWheels==null){ return new Jeep(model,numberOfDoors,isLand); }else{ return new Truck(model,numberOfDoors,numberOfWheels); } }}The only difference is that the methods declared in the abstract class must be implemented in each of the sub-classes.

The factory and the main method stay the same in both cases.

public class CarMain { public static void main(String[] args) { Car car=null; CarFactory carFactory=new CarFactory(); car=carFactory.

getCarType("Ford", new Integer(4), null, new Boolean(true)); System.

out.

println(car.

getType()); }}The output is the following:Pros:it allows loose coupling and a higher level of abstraction;it is scalable and can be used to separate certain implementations away from the application;the factory class can be reused after creating new classes lower in the hierarchy and the code will still work, by simply adding the appropriate instantiation logic;unit testing, because it is simple to cover all of the scenarios by using the super class;Cons:it is often too abstract and hard to understand;it is really important to know when to implement the factory design pattern, because in small applications it just creates an overhead (more code) during object creation;the factory design pattern must maintain its context i.

e.

only classes that inherit from the same parent class or implement the same interface can be applicable for the factory design pattern.

The singleton design pattern is one of the most famous and controversial creational design patterns.

A singleton class is a class which will be instantiated only once during the lifetime of the application i.

e.

there will be only one object shared for all the resources.

The singleton methods are thread safe and may be used by multiple parts of the application at the same time, even when they access a shared resource within the Singleton class.

The perfect example on when to use a singleton class is a logger implementation, in which all the resource write in the same log file and is thread safe.

Other examples include database connections and shared network resources.

Also, whenever the application needs to read a file from the server it is convenient to use a Singleton class, because in that case only one object of the application will be able to access the files stored on the server.

Besides the logger implementation, the config files are another example where the use of a singleton class is efficient.

In java, a singleton is a class with a private constructor.

The singleton class keeps a field with the instance of the class itself.

The object is created using the get method, which calls the constructor if the instance hasn’t been initiated yet.

Earlier, we mentioned that this pattern is the most controversial and it is so because of the multiple implementations for the instance generation.

It must be thread safe, but it also must be efficient.

In the examples we have two solutions.

import java.

nio.

file.

Files;import java.

nio.

file.

Paths;public class LoggerSingleton{private static Logger logger; private String logFileLocation="log.

txt"; private PrintWriter pw; private FileWriter fw; private BufferedWriter bw; private Logger(){ fw = new FileWriter(logFileLocation, true); bw = new BufferedWriter(fw) this.

pw = new PrintWriter(bw); } public static synchronised Logger getLogger(){ if(this.

logger==null){ logger=new Logger(); } return this.

logger; } public void write(String txt){ pw.

println(txt); }}Because the log file will be accessed frequently.

the print writer using a buffered writer makes sure that the file won’t be opened and closed multiple times.

The second implementation includes a private class which holds a static field of the instance of the Singleton class.

The private class can only be accessed within the singleton class i.

e.

only from the get method.

public class Logger{ private static class LoggerHolder(){ public static Singleton instance=new Singleton(); } private Logger(){ // init } public static Logger getInstance(){ return LoggerHolder.

instance; }}The singleton class then can be used from any other class inside the app:Logger log=Logger.

getInstance();log.

write("something");Pros:the singleton class is instantiated only once in the life cycle of the app and it can be used as many times needed;the singleton class allows thread safe access to shared resources;the singleton class cannot be extended and if it is implemented correctly i.

e.

the get method should be synchronised and static, it is thread safe;it is recommended to create an interface first and then design the singleton class itself, because it is easier to test the interface;Cons:problems during testing, when the singleton class accesses a shared resource and the execution of the tests is important;the singleton class also hides some of the dependencies in the code i.

e.

creates dependencies that are not explicitly created;the problem with using singleton without a factory pattern, is that it breaks the single responsibility principle, because the class is managing its own life cycle;The builder pattern is also a creational pattern which allows incremental creation for complex objects.

It is recommended to use this pattern when the field setting demands complex operations or simply the field list is simply too long.

All of the fields for the class are kept in a private inner classpublic class Example{ private String txt; private int num; public static class ExampleBuilder{ private String txt; private int num; public ExampleBuilder(int num){ this.

num=num; } public ExampleBuilder withTxt(String txt){ this.

txt=txt; return this; } public Example build(){ return new Example(num,txt); } } private Example(int num,String txt){ this.

num=num; this.

txt=txt; }}In real cases, the list of arguments will be longer and some of the arguments for the class can be calculated based on other input.

The builder class has the same fields as the class itself and it must be declared static in order to be accessed without having to instantiate an object of the holder class (in this case Example.

java).

The implementation given above is thread safe and can be used in the following way:Example example=new ExampleBuilder(10).

withTxt("yes").

build();Pros:the code is more neat and reusable if the number of arguments in the class is bigger than 6 or 7;the object is created after setting all the needed fields and only fully created objects are available;the builder pattern hides some of the complex calculations inside the builder class and separates it from the application flow;Cons:the builder class must contain all the fields from the original class, so that can take a little more time to develop compared to using the class alone;Observer design pattern is a behavioural design pattern which observes certain entities and handles the changes by propagating them to the concerned parts of the application.

Each container can offer a different implementation for different design patterns and the observer pattern has an implementation in java using the interface Observer for the class which will be affected by the changes in the observer class.

On the other side, the observed class needs to implement the Observable interface.

The Observer interface has only the update method but it has been deprecated in Java 9, because it is not recommended for usage due to its simplicity.

It doesn’t offer details about what has changed and simply finding the changes in bigger objects can be a costly operation.

The recommended alternative is the PropertyChangeListener interface which can specify the actions following the update.

.. More details

Leave a Reply