Spring-based example for implementing Strategy, Factory, and Builder patterns for flexible product creation.
Table of Contents
- Test Guide
Architecture Overview
Design Patterns Used
- Strategy Pattern: Enables different product creation algorithms based on product type
- Factory Pattern: Manages strategy creation and caching
- Builder Pattern: Flexible product details construction with type-specific attributes
Core Components
ProductType.java
Enum defining supported product types. Centralized definition ensures type safety across the system.
public enum ProductType { ELECTRONICS, FURNITURE, CLOTHING}
ProductDetails.java
Core domain model using Builder pattern. Provides flexible specification management for different product types.
@Builderpublic class ProductDetails { private final String productId; private final BigDecimal price; private final String category; private final String name; private final Map<String, String> specifications;
public static class ProductDetailsBuilder { private Map<String, String> specifications = new HashMap<>();
public ProductDetailsBuilder addSpecification(String key, String value) { this.specifications.put(key, value); return this; } }}
Processors
QualityProcessor.java
Quality validation interface. Decouples validation logic from product creation.
public interface QualityProcessor { void validate(ProductDetails details);}
DefaultQualityProcessor.java
Default implementation of quality validation. Can be extended for specific product types.
@Servicepublic class DefaultQualityProcessor implements QualityProcessor { @Override public void validate(ProductDetails details) { // Implementation of quality validation }}
Strategies
ProductStrategy.java
Base strategy interface defining product creation contract. Each product type implements this interface.
@Servicepublic interface ProductStrategy { void create(ProductDetails details); ProductType getType();}
ElectronicsStrategy.java
Electronics-specific creation logic. Handles validation and creation for electronic products.
@Service("electronics")public class ElectronicsStrategy implements ProductStrategy { @Autowired private QualityProcessor processor;
@Override public void create(ProductDetails details) { processor.validate(details); // Electronics specific creation logic }
@Override public ProductType getType() { return ProductType.ELECTRONICS; }}
FurnitureStrategy.java
Furniture-specific creation logic. Handles validation and creation for furniture products.
@Service("furniture")public class FurnitureStrategy implements ProductStrategy { @Autowired private QualityProcessor processor;
@Override public void create(ProductDetails details) { processor.validate(details); // Furniture specific creation logic }
@Override public ProductType getType() { return ProductType.FURNITURE; }}
Factory & Service
ProductStrategyFactory.java
Factory using Spring DI for strategy management. Caches strategies for better performance.
@Componentpublic class ProductStrategyFactory { private final Map<ProductType, ProductStrategy> strategyCache = new EnumMap<>(ProductType.class);
@Autowired public ProductStrategyFactory(List<ProductStrategy> strategies) { strategies.forEach(strategy -> strategyCache.put(strategy.getType(), strategy)); }
public ProductStrategy createStrategy(ProductType type) { ProductStrategy strategy = strategyCache.get(type); if (strategy == null) { throw new IllegalArgumentException("Unknown product type: " + type); } return strategy; }}
ProductService.java
Central service orchestrating product creation. Provides both direct and builder-based creation methods.
@Servicepublic class ProductService { private final ProductStrategyFactory strategyFactory;
@Autowired public ProductService(ProductStrategyFactory strategyFactory) { this.strategyFactory = strategyFactory; }
public void createProduct(ProductType type, ProductDetails details) { ProductStrategy strategy = strategyFactory.createStrategy(type); strategy.create(details); }
public void createProduct(ProductType type, Consumer<ProductDetails.ProductDetailsBuilder> builderConsumer) { ProductDetails.ProductDetailsBuilder builder = ProductDetails.builder(); builderConsumer.accept(builder); createProduct(type, builder.build()); }}
API Layer
ProductController.java
REST controller handling product creation requests. Processes type-specific attributes dynamically.
@RestController@RequestMapping("/api/products")public class ProductController { @Autowired private ProductService productService;
@PostMapping("/create") public ResponseEntity<String> createProduct(@RequestBody JsonNode request) { try { ProductType type = ProductType.valueOf(request.get("productType").asText().toUpperCase());
ProductDetails.ProductDetailsBuilder builder = ProductDetails.builder() .productId(request.get("productId").asText()) .name(request.get("name").asText()) .price(new BigDecimal(request.get("price").asText()));
switch (type) { case ELECTRONICS: addElectronicsSpecs(builder, request); break; case FURNITURE: addFurnitureSpecs(builder, request); break; case CLOTHING: addClothingSpecs(builder, request); break; default: throw new IllegalArgumentException("Unsupported product type"); }
productService.createProduct(type, builder.build()); return ResponseEntity.ok("Product created successfully");
} catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body("Invalid product type"); } catch (Exception e) { return ResponseEntity.badRequest().body("Invalid request format"); } }
private void addElectronicsSpecs(ProductDetails.ProductDetailsBuilder builder, JsonNode request) { builder.addSpecification("processor", request.get("processor").asText()) .addSpecification("ram", request.get("ram").asText()) .addSpecification("storage", request.get("storage").asText()) .category("Electronics"); }
private void addFurnitureSpecs(ProductDetails.ProductDetailsBuilder builder, JsonNode request) { builder.addSpecification("material", request.get("material").asText()) .addSpecification("dimensions", request.get("dimensions").asText()) .addSpecification("weight", request.get("weight").asText()) .category("Furniture"); }
private void addClothingSpecs(ProductDetails.ProductDetailsBuilder builder, JsonNode request) { builder.addSpecification("size", request.get("size").asText()) .addSpecification("color", request.get("color").asText()) .addSpecification("material", request.get("material").asText()) .category("Clothing"); }}
Usage Examples
API Request Examples
// Electronics Product{ "productType": "ELECTRONICS", "productId": "ELEC123", "name": "Super Phone X", "price": "599.99", "processor": "Snapdragon 8 Gen 2", "ram": "8GB", "storage": "256GB"}
// Furniture Product{ "productType": "FURNITURE", "productId": "FUR456", "name": "Ergonomic Chair", "price": "299.99", "material": "Leather", "dimensions": "60x70x120cm", "weight": "15kg"}
// Clothing Product{ "productType": "CLOTHING", "productId": "CLT789", "name": "Winter Jacket", "price": "89.99", "size": "L", "color": "Navy Blue", "material": "Wool"}
System Interaction Diagram
sequenceDiagram participant Client participant ProductController participant ProductService participant ProductStrategyFactory participant ProductStrategy participant QualityProcessor
Client->>ProductController: POST /api/products/create Note over ProductController: Parse JSON & Extract Type ProductController->>ProductController: Build ProductDetails ProductController->>ProductService: createProduct(type, details) ProductService->>ProductStrategyFactory: createStrategy(type) ProductStrategyFactory-->>ProductService: Return cached strategy ProductService->>ProductStrategy: create(details) ProductStrategy->>QualityProcessor: validate(details) QualityProcessor-->>ProductStrategy: Validation result ProductStrategy->>ProductStrategy: Execute type-specific logic ProductStrategy-->>ProductService: Creation complete ProductService-->>ProductController: Success ProductController-->>Client: 200 OK Response
Exception Flow Diagram
sequenceDiagram participant Client participant ProductController participant ProductService participant ProductStrategyFactory
Client->>ProductController: POST with invalid type ProductController->>ProductService: createProduct(invalidType) ProductService->>ProductStrategyFactory: createStrategy(invalidType) ProductStrategyFactory-->>ProductService: Throw IllegalArgumentException ProductService-->>ProductController: Exception propagation ProductController-->>Client: 400 Bad Request
Dependencies
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency></dependencies>
Test Guide
Unit Tests
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <scope>test</scope> </dependency></dependencies>
@Testpublic class ProductDetailsBuilderTest { @Test void shouldBuildValidProductDetails() { ProductDetails details = ProductDetails.builder() .productId("TEST123") .price(new BigDecimal("99.99")) .name("Test Product") .category("Test") .addSpecification("key1", "value1") .build();
assertNotNull(details); assertEquals("TEST123", details.getProductId()); assertEquals(new BigDecimal("99.99"), details.getPrice()); assertEquals("value1", details.getSpecifications().get("key1")); }}
@ExtendWith(MockitoExtension.class)public class DefaultQualityProcessorTest { @InjectMocks private DefaultQualityProcessor processor;
@Test void shouldValidateValidProduct() { ProductDetails details = ProductDetails.builder() .productId("TEST123") .price(new BigDecimal("99.99")) .build();
assertDoesNotThrow(() -> processor.validate(details)); }}
@ExtendWith(MockitoExtension.class)public class ElectronicsStrategyTest { @Mock private QualityProcessor qualityProcessor;
@InjectMocks private ElectronicsStrategy strategy;
@Test void shouldCreateElectronicsProduct() { ProductDetails details = ProductDetails.builder() .productId("ELEC123") .price(new BigDecimal("599.99")) .addSpecification("processor", "Intel i7") .build();
doNothing().when(qualityProcessor).validate(any(ProductDetails.class));
assertDoesNotThrow(() -> strategy.create(details)); verify(qualityProcessor, times(1)).validate(details); }
@Test void shouldReturnCorrectType() { assertEquals(ProductType.ELECTRONICS, strategy.getType()); }}
@ExtendWith(MockitoExtension.class)public class ProductStrategyFactoryTest { @Mock private List<ProductStrategy> strategies;
@Mock private ElectronicsStrategy electronicsStrategy;
@Test void shouldCreateValidStrategy() { when(electronicsStrategy.getType()).thenReturn(ProductType.ELECTRONICS);
List<ProductStrategy> strategyList = Collections.singletonList(electronicsStrategy); ProductStrategyFactory factory = new ProductStrategyFactory(strategyList);
ProductStrategy strategy = factory.createStrategy(ProductType.ELECTRONICS);
assertNotNull(strategy); assertEquals(ProductType.ELECTRONICS, strategy.getType()); }
@Test void shouldThrowExceptionForInvalidType() { ProductStrategyFactory factory = new ProductStrategyFactory(Collections.emptyList());
assertThrows(IllegalArgumentException.class, () -> factory.createStrategy(ProductType.ELECTRONICS)); }}
@ExtendWith(MockitoExtension.class)public class ProductServiceTest { @Mock private ProductStrategyFactory strategyFactory;
@Mock private ProductStrategy productStrategy;
@InjectMocks private ProductService productService;
@Test void shouldCreateProductSuccessfully() { ProductDetails details = ProductDetails.builder() .productId("TEST123") .build();
when(strategyFactory.createStrategy(ProductType.ELECTRONICS)) .thenReturn(productStrategy);
assertDoesNotThrow(() -> productService.createProduct(ProductType.ELECTRONICS, details));
verify(productStrategy, times(1)).create(details); }}
Integration Tests
@ExtendWith(MockitoExtension.class)public class ProductControllerTest { @Mock private ProductService productService;
@InjectMocks private ProductController controller;
@Test void shouldCreateProductFromValidRequest() throws Exception { ObjectMapper mapper = new ObjectMapper(); JsonNode request = mapper.readTree(""" { "productType": "ELECTRONICS", "productId": "ELEC123", "name": "Test Product", "price": "599.99", "processor": "Intel i7", "ram": "16GB", "storage": "1TB" } """);
ResponseEntity<String> response = controller.createProduct(request);
assertEquals(HttpStatus.OK, response.getStatusCode()); verify(productService, times(1)).createProduct(any(), any(ProductDetails.class)); }
@Test void shouldReturnBadRequestForInvalidType() throws Exception { ObjectMapper mapper = new ObjectMapper(); JsonNode request = mapper.readTree(""" { "productType": "INVALID", "productId": "TEST123" } """);
ResponseEntity<String> response = controller.createProduct(request);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); }}
Conclusion
The Product Creation System demonstrates effective use of multiple design patterns to create a flexible, maintainable architecture:
- Flexibility: The Strategy pattern allows easy addition of new product types without modifying existing code
- Performance: Factory pattern with caching optimizes strategy instantiation
- Maintainability: Clear separation of concerns between validation, creation, and API layers
- Extensibility: Type-specific attributes handled through specification map rather than fixed fields
- Error Handling: Comprehensive exception management at controller level
To extend the system:
- Add new product type to
ProductType
enum - Create corresponding strategy implementation
- Add type-specific validation in
QualityProcessor
- Update controller with type-specific attribute handling
The system’s modular design makes it an ideal foundation for building complex product management solutions.```