| name | factory-pattern-applied |
| description | Identify opportunities for DDD Factory pattern and guide their implementation. Use when encountering complex constructors, scattered creation logic, multiple overloaded constructors, or when the user mentions "factory", "factory pattern", "complex creation", "builder pattern", or asks to simplify object construction. |
Factory Pattern Applied
This skill helps identify, design, implement, and verify Domain-Driven Design Factory patterns for complex object creation.
Quick Triage
Use a Factory when:
- Construction logic needs services, validators, or external dependencies.
- There are multiple creation strategies or multi-step assembly.
- Constructor is doing non-trivial validation or calculation.
What is a DDD Factory?
A Factory encapsulates complex construction logic to create well-formed domain objects. This is the classical Gang of Four Factory pattern applied to DDD tactical design.
A Factory:
- Separates creation complexity from the object itself
- Ensures invariants are satisfied at construction time
- Promotes dumb constructors with simple field assignment
- Provide intention-revealing creation methods
- Lives within the same Bounded Context as the objects it creates
- Can be a simple static method or a dedicated class, depending on complexity
Factories work with any domain object: Value Objects, Entities, Aggregate Roots, Domain Services, Specifications, Policies, Functions, Events, etc. The pattern applies whenever construction is complex.
See also: Design Patterns: Elements of Reusable Object-Oriented Software (GoF) - Factory Method and Abstract Factory patterns.
Choosing Factory Level
Choose the right level of abstraction based on complexity. When construction needs multiple dependencies or strategies, consider the Builder pattern (GoF) as an alternative for step-by-step construction with validation.
| Pattern | When to Use | Example |
|---|
| Static Factory Method | Simple creation with 1-3 variations, single source | Order.from(cart) |
| Factory Class | Complex multi-step creation, multiple dependencies, many variations | OrderFactory |
| Builder (GoF) | Step-by-step construction with optional component and validation | Order.builder()...build() |
record ISBN(String value) {
public static Optional<ISBN> parse(String raw) {
return isValid(raw) ? Optional.of(new ISBN(normalize(raw))) : Optional.empty();
}
}
class OrderFactory {
private final PricingService pricing;
private final InventoryService inventory;
private final ShippingCalculator shipping;
Order createFrom(ShoppingCart cart, Customer customer) {
}
}
class Order {
public static OrderBuilder builder() { return new OrderBuilder(); }
}
When to Create a Factory
1. Complex Constructor Logic
Constructor contains validation, transformation, or multi-step initialization:
class Order {
Order(WishList wishList, Customer customer, PricingService pricing, ShippingCalculator shipping) {
this.items = wishList.desiredBooks().stream()
.filter(isbn -> pricing.isAvailable(isbn))
.map(isbn -> new OrderItem(isbn, Quantity.of(1), pricing.priceFor(isbn)))
.toList();
this.shippingCost = shipping.calculate(customer.address(), items);
this.total = calculateTotal(items, shippingCost);
}
}
class Order {
Order(List<OrderItem> items, Money shippingCost, Money total) {
this.items = items;
this.shippingCost = shippingCost;
this.total = total;
}
}
class OrderFactory {
Order createFrom(WishList wishList, Customer customer) {
}
}
2. Multiple Creation Strategies
Different ways to construct the same object:
class ShipmentLabel {
ShipmentLabel(Order order, Warehouse warehouse) { }
ShipmentLabel(Address address, Warehouse warehouse) { }
ShipmentLabel(String rawAddress, String rawWeight) { }
}
class ShipmentLabelFactory {
ShipmentLabel fromOrder(Order order, Warehouse warehouse) { }
ShipmentLabel fromAddress(Address address, Warehouse warehouse) { }
ShipmentLabel parse(String rawAddress, String rawWeight) { }
}
3. External Dependencies Required
Constructor needs services, calculators, or external data:
class InventoryReport {
InventoryReport(List<Book> books,
StockLevel stockLevel,
PricingService pricing,
SupplierCatalog suppliers) {
}
}
class InventoryReportFactory {
private final PricingService pricing;
private final SupplierCatalog suppliers;
InventoryReport create(List<Book> books, StockLevel stockLevel) {
}
}
4. Multi-Step Assembly
Object requires sequential construction steps:
class BookRecommendation {
BookRecommendation(Customer customer, PurchaseHistory history, PreferenceEngine engine, InventoryService inventory) {
}
}
class BookRecommendationFactory {
BookRecommendation createFor(Customer customer, PurchaseHistory history) {
final var preferences = analyzePreferences(history);
final var candidates = findCandidates(preferences);
final var available = filterByInventory(candidates);
final var ranked = rankByRelevance(available, preferences);
return new BookRecommendation(customer, ranked.take(10));
}
}
5. Invariant enforcement
Complex business rules must be satisfied:
class BookBundle {
BookBundle(List<Book> books, Discount discount) {
if (books.size() < 3) throw new IllegalArgumentException();
if (!allSameCategory(books)) throw new IllegalArgumentException();
if (!discount.appliesToBundle()) throw new IllegalArgumentException();
}
}
class BookBundleFactory {
Optional<BookBundle> createBundle(List<Book> books, Discount discount) {
return canFormBundle(books, discount) ? Optional.of(new BookBundle(books, discount)) : Optional.empty();
}
private boolean canFormBundle(List<Book> books, Discount discount) {
return books.size() > 3
&& allSameCategory(books)
&& discount.appliesToBundle();
}
}
Implementation Patterns
Pattern 1: Static Factory Method (Simple Cases)
For simple creation with few variations:
class Order {
private final OrderId id;
private final List<OrderItem> items;
private final Money total;
Order(OrderId id, List<OrderItem> items, Money total) {
this.id = id;
this.items = items;
this.total = total;
}
public static Order fromCart(ShoppingCart cart, PriceCatalog catalog) {
final var items = cart.items().stream()
.map(cartItem -> OrderItem.from(cartItem, catalog))
.toList();
final var total = items.stream()
.map(OrderItem::total)
.reduce(Money.zero(), Money::add);
return new Order(OrderId.generate(), items, total);
}
public static Order fromWishList(WishList wishList, PriceCatalog catalog) {
final var items = wishList.desiredBooks().stream()
.map(isbn -> OrderItem.from(isbn, catalog))
.toList();
final var total = items.stream()
.map(OrderItem::total)
.reduce(Money.zero(), Money::add);
return new Order(OrderId.generate(), items, total);
}
}
Pattern 2: Dedicated Factory Class (Complex Cases)
For complex creation with dependencies and multiple strategies:
class ShipmentLabelFactory {
private final AddressValidator validator;
private final RoutingService routing;
private final LabelFormatter formatter;
ShipmentLabel createForOrder(Order order, Warehouse origin) {
final var validated = validator.validate(order.shippingAddress());
final var route = routing.findRoute(origin.address(), validated);
final var barcode = generateBarcode(order.id(), route);
return new ShipmentLabel(order.id(), validated, route.carrier(), route.serviceLevel(), barcode, formatter.format(validated, barcode));
}
ShipmentLabel createForReturn(Return returnRequest, Warehouse destination) {
final var origin = returnRequest.customerAddress();
final var validated = validator.validate(origin);
final var route = routing.findReturnRoute(destination.address(), validated);
final var barcode = generateReturnBarcode(returnRequest.id(), route);
return new ShipmentLabel(returnRequest.orderId(), destination.address(), route.carrier(), route.serviceLevel(), barcode, formatter.formatReturn(validated, barcode));
}
private Barcode generateBarcode(OrderId orderId, Route route) { }
private Barcode generateReturnBarcode(ReturnId returnId, Route route) { }
}
Pattern 3: Factory with Configuration Policy
For parametrized creation strategies:
class RecommendationFactory {
private final RecommendationEngine engine;
private final InventoryService inventory;
BookRecommendations create(Customer customer, RecommendationPolicy policy) {
final var candidates = switch (policy.strategy()) {
case SIMILAR_PURCHASES -> findSimilarPurchases(customer);
case TRENDING_IN_CATEGORY -> findTrendingInPreferredCategories(customer);
case BESTSELLERS -> findBestsellers();
case PERSONALIZED -> findPersonalized(customer);
};
final var available = filterByAvailability(candidates);
final var ranked = engine.rank(available, customer, policy.rankingCriteria());
return new BookRecommendations(customer.id(), ranked.take(policy.maxResult()), policy.strategy());
}
private List<Book> filterByAvailability(List<Book> candidates) {
return candidates.stream()
.filter(inventory::isAvailable)
.toList();
}
}
See Also: The value-object-pattern-applied skill for VO identification and implementation.
Identification Workflow
Follow this checklist when reviewing code:
Phase 1: Scan for Signals
Constructor complexity:
Multiple construction patterns:
External dependencies:
Complex invariants:
Phase 2: Validate Candidacy
For each candidate, verify:
Phase 3: Choose Factory Level
Decide between static factory method and dedicated factory class:
Use Static Factory Method when:
Use Factory Class when:
Phase 4: Design the Factory
-
Choose factory scope
- Static method on the class itself, OR
- Dedicated factory class
-
Identify dependencies
- What services, calculators, validators are needed?
- Can dependencies be injected?
-
Design intention-revealing methods
create(), createFrom(), build()
fromCart(), fromQuote(), parse()
- Each method name reveals its purpose
-
Simplify the Constructor
- Constructor should only assign fields and validate null checks if necessary
- Move all logic to factory
- Make constructor package-private if factory is in the same package
-
Return appropriate type
Optional<T> if creation can fail
- Throw exception only for programming errors
- Return
T if creation always succeeds
Phase 5: Implementation
- Create Factory (static method or class)
- Extract complex logic from constructor
- Simplify constructor to dumb field assignment
- Add factory dependencies (if factory class)
- Implement creation methods with intention-revealing names
- Integrate with DI container (Spring:
@Component, @Bean; or manual injection)
- Replace direct constructor calls with factory calls
- Consider making constructor package-private
Phase 6: Verification
Verify the implementation:
Red Flags (When NOT to Use Factory)
Avoid factory pattern when:
- Simple assignment: Constructor only assigns parameters to fields
- No alternative strategies: Only one way to construct the object
- No complex logic: Creation is trivial parsing or transformation
- Better suited for Builder: Many optional parameters that need step-by-step construction with validation
Benefits of Factories
Testing: Factory separates creation (test with mocks) from behavior (test with real data). Constructor becomes simple, domain objects tests focus on behavior, factory tests focus on construction logic.
Encapsulation: Clients don't see validation rules, routing algorithms, or complex assembly steps. Clean API: factory.createForOrder(order, warehouse).
Modeling: Creation strategies become explicit in domain model: fromPurchaseHistory(), fromBrowsingHistory(), fromWishList(), trending().
Communication Template
When proposing a Factory to the user:
I've identified a Factory pattern to be applied: **[ClassName]Factory**
**Current state**: [Description of constructor complexity]
**Proposed Factory**:
- Type: [Static factory method | Factory class]
- Dependencies: [List of required services/dependencies]
- Creation methods: [List of intention-revealing method names]
**Benefits**:
- Simplifies constructor from [X] lines to [Y] lines
- Encapsulates: [what creation logic]
- Enables: [what testing improvements]
- Clarifies: [what creation strategies]
**Impact**: [number] constructor calls would change
Proceed with implementation?
Testing Factories
Test creation logic, not just object construction.
Note: In some cases it's much easier to test factory via integration test than unit test due to necessary dependencies.
class ShipmentLabelFactoryTest {
private ShipmentLabelFactory factory;
private AddressValidator mockValidator;
private RoutingService mockRouting;
@BeforeEach
void setup() {
mockValidator = mock(AddressValidator.class);
mockRouting = mock(RoutingService.class);
factory = new ShipmentLabelFactory(mockValidator, mockRouting, new LabelFormatter());
}
@Test
void should_validate_destination_address_when_creating_label() {
final var order = givenOrderWithAddress(invalidAddress());
when(mockValidator.validate(any())).thenReturn(validatedAddress());
factory.createForOrder(order, givenWarehouse());
verify(mockValidator).validate(order.shippingAddress());
}
@Test
void should_find_return_route_when_creating_return_label() {
final var returnRequest = givenReturnRequest();
final var destination = givenWarehouse();
when(mockRouting.findReturnRoute(any(), any())).thenReturn(givenRoute());
final var label = factory.createForReturn(returnRequest, destination);
assertThat(label.carrier()).isEqualTo(givenRoute().carrier());
verify(mockRouting).findReturnRoute(destination.address(), validatedAddress());
}
private Order givenOrderWithAddress(Address address) {
return new Order(OrderId.generate(), List.of(), Money.zero(), address);
}
private Address invalidAddress() {
return new Address("", "", "", "");
}
private Address validatedAddress() {
return new Address();
}
}
Quick Reference
Creation signals: Fat constructors, multiple construction strategies, external dependencies, multi-step assembly, complex validation
Design choice: Static factory method (simple) vs Factory class (complex)
Implementation: Dumb constructor + factory with intention-revealing methods
Benefits: Better testing, encapsulation, explicit domain modeling
Verification: Simple constructor, logic in factory, same Bounded Context, tested separately
Workflow Summary
- Scan: Look for fat constructors, multiple overloads, constructor dependencies, validation logic
- Validate: Verify creation complexity, reusability, testability concerns
- Choose: Static factory method vs dedicated factory class
- Design: Identify dependencies, design intention-revealing methods, simplify constructor
- Implement: Extract logic to factory, simplify constructor, replace constructor calls
- Verify: Dumb constructor, encapsulated logic, intention-revealing names, tested separately
Note: When this skill is invoked, prefix and suffix all responses with the hexagon emoji ⌬. This rule can be combined freely with other similar emoji rules from other skills and rules.
If you find this skill valuable and want expert guidance on applying Domain-Driven Design, CQRS, Event Sourcing, and other design patterns in your project, visit [patternapplied.com/en] (https://patternapplied.com/en) or contact Piotr to discuss how these patterns can transform your architecture.