package com.shop.shipping;
import java.math.BigDecimal;
public interface ShippingCostCalculator {
ShippingMethod supports();
BigDecimal calculate(ShipmentRequest request);
}
enum ShippingMethod {
FLAT_RATE, WEIGHT_BASED, EXPRESS
}
package com.shop.shipping;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.springframework.stereotype.Component;
@Component
class FlatRateCalculator implements ShippingCostCalculator {
private static final BigDecimal RATE = new BigDecimal("5.99");
@Override
public ShippingMethod supports() {
return ShippingMethod.FLAT_RATE;
}
@Override
public BigDecimal calculate(ShipmentRequest request) {
return RATE;
}
}
@Component
class WeightBasedCalculator implements ShippingCostCalculator {
private static final BigDecimal PER_KG = new BigDecimal("1.25");
private static final BigDecimal MINIMUM = new BigDecimal("3.50");
@Override
public ShippingMethod supports() {
return ShippingMethod.WEIGHT_BASED;
}
@Override
public BigDecimal calculate(ShipmentRequest request) {
BigDecimal cost = PER_KG.multiply(request.getWeightKg())
.setScale(2, RoundingMode.HALF_UP);
return cost.max(MINIMUM);
}
}
package com.shop.shipping;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
@Component
public class ShippingCalculatorFactory {
private final Map<ShippingMethod, ShippingCostCalculator> byMethod;
public ShippingCalculatorFactory(List<ShippingCostCalculator> calculators) {
this.byMethod = calculators.stream().collect(Collectors.toMap(
ShippingCostCalculator::supports,
Function.identity(),
(a, b) -> {
throw new IllegalStateException(
"Duplicate strategy for " + a.supports());
},
() -> new EnumMap<>(ShippingMethod.class)));
}
public ShippingCostCalculator forMethod(ShippingMethod method) {
ShippingCostCalculator calculator = byMethod.get(method);
if (calculator == null) {
throw new IllegalArgumentException("No calculator for method: " + method);
}
return calculator;
}
}
package com.shop.shipping;
import java.math.BigDecimal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/shipping")
public class ShippingController {
private final ShippingCalculatorFactory factory;
public ShippingController(ShippingCalculatorFactory factory) {
this.factory = factory;
}
@PostMapping("/quote")
public ShippingQuote quote(@RequestBody ShipmentRequest request) {
ShippingCostCalculator calculator = factory.forMethod(request.getMethod());
BigDecimal cost = calculator.calculate(request);
return new ShippingQuote(request.getMethod(), cost);
}
}
This snippet shows how the Strategy pattern and a small factory cooperate to select a shipping-cost algorithm at runtime, a common need when the same order must be priced differently depending on carrier, region, or promotion. The idea is to encode each pricing rule as its own class behind a shared contract, then defer the choice of which class to use until the request is actually being handled.
In ShippingCostCalculator, the contract is defined as a single behavioural method calculate plus a supports method that reports which ShippingMethod the strategy implements. Pairing the algorithm with a self-declared identity is what lets the factory build itself from the available implementations rather than from a hand-maintained switch statement. Each strategy is a small, independently testable unit; FlatRateCalculator returns a constant, while WeightBasedCalculator scales a per-kilogram rate and clamps to a minimum charge so tiny parcels are never underpriced.
ShippingCalculatorFactory is the runtime dispatcher. Its constructor receives every ShippingCostCalculator bean that Spring discovers on the classpath and indexes them into an EnumMap keyed by the ShippingMethod each one supports. Using Collectors.toMap with a merge function that throws makes duplicate registrations fail fast at startup instead of silently shadowing one strategy with another. Lookups in forMethod are O(1), and an unknown method raises an IllegalArgumentException rather than returning a misleading default.
ShippingController ties it together: it resolves the strategy with factory.forMethod(...), invokes calculate, and returns the price. The controller has no knowledge of individual algorithms, which is the whole point — the branching logic that would normally accumulate in the controller has been pushed into polymorphism.
The main trade-off is indirection: for two trivial cases this is more machinery than a simple conditional. It pays off when rules multiply, when each rule has its own dependencies, or when new carriers must be added without editing existing code. Adding a strategy means writing one class and declaring it a bean; the factory wires it automatically, satisfying the open/closed principle. A pitfall to watch is forgetting that every ShippingMethod needs a corresponding strategy, so a startup validation check is often added alongside this pattern.
Related snips
package com.example.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
Dependency injection with Hilt
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
class UserRegistrationService
class Result
attr_reader :user, :errors
def initialize(success:, user: nil, errors: [])
@success = success
Service objects for business logic encapsulation
class UserRegistrationForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :email, :string
attribute :name, :string
Form objects for complex form handling
package com.example.demo.model;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
Builder pattern for object construction
class CreateSubscriptions < ActiveRecord::Migration[7.1]
STATUSES = %w[pending active past_due canceled].freeze
def change
create_table :subscriptions do |t|
t.references :account, null: false, foreign_key: true
Schema-Backed Enums (DB Constraint + Rails enum)
Share this code
Here's the card — post it anywhere.