我有一个 Mediator 类,它有一个注册管道方法,该方法采用消息类型、管道过滤器类型和消息处理程序类型。
internal static void RegisterPipeLine<T1, T2, T3>()
where T1 : IMessage
where T2 : BaseFilter
where T3 : IMessageHandler
{
var pipeLine = new PipeLine()
{
Filters = [typeof(T2)],
MessageHandler = typeof(T3)
};
pipeLines[typeof(T1)] = pipeLine;
}
我这样称呼
Mediator.RegisterPipeLine<Message1, LogFilter, MessageHandler1>();
这很好,但我想有多个管道过滤器。到目前为止,我已经像这样重载了 RegisterPipeLine 方法
internal static void RegisterPipeLine<T1, T2, T3, T4>()
where T1 : IMessage
where T2 : BaseFilter
where T3 : BaseFilter
where T4 : IMessageHandler
{
var pipeLine = new PipeLine()
{
Filters = [typeof(T2), typeof(T3)],
MessageHandler = typeof(T4)
};
pipeLines[typeof(T1)] = pipeLine;
}
我称之为
Mediator.RegisterPipeLine<Message2, LogFilter, ValidFilter, MessageHandler2>();
但我不想为管道中想要的每个过滤器创建特定的过载。
有没有办法做得更好?
12
5 个回答
5
您可以使用类似构建器模式的东西:
public class PipelineBuilder<TMessage,TMessageHandler>() where TMessage: IMessage, TMessageHandler : IMessageHandler
{
private List<Type> filters = new List<Type>();
public PipeLineBuilder<TMessage,TMessageHandler> WithFilter<TFilter>() where TFilter : BaseFilter
{
filters.Add(typeof(TFilter));
return this;
}
public void Register()
{
var pipeLine = new PipeLine()
{
Filters = filters,
MessageHandler = typeof(TMessageHandler)
};
pipeLines[typeof(TMessage)] = pipeLine;
}
}
用法是
new PipelineBuilder<Message2, MessageHandler2>()
.WithFilter<LogFilter>()
.WithFilter<ValidFilter>()
.Register();
1
-
1How do you intend for the pipeLines dictionary to work in this solution?–
|
Why don’t you prefer to use params
keyword? If you do so, we can handle multiple filters without creating separate overloads for each number of filters.
internal static void RegisterPipeLine<TMessage, THandler>(params Type[] filterTypes)
where TMessage : IMessage
where THandler : IMessageHandler
{
var pipeLine = new PipeLine()
{
Filters = filterTypes,
MessageHandler = typeof(THandler)
};
pipeLines[typeof(TMessage)] = pipeLine;
}
We can call as such.
Mediator.RegisterPipeLine<Message1, MessageHandler1>(typeof(LogFilter), typeof(ValidFilter));
By doing so, we can call with any number of filter types.
3
-
main issue is that we don’t get compile time check to make sure that the params of Type are actually all derived from BaseFilter, like we do with a bit of extra work in my answer.–
-
@tinmanjk, there is no excellence, and there are always drawbacks when there are advantages.–
-
one can strive for it. OP could have overloaded to Func<T1,T2,T3…> style but then again wondered if something could be done…compile time.–
|
I think this would work with adding a helper class and an interface (for covariance) to achieve full compile time safety.
The cost for me is negligible as we instantiate empty objects that won’t put too much pressure on the GC.
public class TypeOf<T> : ITypeOf<T> {
public Type Type => typeof(T);
public static TypeOf<T> Get => new TypeOf<T>(); // convenience
}
public interface ITypeOf<out T> {
Type Type { get; }
}
internal static void RegisterPipeLine<T1, T2, T3>(params ITypeOf<T2>[] baseFilters)
where T1 : IMessage
where T2 : BaseFilter
where T3 : IMessageHandler
{
var pipeLine = new PipeLine()
{
Filters = baseFilters.Select(x => x.Type).ToList()
MessageHandler = typeof(T3)
};
pipeLines[typeof(T1)] = pipeLine;
}
Usage is
RegisterPipeLine<,BaseFilter,>(TypeOf<LogFilter>.Get, TypeOf<ValidFilter>.Get);
|
Here is my take on it. Using two builders.
static void Main(string[] args)
{
var builder = new PipelineBuilder();
builder
.AddPipeLine<Message1, MessageHandler1>()
.WithFilter<LogFilter>()
.WithFilter<ValidFilter>();
builder
.AddPipeLine<Message2, MessageHandler2>()
.WithFilter<LogFilter>();
var pipelines = builder.Build();
}
public class PipelineBuilder()
{
private List<(Type message, Type handler, FilterBuilder builder)> items = new();
public IFilterBuilder AddPipeLine<T1, T2>()
where T1 : IMessage
where T2 : IMessageHandler
{
var filterBuilder = new FilterBuilder();
items.Add((typeof(T1), typeof(T2), filterBuilder));
return filterBuilder;
}
public IReadOnlyDictionary<Type, PipeLine> Build()
{
var pipelines = new Dictionary<Type, PipeLine>();
foreach (var (message, handler, builder) in items)
{
pipelines.Add(
message,
new PipeLine()
{
Filters = builder.Filters,
MessageHandler = handler
});
}
return pipelines;
}
}
public class FilterBuilder : IFilterBuilder
{
public List<Type> Filters { get; } = new();
public IFilterBuilder WithFilter<T>() where T : BaseFilter
{
Filters.Add(typeof(T));
return this;
}
}
public interface IFilterBuilder
{
IFilterBuilder WithFilter<T>() where T : BaseFilter;
}
|
If I were you, I would prefer to use the Chain of Responsibility pattern for filter. It’s more nature for your implementation example.
public abstract class BaseFilter
{
public abstract bool Filter();
}
public abstract class BaseFilter<T>: BaseFilter where T : BaseFilter, new()
{
private readonly T _innerFilter;
protected BaseFilter()
{
_innerFilter = new T();
}
public override bool Filter(IMessage message)
{
_innerFilter.Filter(message);
return true; //// e.g.
}
}
This will give you flexibility of using different filters in conjunction with each other.
As for example
public bool Filter(IMessage message)
{
return _innerFilter.Filter(message) && message.Contains("test"); //// e.g.
}
Your call side of the code won’t change, and it might look like
Mediator.RegisterPipeLine<Message1, ValidFilter<LogFilter>, MessageHandler1>();
. The implementation of this pattern for your case might be different and look like builder that also described here.
|
–
RegisterPipeLine
首先,您的方法看起来不需要是通用的。为什么不让您的调用者只提供Type
参数呢?–
–
BaseFilter
,这会违背目的。–
IEnumerable<Type> filterTypes
–
|