π§βπ¨ A
Design Patternis ageneralandrepeatable solutionto the commonly occurring problems. ADesign Patterncan be also referred to as thebest practicesfrom theexperiences.
In Software Designs, there are considerable design patterns that are commonly applied by developers such as:
Factory MethodSingletonAdapterCompositeFacade DesignVisitor...
where these can be largely divided into three design types:
Creational Design PatternsStructural Design PatternsBehavioural Design PatternsOver the following sections, due to the time constraints, only a few patterns specifically Singleton, Factory, and Adapter, will be thoroughly examined.
π
Singletonis apatternthat limits theinstantiation of a classto 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 allocationsresource management instance consistencySingleton 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 classthread-unsafety under multi-threading environmentThese 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 Methodis apatternthatencapsulatestheinstance 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 reusabilitycouplingsOCP and SRPFactory 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 subclassescode complexityπ
Adapteris astructural design patternthat enables collaboration betweenobjectsof 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 complexityclasses.where its application may have to be carefully examined.
Refactoring Guru (1)
Refactoring Guru (2)
Refactoring Guru (3)
μ€νλ§ μ
λ¬Έμ μν μλ° κ°μ²΄ μ§ν₯μ μ리μ μ΄ν΄