Using Strategy Pattern for Settlement Prorate: Calculating Fees Across Different Marketing Campaigns
The article explains how an e‑commerce platform’s settlement module can use the Strategy pattern to cleanly handle varied fee calculations for group‑buy, flash‑sale, special‑price and full‑discount promotions, avoiding tangled if‑else logic and simplifying future extensions.
Problem
In an e‑commerce settlement module the platform must calculate how much revenue from each order belongs to the platform versus the merchant. Plain orders are simple, but marketing activities such as group‑buy, flash‑sale, special‑price, and full‑discount each provide the three settlement parameters (prorate distribution, marketing ratio, activity price) from different tables, with distinct validation rules and calculation methods. A monolithic if‑else implementation becomes fragile and hard to maintain.
Core Concepts
Prorate Distribution (prorateDistribution): the base proportion the platform extracts from the merchant’s amount; stored per SKU.
Marketing Ratio (marketRatio): a finer‑grained service‑fee rate that takes precedence over prorateDistribution unless it is null or –1.
Activity Price (price): the promotional selling price that directly influences the merchant’s earnings.
All three values are Integer fields in a SettlementBO object.
Why Strategy Pattern
Each activity type obtains the three values from different tables and applies its own validation (e.g., time‑window checks). Encapsulating these differences in separate handlers lets the upper‑level code depend only on a common interface, eliminating the need to modify core logic whenever a new activity is added.
Interface Design
The ProrateDistributionHandler interface defines three methods: type() – returns the activity‑type code used for routing. getActivityProrateDistribution() – returns only the prorate distribution (legacy method). getActivitySettlementParam() – returns a full SettlementBO (newer method).
The input DTO ProrateDistributionDTO carries activity ID, activity type, product ID, SKU ID, child item ID, child SKU ID, and the payment time payTime, which is used for validity checks.
Handler Implementations
Group‑Buy, Flash‑Sale, Special‑Price (Expiration Check)
These three handlers share a two‑step logic:
Query the activity record and verify that payTime falls within the activity’s start and end times. If it is outside, return 0 for the distribution, treating the order as if it did not participate.
If the activity is valid, fetch the SKU‑level configuration to obtain prorateDistribution, marketRatio, and price. Missing configuration defaults to 0.
if (payTime.after(activity.getEndTime()) || payTime.before(activity.getStartTime())) {
return 0;
}
return Optional.ofNullable(grouponSku.getProrateDistribution()).orElse(0);When the activity record cannot be found, the handler throws a BizException. SKU‑level missing values are tolerated with a default 0.
Full‑Discount (No Expiration Check)
The Full‑Discount handler differs:
No activity‑time validation because the validity period is managed upstream in the PMS system.
The prorate distribution is hard‑coded to 0; only marketRatio and price are read from the full_off_activity_item_settlement table.
FullOffActivityItemSettlement item = activityMapper.getByActivityIdAndParentSkuId(
dto.getActivityId(), dto.getChildItemId(), dto.getSkuId());
if (item == null) return new SettlementBO(0);
return new SettlementBO(0,
Optional.ofNullable(item.getMarketRatio()).orElse(0),
item.getPrice());Comparison of Handlers
Activity type codes: Group‑Buy = 2, Flash‑Sale = 1, Special‑Price = 3, Full‑Discount = 6.
Expiration check: present for the first three, absent for Full‑Discount.
Prorate distribution source: SKU config for the first three, fixed 0 for Full‑Discount.
Marketing ratio source: SKU config for the first three, settlement config table for Full‑Discount.
Activity price source: SKU config for the first three, settlement config table for Full‑Discount.
Data source: activity table + SKU table for the first three; independent settlement config table for Full‑Discount.
Strategy Router
Spring injects all ProrateDistributionHandler beans into a list. The router iterates the list and returns the handler whose type() matches the activity type code.
@Autowired
private List<ProrateDistributionHandler> handlerList;
public ProrateDistributionHandler getProrateDistributionHandler(Integer handlerType) {
for (ProrateDistributionHandler handler : handlerList) {
if (Objects.equals(handler.type(), handlerType)) return handler;
}
return null;
}With only four handlers the linear traversal cost is negligible; if the number grows, a map‑based lookup can replace the list.
Two‑Step Compatibility Query
During settlement the system first attempts to read a manually reported prorate distribution from activity_report tables (cached in Redis). If a positive value is found, it is used directly, bypassing the handler. Otherwise the handler’s calculation is invoked. Additionally, when marketRatio == -1, the system falls back to the prorate distribution.
Benefits of the Strategy Pattern
Eliminates sprawling if‑else blocks, making the codebase easier to extend.
Provides each activity its own encapsulated space, allowing distinct validation (e.g., time checks for group‑buy), separate data sources (SKU tables vs. independent settlement tables), and tailored error handling (exception for missing activity ID, silent zero for expired activity).
Design decision: returning 0 for expired activities treats expiration as a normal business scenario; throwing an exception for missing activity data surfaces data inconsistency.
When to Apply the Pattern
If a single operation must query different tables, perform different validations, or return different parameter structures based on a type code, the Strategy pattern is appropriate. If only parameter values differ while the logic remains identical, a simple configuration table suffices.
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.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
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.
