Creating Deliveries & Gateways Without Scattered new
Scattering `if (mode == "BIKE") new ExpressBikePartner(...)` and direct instantiation across dispatchers, background jobs, and admin consoles tightly couples callers to concrete partner constructors and their complex private parameters (like aviation permits for drones). Changing a single constructor breaks ten different files.
Callers that only need to call dispatch(orderId) are tightly coupled to every concrete class constructor and its private initialization parameters.
Define an interface for creating an object, but let subclasses decide which class to instantiate, deferring instantiation to specialized creators.
We define a common DeliveryPartner product interface. For simple string/enum conversion, a Simple Factory (DeliveryPartnerFactory) encapsulates parameter lookups in one place. For architectural workflows, the Factory Method pattern defines a FulfillmentService base class whose fulfillOrder() template method delegates partner creation to a pure virtual createDeliveryPartner() factory method implemented by regional creator subclasses.
In Sprint 4 (A4), we implemented an OrderState state machine ensuring orders transition cleanly through Placed, Accepted, Cooking, PickedUp, and Delivered.
When cooking finishes and an order transitions to pickup, our fulfillment engine must dispatch the appropriate delivery partner (ExpressBikePartner, EcoBatchPartner, or DronePartner) based on urban zones, campus restrictions, and delivery urgency.
Scattering if (mode == "BIKE") new ExpressBikePartner(...) and direct instantiation across dispatchers, background jobs, and admin consoles tightly couples callers to concrete partner constructors and their complex private parameters (like aviation permits for drones). Changing a single constructor breaks ten different files.
Using Factory Method (with FulfillmentService creators deferring partner creation to subclasses like UrbanExpressFulfillment and TechParkFulfillment), callers depend purely on the abstract DeliveryPartner interface. Next in Sprint 6 (A6 Β· DIP), we discover that our high-level OrderService still directly instantiates concrete database and payment clients, making unit tests slow, brittle, and network-dependent.
The product ticket
Our logistics engine assigns different delivery fleets based on order urgency, location, and packaging: ExpressBikeDelivery (urgent motorcycle couriers), EcoBatchDelivery (batched electric bikes for university campuses), and DroneDelivery (autonomous aerial drops for tech parks).
- β’
Support multiple fulfillment modes with differing pricing algorithms and dispatch mechanisms.
- β’
Prevent callers throughout the app from scattering
newormake_uniquecalls for concrete partner classes. - β’
Allow specialized regional fulfillment centers to define which partner fleet to instantiate without altering core dispatch workflows.
Autonomous drones require security permits (flightPermit_) and airspace clearance tokens during construction. In the naive codebase, every caller initializing DroneDelivery had to copy-paste dummy permit strings and config lookups. When constructor parameters change, ten different files break simultaneously.
Step 1 β The naive code & why it breaks
Every caller (OrderDispatcher, ScheduledDeliveryJob, AdminFulfillmentConsole) directly instantiates concrete delivery classes with new or std::make_unique inside repetitive if / else chains.
#include <memory>
#include <print>
#include <string>
// Concrete delivery partner classes:
class ExpressBikeDelivery {
public:
void dispatch(const std::string& orderId) {
std::println("[EXPRESS BIKE] Urgent rider dispatched for order {}", orderId);
}
};
class EcoBatchDelivery {
public:
void dispatch(const std::string& orderId) {
std::println("[ECO BATCH] Batched EV courier queued for order {}", orderId);
}
};
class DroneDelivery {
std::string flightPermit_;
public:
explicit DroneDelivery(std::string permit) : flightPermit_(std::move(permit)) {}
void dispatch(const std::string& orderId) {
std::println("[DRONE] Aerial drone dispatched for order {} (permit: {})", orderId, flightPermit_);
}
};
// Caller 1: Normal Order Dispatcher
class OrderDispatcher {
public:
void dispatchOrder(const std::string& orderId, const std::string& type) {
// Scattered instantiation with hardcoded conditional logic:
if (type == "EXPRESS") {
auto partner = std::make_unique<ExpressBikeDelivery>();
partner->dispatch(orderId);
} else if (type == "ECO") {
auto partner = std::make_unique<EcoBatchDelivery>();
partner->dispatch(orderId);
} else if (type == "DRONE") {
// Caller has to know Drone needs a permit ID!
auto partner = std::make_unique<DroneDelivery>("DGCA-BLR-404");
partner->dispatch(orderId);
}
}
};
// Caller 2: Scheduled Bulk Job (duplicates the exact same construction logic!)
class ScheduledDeliveryJob {
public:
void runJob(const std::string& orderId, const std::string& type) {
if (type == "EXPRESS") {
auto partner = std::make_unique<ExpressBikeDelivery>();
partner->dispatch(orderId);
} else if (type == "ECO") {
auto partner = std::make_unique<EcoBatchDelivery>();
partner->dispatch(orderId);
} else if (type == "DRONE") {
auto partner = std::make_unique<DroneDelivery>("DGCA-BLR-404");
partner->dispatch(orderId);
}
}
};
int main() {
OrderDispatcher dispatcher;
dispatcher.dispatchOrder("ORD-501", "EXPRESS");
dispatcher.dispatchOrder("ORD-502", "DRONE");
ScheduledDeliveryJob job;
job.runJob("ORD-503", "ECO");
}Callers that only need to call dispatch(orderId) are tightly coupled to every concrete class constructor and its private initialization parameters.
When DroneDelivery adds a required aviation permit argument, developers must update every single new DroneDelivery(...) call site scattered across separate job and service files.
The same if (type == "EXPRESS") ... else if (type == "DRONE") logic is duplicated across multiple background jobs, violating DRY.
Step 2 β The fix: Factory Method (+ Simple Factory)
Define an interface for creating an object, but let subclasses decide which class to instantiate, deferring instantiation to specialized creators.
We define a common DeliveryPartner product interface. For simple string/enum conversion, a Simple Factory (DeliveryPartnerFactory) encapsulates parameter lookups in one place. For architectural workflows, the Factory Method pattern defines a FulfillmentService base class whose fulfillOrder() template method delegates partner creation to a pure virtual createDeliveryPartner() factory method implemented by regional creator subclasses.
DeliveryPartner β declares common methods dispatch(orderId), partnerType(), and costPaise(distance).
ExpressBikePartner, EcoBatchPartner, DronePartner β implement dispatch logic and vehicle-specific cost calculations.
FulfillmentService β defines the high-level fulfillOrder() workflow and declares the createDeliveryPartner() factory method.
UrbanExpressFulfillment, GreenCampusFulfillment, TechParkFulfillment β override the factory method to return the right concrete partner.
Unlock Full UML Diagram & C++23 Solution
The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.
First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.