Comprehensive Guide to Java Design Patterns, Distributed Systems, and Core Algorithms
This article provides an extensive overview of common Java design patterns—including Singleton, Factory, Proxy, Observer, and Decorator—along with detailed code examples, followed by discussions of distributed system concepts, CAP theorem, BASE theory, and fundamental algorithms such as sorting and binary search.
This article introduces several widely used design patterns in Java, explaining their purpose and typical use cases. It covers Singleton, Factory, Proxy (static, dynamic, and CGLIB), Observer, and Decorator patterns, describing how each solves common software design problems.
Static Proxy Example :
package com.test.proxy;
public interface Target {
public String execute();
}
public class TargetImpl implements Target {
@Override
public String execute() {
System.out.println("TargetImpl execute!");
return "execute";
}
}
public class Proxy implements Target {
private Target target;
public Proxy(Target target) { this.target = target; }
@Override
public String execute() {
System.out.println("perProcess");
String result = this.target.execute();
System.out.println("postProcess");
return result;
}
}
public class ProxyTest {
public static void main(String[] args) {
Target target = new TargetImpl();
Proxy p = new Proxy(target);
String result = p.execute();
System.out.println(result);
}
}Dynamic Proxy Example (JDK) :
package com.test.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public interface Target {
public String execute();
}
public class TargetImpl implements Target {
@Override
public String execute() {
System.out.println("TargetImpl execute!");
return "execute";
}
}
public class DynamicProxyHandler implements InvocationHandler {
private Target target;
public DynamicProxyHandler(Target target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("========before==========");
Object result = method.invoke(target, args);
System.out.println("========after===========");
return result;
}
}
public class DynamicProxyTest {
public static void main(String[] args) {
Target target = new TargetImpl();
DynamicProxyHandler handler = new DynamicProxyHandler(target);
Target proxySubject = (Target) Proxy.newProxyInstance(
TargetImpl.class.getClassLoader(),
TargetImpl.class.getInterfaces(),
handler);
String result = proxySubject.execute();
System.out.println(result);
}
}CGLIB Proxy Example :
package com.test.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(">>>>MethodInterceptor start...");
Object result = proxy.invokeSuper(obj, args);
System.out.println(">>>>MethodInterceptor ending...");
return "result";
}
}
public class CglibTest {
public static void main(String[] args) {
System.out.println("***************");
Target target = new Target();
CglibTest test = new CglibTest();
Target proxyTarget = (Target) test.createProxy(Target.class);
String res = proxyTarget.execute();
System.out.println(res);
}
public Object createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new MyMethodInterceptor());
return enhancer.create();
}
}The article also presents three typical Singleton implementations (lazy synchronized, eager, and double‑checked locking with volatile ), showing how to guarantee a single instance in multithreaded environments.
Factory Pattern Example :
public interface Shape { void draw(); }
public class Rectangle implements Shape { public void draw() { System.out.println("Inside Rectangle::draw() method."); } }
public class Square implements Shape { public void draw() { System.out.println("Inside Square::draw() method."); } }
public class Circle implements Shape { public void draw() { System.out.println("Inside Circle::draw() method."); } }
public class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType == null) return null;
shapeType = shapeType.toLowerCase();
switch (shapeType) {
case "circle": return new Circle();
case "rectangle": return new Rectangle();
case "square": return new Square();
default: return null;
}
}
}
public class FactoryPatternDemo {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
Shape shape2 = shapeFactory.getShape("RECTANGLE");
shape2.draw();
Shape shape3 = shapeFactory.getShape("SQUARE");
shape3.draw();
}
}Observer Pattern Example :
public class Subject {
private List
observers = new ArrayList<>();
private int state;
public int getState() { return state; }
public void setState(int state) { this.state = state; notifyAllObservers(); }
public void attach(Observer observer) { observers.add(observer); }
public void notifyAllObservers() { for (Observer o : observers) o.update(); }
}
public abstract class Observer { protected Subject subject; public abstract void update(); }
public class BinaryObserver extends Observer {
public BinaryObserver(Subject subject) { this.subject = subject; subject.attach(this); }
@Override public void update() { System.out.println("Binary String: " + Integer.toBinaryString(subject.getState())); }
}
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
new BinaryObserver(subject);
new HexaObserver(subject);
new OctalObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}The Decorator pattern is also illustrated with a Shape interface, concrete shapes, an abstract ShapeDecorator , and a RedShapeDecorator that adds a red border before delegating to the wrapped shape.
Beyond design patterns, the article discusses distributed system fundamentals: the motivation for distributing workloads, the difference between horizontal scaling and vertical splitting, the concepts of clusters, micro‑services, multi‑threading, and high‑concurrency handling.
It explains the CAP theorem, presenting the three properties (Consistency, Availability, Partition tolerance) in a table and describing why a system can satisfy at most two of them when a network partition occurs. The text also clarifies common misconceptions about “3‑choose‑2”.
The BASE theory (Basically Available, Soft state, Eventually consistent) is introduced as a practical response to CAP, highlighting how modern large‑scale services trade strict consistency for higher availability and how techniques such as database sharding and eventual consistency are applied.
Algorithmic fundamentals are covered next. Simple sorting algorithms (bubble sort, selection sort, quick sort) are described with Java implementations, including time‑complexity analysis. Recursive examples such as factorial calculation and the Fibonacci sequence are shown. Binary search is presented with both iterative logic and a discussion of overflow‑safe midpoint calculation.
Finally, the article introduces the Consistent Hash algorithm, explaining its ring‑based mapping, how virtual nodes improve balance, and how the algorithm gracefully handles node addition or failure. A Java implementation of a generic ConsistentHash class with methods to add, remove, and locate nodes is provided.
Overall, the document serves as a comprehensive reference for Java developers seeking to understand core design patterns, distributed architecture principles, consistency models, and basic algorithmic techniques.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.