相似职责接口设计问询:ICanRotateWithinLimitedRange参数方案
Great question! This is a super common scenario where you need to enforce "one or the other" parameter rules while keeping your interface clean and intuitive. Let’s break down a few practical approaches I’ve used in real-world projects:
1. Method Overloading (For Languages That Support It)
If your language allows method overloading (like C#, Java, or C++), this is probably the most straightforward pick. You define two separate methods in the interface, each tailored to one parameter combination. The compiler will automatically enforce that callers use one or the other—no runtime errors from invalid parameter mixes.
Example in C#:
public interface ICanRotateWithinLimitedRange { // Option 1: Define range using start + end angles void ConfigureRotationRange(double startingAngle, double endingAngle); // Option 2: Define range using start angle + total rotation degrees void ConfigureRotationRange(double startingAngle, double rotationRangeInDegrees); }
- Pros: Intuitive for callers, compile-time validation of parameter rules, no extra boilerplate code.
- Cons: Doesn’t work in languages without overloading (e.g., Go, some functional languages), adds an extra method to the interface.
2. Parameter Object Pattern (Most Flexible & Clean)
This approach wraps all rotation range parameters into a dedicated value object that enforces the "one or the other" constraint internally. The interface only accepts this object, keeping it lean and focused on its core responsibility.
You can use static factory methods to make creating valid range instances explicit and self-documenting. Here’s how it might look in C#:
// Value object to encapsulate rotation range logic public class RotationRange { public double StartingAngle { get; } public double EndingAngle { get; } // Private constructor to enforce creation via factory methods private RotationRange(double startingAngle, double endingAngle) { StartingAngle = startingAngle; EndingAngle = NormalizeAngle(endingAngle); // Add logic to wrap angles (e.g., 370° → 10°) } // Factory method for start + end angle public static RotationRange FromStartAndEnd(double startingAngle, double endingAngle) { return new RotationRange(startingAngle, endingAngle); } // Factory method for start angle + rotation range public static RotationRange FromStartAndRange(double startingAngle, double rotationRangeInDegrees) { var endingAngle = startingAngle + rotationRangeInDegrees; return new RotationRange(startingAngle, endingAngle); } private double NormalizeAngle(double angle) { // Add logic to keep angle within 0-360° or your desired range return angle % 360; } } // Simplified interface public interface ICanRotateWithinLimitedRange { void SetRotationRange(RotationRange rotationRange); }
- Pros: Encapsulates all range-related logic (like angle normalization) in one place, interface stays clean, callers use clear, self-documenting factory methods, works in all languages.
- Cons: Requires creating an extra class, but this is a minor tradeoff for better maintainability long-term.
3. Optional Parameters with Runtime Validation (Quick & Dirty)
If you’re working in a language that supports optional parameters (like Python, C# 4+), you can define a single method with optional endingAngle and rotationRangeInDegrees, then add runtime checks to enforce the constraint.
Example in Python:
from abc import ABC, abstractmethod class ICanRotateWithinLimitedRange(ABC): @abstractmethod def set_rotation_range(self, starting_angle: float, ending_angle: float = None, rotation_range_in_degrees: float = None): # Enforce mutual exclusivity if ending_angle is None and rotation_range_in_degrees is None: raise ValueError("You must provide either ending_angle or rotation_range_in_degrees") if ending_angle is not None and rotation_range_in_degrees is not None: raise ValueError("You can only provide one of ending_angle or rotation_range_in_degrees") # Calculate ending angle if using range if rotation_range_in_degrees is not None: ending_angle = starting_angle + rotation_range_in_degrees # Rest of your logic here
- Pros: Minimal interface changes, quick to implement for small projects.
- Cons: Errors are only caught at runtime, callers might accidentally pass both parameters, less self-documenting than other approaches.
My Recommendation
Personally, I lean towards the Parameter Object Pattern for most cases. It not only enforces the parameter rules but also gives you a dedicated place to add related logic (like angle normalization, range validation) without cluttering your interface or implementing classes. If your language supports overloading, that’s a close second for its simplicity and compile-time safety.
内容的提问来源于stack exchange,提问作者M.Y. Babt

