如何临时排除指定通知渠道?C#通知系统优化需求
Great question! Let's tackle this while keeping solid OOP principles intact—specifically avoiding tight coupling between your Runner class and concrete channel types like MailNotificationChannel. The key is to lean into abstraction and let your NotificationChannelEngine handle the filtering logic, so Runner never needs to reference specific channel implementations.
Core Approach: Temporary Filter Rules in the Engine
Instead of modifying the Process method with parameters or adding Suppress/Resume that depend on concrete types, we'll extend the engine to support temporary, rule-based channel filtering. Here's how it works:
- Add a unique identifier to the
INotificationChannelabstraction (so we can target channels without knowing their concrete type) - Update the engine to manage temporary filter functions
- Let
Runneradd/remove filters using the abstract identifier, never touching concrete channel classes
Step 1: Extend the INotificationChannel Abstraction
Add a ChannelId property to uniquely identify each channel without exposing its concrete type:
public interface INotificationChannel { // Unique identifier for the channel (e.g., "Mail", "FileLog", "Console") string ChannelId { get; } void SendNotification(string message); }
Implement this in your concrete channels (example for Mail):
public class MailNotificationChannel : INotificationChannel { public string ChannelId => "Mail"; public void SendNotification(string message) { // Existing mail sending logic } }
Step 2: Update NotificationChannelEngine to Support Temporary Filters
Modify the engine to track temporary filters and apply them when processing notifications:
public class NotificationChannelEngine { private readonly List<INotificationChannel> _registeredChannels = new(); private readonly Dictionary<Guid, Func<INotificationChannel, bool>> _temporaryFilters = new(); public void RegisterChannel(INotificationChannel channel) { _registeredChannels.Add(channel); } // Adds a temporary filter and returns an ID to later remove it public Guid AddTemporaryFilter(Func<INotificationChannel, bool> filterRule) { var filterId = Guid.NewGuid(); _temporaryFilters.Add(filterId, filterRule); return filterId; } // Removes a specific temporary filter using its ID public bool RemoveTemporaryFilter(Guid filterId) { return _temporaryFilters.Remove(filterId); } public void Process(string message) { // Only process channels that pass ALL temporary filters var eligibleChannels = _registeredChannels .Where(channel => _temporaryFilters.Values.All(filter => filter(channel))); foreach (var channel in eligibleChannels) { channel.SendNotification(message); } } }
Step 3: Update Runner to Use Filters (No Concrete Dependencies)
Now your Runner can temporarily exclude the mail channel without ever referencing MailNotificationChannel:
public class Runner { private readonly NotificationChannelEngine _notificationEngine; private Guid? _mailExclusionFilterId; public Runner(NotificationChannelEngine notificationEngine) { _notificationEngine = notificationEngine; } public void Operation3() { // Add a filter to exclude the "Mail" channel _mailExclusionFilterId = _notificationEngine.AddTemporaryFilter( channel => channel.ChannelId != "Mail" ); // Process notification—mail will be skipped _notificationEngine.Process("Operation 3: Critical update (no mail)"); } public void Operation4() { // Remove the filter to restore mail notifications if (_mailExclusionFilterId.HasValue) { _notificationEngine.RemoveTemporaryFilter(_mailExclusionFilterId.Value); _mailExclusionFilterId = null; } // Process notification—mail is now included again _notificationEngine.Process("Operation 4: Update complete (mail restored)"); } }
Why This Follows OOP Principles
- Dependency Inversion Principle:
Runneronly depends on abstractions (INotificationChannel,NotificationChannelEngine)—no concrete channel types are referenced. - Open/Closed Principle: Adding new channel exclusion rules doesn't require modifying existing engine or runner code.
- Single Responsibility Principle: The engine handles channel management and filtering, while
Runnerfocuses on business operations. - Flexibility: You can easily extend this to exclude multiple channels, or add more complex filter rules (e.g., exclude based on channel priority) without breaking existing code.
内容的提问来源于stack exchange,提问作者Arie

