Master the Four Structural Design Patterns: Proxy, Decorator, Adapter, Facade with Spring Code Examples
This article explains the four essential structural design patterns—Proxy, Decorator, Adapter, and Facade—detailing their core concepts, real‑world Java/Spring use cases, code implementations, and interview‑focused comparisons to help developers master enterprise‑level architecture.
Why Structural Patterns Matter
Unlike Creational patterns (how objects are created) and Behavioral patterns (how objects interact), Structural patterns answer the question of how to compose objects like building blocks to form more powerful structures. The GoF catalog lists seven, but in enterprise development and interviews four dominate: Proxy, Decorator, Adapter, and Facade.
1. Proxy Pattern – The Soul of Spring AOP and RPC
Core Idea
Provide a proxy that controls access to another object, adding functionality without modifying the original code.
Anti‑example: Hard‑coded logging
public class UserServiceImpl implements UserService {
public User getUserById(Long id) {
long start = System.currentTimeMillis(); // 💩 non‑business logic intrusion
log.info("开始查询用户: {}", id);
User user = userDao.selectById(id); // real business
log.info("查询结束,耗时: {} ms", System.currentTimeMillis() - start);
return user;
}
}If a service has 100 methods, copying this logging code 100 times is impractical.
Static Proxy – Let the "stand‑in" handle the dirty work
// ① Real object – pure business logic
public class UserServiceImpl implements UserService {
public User getUserById(Long id) {
return userDao.selectById(id);
}
}
// ② Proxy object – adds all enhancement logic
public class UserServiceProxy implements UserService {
private UserService target; // holds reference to real object
public UserServiceProxy(UserService target) { this.target = target; }
@Override
public User getUserById(Long id) {
long start = System.currentTimeMillis();
log.info("[Proxy] 开始查询用户: {}", id);
User user = target.getUserById(id);
log.info("[Proxy] 查询结束,耗时: {} ms", System.currentTimeMillis() - start);
return user;
}
}Structure: proxy and real class implement the same interface; the proxy holds a reference to the real object and executes enhancement logic before delegating.
Static Proxy Pain Points
When UserService has 50 methods, the proxy must duplicate all 50 signatures.
Multiple services (e.g., OrderService, PayService) would require dozens of proxy classes.
Dynamic Proxy – The Foundation of Spring AOP and Dubbo
At runtime, reflection generates a proxy class, allowing a single interceptor to handle all methods.
JDK Dynamic Proxy : based on interfaces, uses Proxy.newProxyInstance() + InvocationHandler.
CGLIB Dynamic Proxy : based on subclassing, uses ASM bytecode generation; Spring falls back to it when the target class lacks an interface.
In practice developers annotate with @Aspect + @Around; the underlying mechanism is dynamic proxy.
Real‑world Mapping
Spring AOP – @Transactional, @Cacheable are implemented via dynamic proxies.
MyBatis – MapperProxy creates a proxy for mapper interfaces.
Dubbo RPC – the consumer invokes a remote service through a proxy, making network communication transparent.
2. Decorator Pattern – Java IO and Runtime Feature Extension
Core Idea
Dynamically add responsibilities to an object. Compared with subclassing, decorators are more flexible and avoid class explosion.
Anti‑example: Subclass explosion with milk‑tea toppings
Base milk tea costs 10 元; adding pearl (+2 元), coconut (+3 元), boba (+4 元). Using inheritance for every combination would require 2¹⁰ = 1024 classes.
Decorator Implementation
// ① Abstract component
public interface Drink {
String getDescription();
int cost();
}
// ② Concrete component – basic milk tea
public class BaseMilkTea implements Drink {
public String getDescription() { return "基础奶茶"; }
public int cost() { return 10; }
}
// ③ Abstract decorator – holds a Drink reference and implements Drink
public abstract class DrinkDecorator implements Drink {
protected Drink drink;
public DrinkDecorator(Drink drink) { this.drink = drink; }
}
// ④ Concrete decorator – add pearl
public class PearlDecorator extends DrinkDecorator {
public PearlDecorator(Drink drink) { super(drink); }
public String getDescription() { return drink.getDescription() + " + 珍珠"; }
public int cost() { return drink.cost() + 2; }
}
// ⑤ Concrete decorator – add coconut
public class CoconutDecorator extends DrinkDecorator {
public CoconutDecorator(Drink drink) { super(drink); }
public String getDescription() { return drink.getDescription() + " + 椰果"; }
public int cost() { return drink.cost() + 3; }
}Client Usage – Stack decorators as needed
// Customer orders: basic milk tea + pearl + coconut
Drink myDrink = new CoconutDecorator(
new PearlDecorator(
new BaseMilkTea()
)
);
System.out.println(myDrink.getDescription()); // 输出:基础奶茶 + 珍珠 + 椰果
System.out.println("总价: " + myDrink.cost() + " 元"); // 输出:总价: 15 元Changing order or adding more layers is trivial, demonstrating the flexibility over inheritance.
Real‑world Mapping – Java IO Streams
Java IO uses the decorator pattern: each stream wraps the previous one, adding capabilities such as buffering, character conversion, etc.
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("a.txt")
)
); InputStream / Reader– abstract component. FileInputStream – concrete component (lowest level). FilterInputStream – abstract decorator. BufferedInputStream – concrete decorator adding buffering. DataInputStream – concrete decorator adding primitive‑type read/write.
3. Proxy vs. Decorator – Interview‑Level Distinction
Proxy focuses on "control"; Decorator focuses on "enhancement".
Core purpose : Proxy – control access, hide implementation, intercept methods; Decorator – dynamically add behavior, stack features.
Where the wrapper comes from : Proxy – created/injected inside the proxy class, invisible to the caller; Decorator – supplied by the client via constructor, caller actively composes.
Binding time : Proxy – determined at compile‑time or startup (strong binding); Decorator – composed at runtime (highly flexible).
Typical use cases : Proxy – Spring AOP, MyBatis mapper, Dubbo RPC; Decorator – Java IO streams, Spring transaction/cache wrappers.
4. Adapter Pattern – The "Translator" that Bridges Incompatible Interfaces
Core Idea
Convert one interface into another expected by the client, enabling incompatible components to work together.
Business Scenario: Dual payment callbacks (Alipay & WeChat)
Alipay callback: trade_no, total_amount
WeChat callback: transaction_id, total_fee
Internal system expects: orderId, amountEach third‑party uses its own “dialect”; the internal system speaks a single “language”.
Solution – Write an adapter for each third‑party
// Standard internal interface
public interface PayCallback {
String getOrderId();
int getAmount();
}
// Alipay adapter – translates Alipay fields to the standard
public class AliPayAdapter implements PayCallback {
private AliPayCallbackDTO aliPayData;
public AliPayAdapter(AliPayCallbackDTO data) { this.aliPayData = data; }
@Override public String getOrderId() { return aliPayData.getTrade_no(); }
@Override public int getAmount() { return (int)(aliPayData.getTotal_amount() * 100); }
}
// WeChat adapter – translates WeChat fields to the standard
public class WechatPayAdapter implements PayCallback {
private WechatCallbackDTO wechatData;
public WechatPayAdapter(WechatCallbackDTO data) { this.wechatData = data; }
@Override public String getOrderId() { return wechatData.getTransaction_id(); }
@Override public int getAmount() { return wechatData.getTotal_fee(); }
}Business logic depends only on PayCallback. Adding a new channel only requires a new adapter, leaving core code untouched.
Real‑world Mapping
Java IO – InputStreamReader adapts InputStream to Reader.
Spring MVC – HandlerAdapter adapts various handlers to a unified processing flow.
SLF4J – a logging façade that adapts Log4j, Logback, etc., to a common API.
5. Facade Pattern – The "Big Butler" for Microservice‑Era Complexity
Core Idea
Provide a unified high‑level interface to a complex subsystem, hiding internal intricacies and reducing client coupling.
Business Scenario: One‑click order in an app
Front‑end would otherwise need to call four microservices (inventory, coupon, order, points), causing network overhead and tight coupling.
// Facade class – the “big butler” for one‑click ordering
@Service
public class OrderFacade {
@Autowired private InventoryService inventoryService;
@Autowired private CouponService couponService;
@Autowired private OrderService orderService;
@Autowired private PointsService pointsService;
public OrderResult createOrder(CreateOrderRequest request) {
// ① lock stock
inventoryService.lockStock(request.getSkuId(), request.getQuantity());
// ② redeem coupon
couponService.redeem(request.getCouponId(), request.getUserId());
// ③ create order
Order order = orderService.create(request);
// ④ grant points
pointsService.grant(request.getUserId(), order.getAmount());
return OrderResult.success(order.getId());
}
}Front‑end now calls a single endpoint POST /order/create; the facade hides all internal service orchestration.
Real‑world Mapping
API Gateway (Nginx/Kong) – a façade exposing a unified entry point.
Spring Boot auto‑configuration – a single annotation ( @SpringBootApplication) that wires dozens of configuration classes.
SLF4J Logger – a façade that presents a simple log.info() API while hiding the underlying logging framework.
6. Quick‑Reference Card for the Four Structural “Kings”
Proxy – “Find a stand‑in to do the work”; solves adding logging, permissions, transactions, RPC without changing original code; typical in Spring AOP, MyBatis, Dubbo.
Decorator – “Stack toys to add ingredients”; avoids subclass explosion, enables dynamic feature stacking; typical in Java IO streams, caching/limiting wrappers.
Adapter – “Interface mismatch? I’ll bridge it”; reconciles incompatible APIs, useful for payment gateway integration, legacy system refactoring.
Facade – “Unified entry for easy management”; hides complex subsystems, ideal for microservice API gateways, BFF aggregation layers.
Key Takeaways
Proxy and Decorator look similar in code structure but differ in intent: one controls, the other enhances.
Adapter and Facade are both “middle‑layer” concepts – Adapter smooths differences, Facade shields complexity.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tinker Programmer
Solving problems with code, sharing practical tech insights, and leveling up together!
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.
