重构咨询:将特定校验接口泛化为通用泛型接口
Great question! Your approach to genericizing the validator interfaces is spot-on—this is exactly how you decouple validation logic from specific plane/passenger types and enable reuse across all variants. Let’s break down how to implement this step by step, with concrete code examples tailored to your scenario.
1. First: Confirm Your Core Idea is Correct
Your instinct to replace type-specific interfaces with generic ones is perfect. By abstracting the validation behavior around a bounded generic type parameter (e.g., T extends Plane), you’ll eliminate duplicate interface definitions for each plane model and make the system flexible enough to handle new types without rewriting core code.
2. Step-by-Step Implementation
Let’s start with foundational classes and work our way up to generic validators, builders, and runners.
2.1 Define Base Classes for Planes and Passengers
First, create abstract base classes that all specific plane/passenger types inherit from. This gives us a common type boundary for our generics:
// Abstract Plane class (all planes extend this) public abstract class Plane { protected final String modelType; protected Plane(String modelType) { this.modelType = modelType; } // Add common plane properties/methods (e.g., getFuelLevel()) } // Example concrete plane: Boeing 737 public class Boeing737 extends Plane { public Boeing737() { super("Boeing737"); } // Add Boeing 737-specific logic here } // Abstract Passenger class (all passenger types extend this) public abstract class Passenger { protected final String passengerType; protected Passenger(String passengerType) { this.passengerType = passengerType; } } // Example concrete passenger: Economy public class EconomyPassenger extends Passenger { public EconomyPassenger() { super("Economy"); } }
2.2 Generic Validator Fragment Interfaces
Next, define generic interfaces that replace your type-specific PlaneValidatorFragment and PassengerValidatorFragment. Start with a top-level generic interface for all validators, then extend it for plane/passenger-specific cases:
// Top-level generic validator interface (shared core behavior) public interface ValidatorFragment<T> { boolean validate(T target); String getFailureMessage(); } // Generic plane-specific validator (extends core interface) public interface PlaneValidatorFragment<T extends Plane> extends ValidatorFragment<T> { // Optional: Add plane-specific default methods (e.g., priority ordering) default int getValidationPriority() { return 1; // Default priority for plane validations } } // Generic passenger-specific validator public interface PassengerValidatorFragment<T extends Passenger> extends ValidatorFragment<T> { // Optional: Add passenger-specific methods here }
2.3 Implement Generic and Type-Specific Validator Fragments
Now you can write validators that either work for all planes/passengers (using the generic type) or are tailored to a specific model:
// Generic fuel validator (works for ANY Plane type) public class EnoughFuelValidator<T extends Plane> implements PlaneValidatorFragment<T> { @Override public boolean validate(T plane) { // Generic fuel check logic (e.g., plane.getFuelLevel() > MIN_REQUIRED) return true; } @Override public String getFailureMessage() { return "Insufficient fuel for takeoff"; } } // Boeing 737-specific lights validator (only works for Boeing737) public class Boeing737WorkingLightsValidator implements PlaneValidatorFragment<Boeing737> { @Override public boolean validate(Boeing737 plane) { // Boeing 737-specific light check logic return true; } @Override public String getFailureMessage() { return "Boeing 737 navigation lights malfunction"; } } // Generic seatbelt validator (works for ANY Passenger type) public class SeatbeltFastenedValidator<T extends Passenger> implements PassengerValidatorFragment<T> { @Override public boolean validate(T passenger) { return true; } @Override public String getFailureMessage() { return "Seatbelt not fastened"; } }
2.4 Generic Sequence Builder and Runner
Update your FragmentSequenceBuilder and FragmentSequenceRunner to use generics, so they can handle any plane/passenger type:
// Generic Plane Validation Sequence Builder public class PlaneValidationBuilder<T extends Plane> { private final List<PlaneValidatorFragment<T>> fragments = new ArrayList<>(); public PlaneValidationBuilder<T> addFragment(PlaneValidatorFragment<T> fragment) { fragments.add(fragment); return this; } // Optional: Auto-load default fragments for a specific plane type public PlaneValidationBuilder<T> loadDefaultFragments(Class<T> planeClass) { if (Boeing737.class.isAssignableFrom(planeClass)) { fragments.add(new EnoughFuelValidator<>()); fragments.add(new Boeing737WorkingLightsValidator()); } // Add logic for other plane types here return this; } public PlaneValidationRunner<T> build() { // Sort fragments by priority before passing to runner List<PlaneValidatorFragment<T>> sortedFragments = fragments.stream() .sorted(Comparator.comparing(PlaneValidatorFragment::getValidationPriority)) .collect(Collectors.toList()); return new PlaneValidationRunner<>(sortedFragments); } } // Generic Plane Validation Runner public class PlaneValidationRunner<T extends Plane> { private final List<PlaneValidatorFragment<T>> fragments; public PlaneValidationRunner(List<PlaneValidatorFragment<T>> fragments) { this.fragments = fragments; } public ValidationResult run(T plane) { List<String> failures = new ArrayList<>(); for (PlaneValidatorFragment<T> fragment : fragments) { if (!fragment.validate(plane)) { failures.add(fragment.getFailureMessage()); } } return new ValidationResult(failures.isEmpty(), failures); } } // Reusable Validation Result class public class ValidationResult { private final boolean isValid; private final List<String> failureMessages; public ValidationResult(boolean isValid, List<String> failureMessages) { this.isValid = isValid; this.failureMessages = failureMessages; } // Getters for isValid and failureMessages }
2.5 Usage Example
Here’s how you’d use the generic system to validate a Boeing 737:
// Create a Boeing 737 instance Boeing737 my737 = new Boeing737(); // Build and run validation PlaneValidationRunner<Boeing737> runner = new PlaneValidationBuilder<Boeing737>() .loadDefaultFragments(Boeing737.class) .addFragment(new CustomBoeing737Validator()) // Add a custom validator .build(); ValidationResult result = runner.run(my737); // Handle the result if (!result.isValid()) { result.getFailureMessages().forEach(System.err::println); }
3. Optional Improvements
To make this system even more robust:
- Use a Factory Pattern: Replace the hardcoded
loadDefaultFragmentslogic with a factory that maps plane/passenger types to their default validators (e.g.,PlaneValidatorFactory.getValidatorsFor(Boeing737.class)). - Annotation-Driven Loading: Add custom annotations (e.g.,
@PlaneValidator(target = Boeing737.class)) to your validator fragments, then scan for these annotations at runtime to auto-load validators. - Dependency Injection: If using a DI framework (like Spring), inject validators by their generic type to avoid manual builder setup.
Final Note
Your initial idea is solid—genericizing these interfaces is the right way to achieve reuse and flexibility. The key is to anchor everything to the abstract base classes (Plane/Passenger) and use bounded generics (T extends Plane) to enforce type safety while keeping the system open to new types.
内容的提问来源于stack exchange,提问作者yulai

