Fundamentals 23 min read

Master Design Patterns: Real‑World Examples and Complete Java Implementations

This article introduces design patterns, lists the most common patterns such as Strategy, Factory, Singleton, Proxy, Factory Method, Observer, Template Method, and Adapter, and walks through concrete Java code examples, step‑by‑step explanations, and practical scenarios that demonstrate how each pattern solves specific design problems.

Architect
Architect
Architect
Master Design Patterns: Real‑World Examples and Complete Java Implementations

Design patterns overview

Design patterns are reusable solutions to recurring software‑design problems. When a pattern matches the problem context it can be applied in both classic object‑oriented design and Domain‑Driven Design (DDD).

1. Strategy pattern – discount calculation

Problem : A shopping mall offers three discount tiers (≥2000 → 20% off, 500‑1000 → 10% off, 0‑500 → 5% off). The calculation logic must vary with the purchase amount.

Solution :

Define a Strategy interface with String strategy() (identifier) and void algorithm() (calculation).

Implement three concrete strategies ( ConcreteStrategyA, ConcreteStrategyB, ConcreteStrategyC) that print a message indicating which strategy is executed.

Use an enum StrategySelector to map identifiers to names.

Create a StrategyRunner interface with void execute(String strategy). Its implementation builds a static list of all Strategy beans, converts it to a Map<String,Strategy>, and looks up the appropriate instance at runtime.

Register StrategyRunner as a Spring bean; Spring injects all Strategy implementations automatically.

Expose a REST endpoint /designPatterns/algorithm?strategy=… that calls strategyRunner.execute(...). A request with strategy=strategyA prints process with strategyA... on the console.

2. Simple factory (payment) pattern

Problem : An e‑commerce system must support multiple payment channels (Alipay, WeChat Pay, UnionPay) and be extensible for future channels.

Solution :

Define IPayment with Boolean pay(PaymentBody paymentBody).

Provide concrete implementations AliPay, WechatPay, UnionPay that log the payment method and return Boolean.TRUE.

Create PayStrategyEnum mapping a type (e.g., "ZFB", "WX", "UNION") to the fully‑qualified class name of the corresponding implementation.

Implement PaymentFactory.getPayStrategy(String type):

String value = EnumUtil.getFieldBy(PayStrategyEnum::getValue, PayStrategyEnum::getType, type);
return ReflectUtil.newInstance(value);

This uses EnumUtil (from Hutool) to read the enum and ReflectUtil to instantiate the class via reflection.

Wrap the strategy in a context object PaymentContext that delegates pay to the injected IPayment instance.

Provide a static handler PaymentStrategyHandler.pay(PaymentBody) that validates the type, obtains the strategy from the factory, builds the context, and executes the payment.

Expose a POST endpoint /designPatterns/pay that forwards the request body to PaymentStrategyHandler.pay.

3. Singleton pattern

Implementation using a lazy‑initialized static inner holder class ensures thread‑safety without explicit synchronization:

class Singleton {
    private Singleton() {}
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() { return SingletonInstance.INSTANCE; }
}

4. Proxy pattern – landlord & agency

Problem : Direct access to a landlord is inconvenient; a real‑world proxy (agency) can control access and add fees.

Solution : Subject defines void rentHouse(). HouseOwner implements Subject and prints a success message. HouseProxy also implements Subject, holds a HouseOwner instance, prints a fee message, then delegates to HouseOwner.rentHouse().

Client code uses the proxy:

Subject proxy = new HouseProxy();
proxy.rentHouse();

Output shows the agency fee followed by the landlord's message.

5. Factory Method pattern – network configuration services

Problem : Different product types (A, B, C, D) require distinct NetworkConfigCrudService implementations.

Solution :

Define NetworkConfigFactoryService with

NetworkConfigCrudService getSpecificService(String productType)

.

Implement NetworkConfigFactoryServiceImpl that injects four concrete services ( AServiceImplDServiceImpl) and selects one via a switch on productType.

Each concrete service implements NetworkConfigCrudService and returns a NetworkConfigVO.

Controller obtains the specific service from the factory (e.g., factoryService.getSpecificService("A")) and calls getNetwork(dto).

6. Observer pattern – weather‑station example

Problem : Multiple display elements must stay synchronized with weather data changes.

Solution : Subject defines registration, removal, and notification methods. Observer defines update(float temp, float humidity, float pressure). WeatherData implements Subject, stores a list of observers, and notifies them when measurements change. CurrentConditionDisplay implements Observer and a custom Display interface; it registers itself in the constructor, updates its internal state on update, and prints the current conditions.

Alternative implementation using Java's built‑in Observable and Observer is also shown.

7. Template Method pattern – house construction

Problem : The construction algorithm (foundation → pillars → walls → windows) must stay in a fixed order, while pillar and wall materials vary per house type.

Solution :

Abstract class HouseTemplate defines a final buildHouse() method that calls buildFoundation(), abstract buildPillars(), abstract buildWalls(), and buildWindows().

Concrete subclasses WoodenHouse, GlassHouse, and ConcreteHouse override buildPillars() and buildWalls() with type‑specific messages.

Client code creates each subclass and invokes buildHouse(), producing output that demonstrates the invariant order and the varying steps.

8. Adapter pattern – network‑cable to USB/Type‑C

Problem : A network cable (the Adaptee) has a connector incompatible with a computer’s USB/Type‑C port.

Solution : Adaptee provides void request() ("link network cable").

Target interface NetToUsb defines void handleRequest(). Adapter extends Adaptee and implements NetToUsb; its handleRequest() simply calls super.request().

Client Computer expects a NetToUsb instance and calls net(adapter). The main method creates an Adapter and passes it to Computer.net, resulting in the network‑cable message being printed.

Key takeaways

Each pattern demonstrates a concrete problem, the reasoning that leads to the chosen structure, and a complete Java implementation (including interfaces, concrete classes, enums, and Spring integration where relevant). The examples illustrate how patterns enforce invariants, promote extensibility, and decouple client code from concrete implementations.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsSoftware ArchitectureStrategy PatternProxyFactory PatternTemplate MethodSingletonAdapterObserver
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.