π§βπ¨ A
Design Pattern
is ageneral
andrepeatable solution
to the commonly occurring problems. ADesign Pattern
can be also referred to as thebest practices
from theexperiences
.
In Software Designs
, there are considerable design patterns
that are commonly applied by developers such as:
Factory Method
Singleton
Adapter
Composite
Facade Design
Visitor
...
where these can be largely divided into three design types
:
Creational Design Patterns
Structural Design Patterns
Behavioural Design Patterns
Over the following sections, due to the time constraints, only a few patterns
specifically Singleton
, Factory
, and Adapter
, will be thoroughly examined.
π
Singleton
is apattern
that limits theinstantiation of a class
to asingle instance
.
Refactoring Guru (1) Available at here
Singleton
implies all threads
if under multi-threading context
, sharing a single instance
of a class
and hence:
memory allocations
resource management
instance consistency
Singleton
can be implemented via a few Java
components such as a private access modifier
, a static keyword
, and so on:
Singleton
public class Singleton {
private static Singleton singleton; // static
private Singleton() { // private constructor
}
public static Singleton getInstance() { // static method
if (singleton == null) {
sychronized (Singleton.class) {
if (singleton == null)
singleton = new Singleton();
}
}
return singleton;
}
}
Above Singleton Class
is a conventional approach to create an instance in the context of Singleton Pattern
, where in the client code
:
Client Code
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = new Singleton(); // prohibited
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println("singleton1 = " + singleton1); // singleton1 = com.example.demo.Singleton@1921994e
System.out.println("singleton2 = " + singleton2); // singleton2 = com.example.demo.Singleton@1921994e
}
its access to the Singleton instance
is only viable through the getInstance() method
.
Above Singleton pattern
, however, heavily relies on the concrete class instantiation
so that it has a high likelihood of violating OCP
and DIP
from the SOLID principles
.
Furthermore, followed by its innate nature, where the identical class instance
is shared under the multi-threading context
, careful control over a class
to ensure the thread-safe
becomes necessary. Hence, Singleton
incurs:
tests
(no constructor
)dependency
on a concrete class
thread-unsafety
under multi-threading environment
These can be almost all addressed followed by a new Singleton Pattern
implemented via Map
or HashTable
data structure
, where it saves an instance
and returns the instance
whenever necessary. Popular usecases of this improved Singleton
are Threadpool
, Database Connection Pool
, Spring DI or Bean Container
, and etc
.
Singleton (HashMap)
public class Container {
private static Map<String, Object> map = new HashMap<>();
public Object get(String key) {
return map.get(key);
}
public void put(String key, Object value) {
map.put(key, value);
}
}
π
Factory Method
is apattern
thatencapsulates
theinstance creational logic
.
Refactoring Guru (2) Available at here
In Factory Method Pattern
, methods
specifically create instances
, where it replaces the direct new
object calls
to the method invockation
and thereby becoming the Factory Method Pattern
. The pattern
extensively applies abstraction
or interfaces
strictly related to the instance creational logics
, where the actual implementation is preserved for the concrete classes
.
Factory Method Pattern
:
code reusability
couplings
OCP
and SRP
Factory Method Pattern
can be implemented via abstract classes
and interfaces
, abstract methods
, and concrete classes
:
Factory Method
// Logistics class
public abstract class Logistics {
public final Transport planDelivery() {
Transport transport = createTransport();
transport.deliver();
return transport;
}
protected abstract Transport createTransport() {
}
}
// Transport interface
public interface Transport {
void deliver();
}
In the above Logistics class
, a creational method
, createTransport() method
is declared as protected abstract
, where it implies that the actual implementation falls to the concrete classes
. Client
, simply by calling planDelivery()
can acquire the Transport instances
.
Transport interface
is the interface
for the Trasport classes
where these classes
implement this interface
. The interface
is replaceable with an abstract class
if necessary.
Concrete Class
// Lorry class
public class Lorry implements Transport {
public void deliver() {
System.out.println("DELIVERING BY A LORRY");
}
}
// Ship class
public class Ship implements Transport {
public void deliver() {
System.out.println("DELIVERING BY A SHIP");
}
}
// LandLogistics class
public class LandLogistics extends Logistics {
public Transport createTransport() {
return new Lorry();
}
}
// SeaLogistics class
public class SeaLogistics extends Logistics {
public Transport createTransport() {
return new Ship();
}
}
Client
public class Client {
public static void main(String[] args) {
Logistics seaLogistics = new SeaLogistics();
Logistics landLogistics = new LandLogistics();
seaLogistics.planDelivery(); // "DELIVERING BY A SHIP"
landLogistics.planDelivery(); // "DELIVERING BY A LORRY"
}
}
Finally at the client-level
, just simply calling on planDelivery()
, all the instance creational logic
becomes executed behind the scene, followed by the instantiation encapsulation
.
Factory Method
, however, can generate these negative impacts to the code:
factory classes
subclasses
code complexity
π
Adapter
is astructural design pattern
that enables collaboration betweenobjects
of incompatibletypes
.
Refactoring Guru (3) Available at here
Adapter pattern
works by wrapping an object
as an instance variable
and inheriting
or implementing
the class
or interface
that needs compatibility. The Adapter
then implements the logic needed to make the wrapped object
compatible with this class
or interface
.
Adapter
maintains:
SRP
interface
or data conversion code can be separated from the core business logic
of the programme
.OCP
adapter
through the client interface
without modifying existing class code
.Adapter
can be implemented as below:
Adapter
// Adapter
public class AAdapter extends A {
private B b;
public AAdapter(B b) {
this.b = b;
}
@Override
public int multiply() {
return 3 * b.getB();
}
}
// Class A
public class A {
private int a;
private int b;
public A(int a, int b) {
this.a = a;
this.b = b;
}
public int multiply() {
return a * b;
}
}
// Class B
public class B {
private int b;
public B(int b) {
this.b = b;
}
public int getB() {
return b;
}
}
class extension
can be replaced with interface implementation
.
Client
public class Client {
public static void main(String[] args) {
A a = new A(3, 5);
AAdapter b = new AAdapter(new B(5));
System.out.println(a.multiply());
System.out.println(b.multiply());
}
}
class B
which was not compatible to class A
is now compatible via AAdapter class
where it implements all the relevant business logic
that class A
performs.
Good use cases of Adapter
in the practice could be JDBC
and JRE
where given the interfaces
, the actual implementation falls to the concrete classes
and these implementations always run.
Adapter
, however, can result in:
code complexity
classes
.where its application may have to be carefully examined.
Refactoring Guru (1)
Refactoring Guru (2)
Refactoring Guru (3)
μ€νλ§ μ
λ¬Έμ μν μλ° κ°μ²΄ μ§ν₯μ μ리μ μ΄ν΄