All GoF patterns have a good purpose and solve major problems of object-oriented design, but some patterns are most commonly used in the Java and Java EE ecosystem. In this book, these patterns are treated as basic design patterns because they are most commonly used to implement solutions on Java's APIs, frameworks, and algorithms. Consequently, understanding these patterns will help us to understand these APIs, frameworks, and algorithms, and we'll, in turn, be able to create a better solution using Java. These patterns are Singleton, Abstract Factory, Facade, Iterator, and Proxy.
Understanding the basic design patterns of the Java world
Explaining Singleton
In a software project, in some solutions, we may want to ensure that a class has only one instance of an object throughout the project and that this object is accessible at any point in the project. Creating a global instance or static instance will not ensure that this class will not be used at another point in another instance. The best way to solve this is by using the Singleton pattern, which ensures that there is only one instance of a class in the entire project. In the following diagram, we are showing the structure of Singleton and how it is designed:
Here, we have one class called Singleton which has a private constructor, as well as a reference variable of Singleton and a method for returning its unique instance. A good example of an application is a situation in which we want to create a class responsible for application configurations (paths to some resource, parameters to access filesystems, behaviors of the environment). Often, the application has some configurations and we need a class to represent these application configurations. Thus, this class of application configuration doesn't need various instances, but only one instance.Â
Another application of Singleton is when we want to create an Abstract Factory that will be explained in the following subsection. Generally, we will have only one Abstract Factory throughout the application. With this, we can use a Singleton to guarantee that we will have only one instance of Abstract Factory.
This pattern is often used in frameworks and APIs, but it is common for this pattern to be found in the code of projects, mainly on Java EE.
The use of the Singleton pattern can be a good practice depending on the scenario, but depending on the scenario the use of Singleton can be a bad practice. The Singleton should not be used when the object is stateful and maintain a state, because with Singleton the same instance of the object is shared by all processes of application and if some process updates a state of this object all processes of application will be impacted by this update. Furthermore, we can have a problem with the concurrent update of the state of a Singleton.
Explaining Abstract Factory
Sometimes, we need to create a family of objects in a project. Imagine that we have an e-commerce and we have various kinds of products such as cell phones, notebooks, and tablets. These products are objects of the same family. If we create objects throughout a software, we will face problems if we then need to modify the initialization process of this object.
Using Abstract Factory will help us to solve problems including a system which should be independent of how its products are created, a system that should use one of the multiple families of products, and a system that should work with objects which are designed to be used together. Using this pattern will be beneficial as it isolates concrete classes. This means that with this pattern, we can control which class of objects that can be initiated on software. Furthermore, it permits the exchange of products easily and provides consistency among products.
The Abstract Factory pattern creates a single point of creation for objects and if we need to change the algorithm of object creation, we need only modify the concrete factory. In the following diagram, you can see the structure of Abstract Factory and how it is designed:
In our example, the Abstract Factory's structure has three main classes—AbstractFactory, Product, and Sale. The concrete classes of AbstractFactory are CellPhoneFactory, NotebookFactory, and TabletFactory. CellPhoneFactory is a concrete class responsible for creating the concrete classes CellphoneProduct and CellphoneSale, NotebookFactory is a concrete class responsible for creating the concrete classes NotebookProduct and NotebookSale, and the TabletFactory is a concrete class responsible for creating the concrete classes TabletProduct and TabletSale. A Client is a class responsible for using AbstractFactory to create AbstractProduct and AbstractSale. The concrete factory is created at runtime and it then creates the concrete product and sale.
The Abstract Factory pattern is sometimes used with another pattern such as Singleton, which we described earlier. Abstract Factory is a single point of creation, and often we need only one instance of it in an entire system. With this, using a Singleton pattern can help us create a design better and more efficiently.
This pattern is often used in frameworks and APIs that have a difficult creation process for an object, such as connections or sessions.
Explaining Facade
Projects can sometimes turn out to be very complex and big, making them difficult to design and organize. To solve this, a great solution is to break a system into subsystems (divide and conquer) and make them less complex and better organized.
The Facade pattern creates a higher-level interface to hide a complexity of a set of interfaces in a subsystem. This pattern reduces the complexity and coupling, minimizing communication and dependencies between subsystems. In the following diagram, you can see the structure of Facade and how it is designed:
In the preceding diagram, we can see the Facade pattern encapsulating all of the calls to subsystems and hiding these calls from the client. The system has one interface, Facade, and the client calls this interface in order to call subsystems. Thus, clients does not call the subsystems directly. With this solution, the client doesn't need to know about the subsystem and its complexity.
This pattern is often used in projects and systems that have high complexity and need to be broken down into subsystems.
Explaining Iterator
Imagine that we want a way to access elements of an aggregate object sequentially without exposing its internal structure. The Iterator pattern does just that.
The Iterator pattern is responsible for sequentially accessing the aggregate object and defining an interface to access the elements without exposing the internal structure. This interface doesn't put a new element on the aggregate object, but simply reads elements to it. In the following diagram, you can see the structure of an Iterator and how it is designed:
In the preceding diagram, we can see the Aggregate and Iterator interfaces with their concrete subclasses. The client is the class that uses the Iterator to access elements of Aggregate.
This pattern is used on Java collections such as list, deque, and set. Understanding this pattern will help you to understand Java collections.
Explaining Proxy
Sometimes, creating a new object can be a big process and several rules can be involved in creating this object. Imagine that we want to create a list of objects, and these objects represent telecommunication equipment, which has a lot of calculus to generate the information of each object. As well as this, these objects will not be accessed at the same time but will be accessed on demand. A good strategy is to create each object when it is accessed, thereby minimizing the cost and time it takes to create all objects and only access some. The Proxy can help us to solve this.
The Proxy pattern is a pattern that surrogates an object instance (original object) to another object instance (Proxy object) that permitting access control to the original object. In the following diagram, you can see the structure of Proxy and how it is designed:
From the preceding diagram, we can see a structure of the Proxy pattern. If Subject is an interface that clients use to access object operations, then RealSubject is the class of the original object and Proxy is the class that works as a Proxy. Then, when the client accesses the object, they will access the Proxy object, and the Proxy object will then access the RealSubject object and return this object to the client.
This pattern is used in frameworks and APIs that implement JPA specification and object relational mapping (ORM).