Как извлечь записи из таблицы с разными типами, не нарушая SOLID - PullRequest
1 голос
/ 12 марта 2019

Допустим, у меня есть уведомление в разных форматах, например

{UserName} отметил вас в комментарии в аккаунте {ClientName}.

Client {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 при получении всех уведомлений.Итак, вопрос в том, что вы могли бы мне предложить?

1 Ответ

1 голос
/ 14 марта 2019

Не могу помочь себе в создании чего-то «быстрого», которое, по моему скромному мнению, является чем-то твердым для вас, чтобы расширить его.

Давайте начнем с некоторых данных, которые обычно извлекаются из базы данных.в пути.Таким образом, уведомления в следующей части будут извлекаться из базы данных.

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) решение.Если у вас есть вопросы / замечания относительно этой настройки, не стесняйтесь сообщать мне.

Надеюсь, это поможет вам и другим.

...