假设我收到了其他格式的通知,例如
{UserName}标签使您在{ClientName}的帐户中添加注释。
客户{ClientName}正在等待批准。
,依此类推。我想使用占位符将消息保留在数据库中,并在动态列出它们时将占位符替换为来自右表的相应名称。 因此,对于以上示例,我的表将包含以下列:
Message: string
Type: enum [OnePlaceholder, TwoPlaceholders]
UserId: int
ClientId: int
当我从那些表中获取数据以显示它时,我应该检查每条记录是什么类型,并根据它来格式化消息。
var list = myTable.Select(m => {
if(n.Type == OnePlaceholder)
return new { Message = string.Format(n.Text, n.Client.Name) }
else if(n.Type == TwoPlaceholder)
return new { Message = string.Format(n.Text, n.User.Name, n.Client.Name) }
}.ToList();
但这显然不是一个好的解决方案。同时,这也打破了SOLID原则,因为如果有新类型的通知消息,我应该先在表中添加新列,然后在获取所有通知时添加else if check。 所以问题是你能建议我什么?
答案 0 :(得分:1)
无法帮助自己组成一些“快速”项目,以我的拙见,这是您可以扩展的坚实基础。
让我们从一些数据开始,通常以某种方式从数据库中检索这些数据。因此,下一部分中的通知将从数据库中检索。
var notifications = new[] {
"{user#1} tagged you in a comment in {client#1}'s account.",
"Client {client#1} is waiting for approval.",
"{user#2} assigned workitem {workitem#5} to {user#1}."
};
我们在这里唯一关心的是,占位符将根据占位符内的内容替换为值。因此,我们需要一些可以用值代替占位符的东西。让我们为其定义一些可以做到的事情。
public interface IPlaceHolderProcessor
{
string SubstitutePlaceHolders(string text);
}
这使我可以编写以下内容来处理通知。
IPlaceHolderProcessor processor = CreatePlaceHolderProcessor();
List<string> messages = notifications.Select(x => processor.SubstitutePlaceHolders(x)).ToList();
仅此而已...好吧,我们确实需要为该接口编写一个实现。好吧,让我们编写一个可扩展的...可测试的实现。
public class PlaceHolderProcessor : IPlaceHolderProcessor
{
IPlaceHolderValueProvider _valueProvider;
IPlaceHolderParser _parser;
public PlaceHolderProcessor(IPlaceHolderValueProvider valueProvider, IPlaceHolderParser parser)
{
_valueProvider = valueProvider;
_parser = parser;
}
public string SubstitutePlaceHolders(string message)
{
var sb = new StringBuilder();
var idx = 0;
var placeHolders = _parser.FindPlaceHolders(message);
_valueProvider.PreFillValues(placeHolders);
foreach (var placeholder in placeHolders)
{
sb.Append(message.Substring(idx, placeholder.Start - idx));
idx = placeholder.End;
sb.Append(_valueProvider.GetValue(placeholder));
}
sb.Append(message.Substring(idx));
return sb.ToString();
}
}
public interface IPlaceHolderValueProvider
{
string GetValue(PlaceHolder placeholder);
void PreFillValues(IEnumerable<PlaceHolder> placeholders);
}
public interface IPlaceHolderParser
{
IEnumerable<PlaceHolder> FindPlaceHolders(string text);
}
此实现仅实现查找替换。扫描文本如何工作或如何获取值不是此实现关心的问题。它仅知道解析器将返回占位符,并且值提供者将提供值来替换占位符。处理器的唯一任务是生成一个字符串,在其中将占位符替换为正确的值。
因此,让我们开始制作一个简单的解析器...
public class DefaultPlaceHolderParser : IPlaceHolderParser
{
public IEnumerable<PlaceHolder> FindPlaceHolders(string text)
{
foreach (Match match in Regex.Matches(text, @"(?<=\{)([^\}]+)(?=\})"))
{
yield return new PlaceHolder { Start = match.Index - 1, End = match.Index + match.Length + 1, Name = match.Value };
}
}
}
此IPlaceHolderParser实现现在返回占位符。下一步是将它们替换为值。因此,我们需要实现IPlaceHolderValueProvider。让我们为此定义一个实现。
public abstract class PlaceHolderValueProviderBase : IPlaceHolderValueProvider
{
public abstract string GetValue(PlaceHolder placeholder);
public virtual void PreFillValues(IEnumerable<PlaceHolder> placeholders) { }
}
public class DefaultPlaceHolderValueProvider : PlaceHolderValueProviderBase
{
Dictionary<string, string> _cache;
public DefaultPlaceHolderValueProvider()
{
_cache = new Dictionary<string, string>();
}
public override string GetValue(PlaceHolder placeholder)
{
if (!_cache.ContainsKey(placeholder.Name))
{
_cache[placeholder.Name] = InternalGetValue(placeholder.Name);
}
return _cache[placeholder.Name];
}
public override void PreFillValues(IEnumerable<PlaceHolder> placeholders)
{
// Use an optimized way of retrieving placeholder values and fill the _cache
}
private string InternalGetValue(string placeHolder)
{
var values = placeHolder.Split('#');
var entity = values[0];
var id = int.Parse(values[1]);
// Here you would hit the database to get a single placeholder value.
return $"[{entity}{id:000}]";
}
}
此实现将不会访问数据库,它有望提供足够的指针以查看此处发生的情况。您可以将此作为自己实施的基准。
顺便说一句..我忘记了一些东西。 CreatePlaceHolderProcessor方法只是创建合适的IPlaceHolderProcessor实现的简单工厂方法。就我而言,我使用了以下内容。
public IPlaceHolderProcessor CreatePlaceHolderProcessor()
{
var valueProvider = new DefaultPlaceHolderValueProvider();
var processor = new PlaceHolderProcessor(valueProvider, new DefaultPlaceHolderParser());
return processor;
}
这提供了一个可行且可扩展的(SOLID)解决方案。如果您对此设置有疑问/评论,请随时给我发消息。
希望这对您和其他人有帮助。