咨询:重构含多个相同签名方法的类,基于SOLID原则优化
Great call going with the composite pattern here—it’s a perfect fit for breaking down those monolithic methods into SOLID-compliant components! Let’s walk through how to refine this approach to fully align with SOLID principles, starting with the core structure and building out from there.
Step 1: Define a Clear Abstraction
First, let’s solidify your IDoSomething interface. If you need to route requests to specific handlers (instead of running all of them), add a CanHandle method to let each component declare when it’s responsible for processing an id. This makes the composite’s logic clean and type-safe:
public interface IDoSomething { // The core operation each component implements string DoSomething(int id); // Optional: For routing scenarios, declare if this component handles the given id bool CanHandle(int id); }
Step 2: Extract Each Method to Its Own Handler
Each original method (MethodA, MethodB, MethodC) gets its own class that implements IDoSomething. This enforces the Single Responsibility Principle—each class does one thing and does it well:
public class MethodAHandler : IDoSomething { public string DoSomething(int id) { // Paste your original MethodA implementation here return $"Processed by MethodA for ID: {id}"; } public bool CanHandle(int id) { // Define your logic for when MethodA should handle the request // Example: Handle IDs divisible by 3 return id % 3 == 0; } } public class MethodBHandler : IDoSomething { public string DoSomething(int id) { // Paste your original MethodB implementation here return $"Processed by MethodB for ID: {id}"; } public bool CanHandle(int id) { // Example: Handle IDs leaving remainder 1 when divided by 3 return id % 3 == 1; } } public class MethodCHandler : IDoSomething { public string DoSomething(int id) { // Paste your original MethodC implementation here return $"Processed by MethodC for ID: {id}"; } public bool CanHandle(int id) { // Example: Handle IDs leaving remainder 2 when divided by 3 return id % 3 == 2; } }
Step 3: Implement the Composite Class
Now build your composite to either route requests to the correct handler or aggregate results from all handlers—choose the approach that fits your use case:
Option 1: Routing Composite (Single Handler Execution)
This composite finds the right handler for the given id and delegates to it. Perfect if only one method should run per request:
public class CompositeDoSomethingRouter : IDoSomething { private readonly IEnumerable<IDoSomething> _handlers; // Inject all handlers via constructor (follows Dependency Inversion Principle) public CompositeDoSomethingRouter(IEnumerable<IDoSomething> handlers) { _handlers = handlers ?? throw new ArgumentNullException(nameof(handlers)); } public string DoSomething(int id) { var handler = _handlers.FirstOrDefault(h => h.CanHandle(id)); if (handler == null) { throw new InvalidOperationException($"No handler found for ID: {id}"); } return handler.DoSomething(id); } // For routing, CanHandle isn't needed unless the composite itself is used as a handler public bool CanHandle(int id) => _handlers.Any(h => h.CanHandle(id)); }
Option 2: Aggregating Composite (All Handlers Execution)
Use this if you need to run all methods and combine their results:
public class CompositeDoSomethingAggregator : IDoSomething { private readonly IEnumerable<IDoSomething> _handlers; public CompositeDoSomethingAggregator(IEnumerable<IDoSomething> handlers) { _handlers = handlers ?? throw new ArgumentNullException(nameof(handlers)); } public string DoSomething(int id) { // Run all handlers and combine results (customize the joining logic as needed) var results = _handlers.Select(h => h.DoSomething(id)); return string.Join(Environment.NewLine, results); } public bool CanHandle(int id) => true; // Aggregator handles all IDs }
Step 4: Use the Composite
To use this in your application, inject the handlers into the composite (ideally via a DI container like Microsoft DI or Autofac for automatic registration):
// Example setup (without DI) var handlers = new List<IDoSomething> { new MethodAHandler(), new MethodBHandler(), new MethodCHandler() }; // Routing usage var router = new CompositeDoSomethingRouter(handlers); Console.WriteLine(router.DoSomething(3)); // Outputs MethodA's result Console.WriteLine(router.DoSomething(4)); // Outputs MethodB's result // Aggregating usage var aggregator = new CompositeDoSomethingAggregator(handlers); Console.WriteLine(aggregator.DoSomething(5)); // Outputs all three results
Why This Fits SOLID
Let’s break down the compliance:
- Single Responsibility: Each handler owns one method’s logic; the composite only manages routing/aggregation.
- Open/Closed: Add a new method? Just create a new
IDoSomethingimplementation—no changes to existing code. - Liskov Substitution: Any
IDoSomethingimplementation can replace another without breaking the composite. - Dependency Inversion: The composite depends on the abstract
IDoSomethinginterface, not concrete handler classes. - Interface Segregation: The interface is focused on the single operation (
DoSomething) plus optional routing logic—no unused methods.
Bonus Tips
- DI Integration: Register all
IDoSomethingimplementations in your DI container, then inject the enumerable into the composite automatically. - Handler Dependencies: If your methods rely on other services, inject them into the handler’s constructor (keeps everything decoupled).
- Flexible Routing: If routing isn’t based on
id, adjust theCanHandlemethod’s parameters to match your decision criteria.
内容的提问来源于stack exchange,提问作者stefjnl

