使用真实数据的Builder模式:控制器作为指挥者是否违反设计原则?
Great question! Let me reassure you first: your approach does NOT violate the Builder pattern's design principles — in fact, this is exactly how the pattern is meant to be used in real-world applications.
Let’s break down why this is perfectly valid:
1. Builder’s core isn’t about hardcoding values
Tutorials use hardcoded strings like setFoo("hardcoded value") purely for simplicity, to focus on explaining the pattern’s structure. The whole point of the Builder pattern is to let you dynamically configure objects with any data source — whether it’s a database, user input, API responses, or (like your case) data fetched in an MVC controller.
2. Using a controller as the Director makes sense
The Builder pattern often includes a "Director" role: a component that controls the construction flow, deciding which properties to set and with what values. Your MVC controller fits this role perfectly: it’s responsible for gathering the necessary data (from the database, in your example) and directing the Builder to assemble the final object. This separation of concerns is exactly what the pattern encourages.
3. Key things to keep in mind (to stay true to the pattern)
To make sure you’re using Builder optimally:
- Enforce immutability (if desired): If your goal is to create immutable objects (a common use case for Builder), ensure the final object has no setters, and the Builder only passes values to the object’s private constructor.
- Keep the Builder focused: Don’t add business logic to the Builder — its sole job should be collecting values and constructing the object. Leave data fetching and validation (beyond basic checks like non-null required fields) to the controller or service layer.
- Use method chaining (optional but clean): Modify your Builder’s setters to return the Builder instance itself, which makes the code more readable (like
fooBuilder.setBar(data).setFoo(moreData).build()).
Example of a clean implementation with real data
// Immutable Foo class public class Foo { private final String bar; private final String foo; // Private constructor to enforce Builder usage private Foo(FooBuilder builder) { this.bar = builder.bar; this.foo = builder.foo; } // Getters only (no setters for immutability) public String getBar() { return bar; } public String getFoo() { return foo; } public static class FooBuilder { private String bar; private String foo; // Chained setters return the Builder instance public FooBuilder setBar(String bar) { this.bar = bar; return this; } public FooBuilder setFoo(String foo) { this.foo = foo; return this; } // Build method with basic validation public Foo build() { if (bar == null || bar.isBlank()) { throw new IllegalArgumentException("Bar cannot be empty"); } return new Foo(this); } } } // MVC Controller acting as Director @Controller public class FooController { private final FooDataService dataService; // Constructor injection (dependency injection best practice) public FooController(FooDataService dataService) { this.dataService = dataService; } @GetMapping("/create-foo") public ResponseEntity<Foo> createFoo() { // Fetch real data from database via service String barData = dataService.fetchBarFromDb(); String fooData = dataService.fetchFooFromDb(); // Direct the Builder to construct the Foo object Foo finalFoo = new Foo.FooBuilder() .setBar(barData) .setFoo(fooData) .build(); return ResponseEntity.ok(finalFoo); } }
Final takeaway
Your approach is aligned with the Builder pattern’s core goals: separating object construction from its representation, allowing flexible configuration, and ensuring clean, readable code. The hardcoded values in tutorials are just a teaching tool — real-world usage relies on dynamic data inputs like the ones you’re using.
内容的提问来源于stack exchange,提问作者john

