В поисках лучшего дизайна: механизм кэширования в памяти только для чтения

У меня есть объект категории (класс), который имеет ноль или одну родительскую категорию и много дочерних категорий - это древовидная структура. Данные категорий хранятся в СУБД, поэтому для повышения производительности я хочу загрузить все категории и кэшировать их в памяти при запуске приложения.

Наша система может иметь плагины, и мы разрешаем авторам плагинов получать доступ к дереву категорий, но они не должны изменять кэшированные элементы и дерево (я думаю, что дизайн, не предназначенный только для чтения, может вызвать некоторые незначительные ошибки в этом senario), только система знает когда и как обновить дерево.

Вот несколько демонстрационных кодов:

public interface ITreeNode<T>
    where T : ITreeNode<T>
{
    // No setter
    T Parent { get; }
    IEnumerable<T> ChildNodes { get; }
}

// This class is generated by O/R Mapping tool (e.g. Entity Framework)
public class Category : EntityObject
{
    public string Name { get; set; }
}

// Because Category is not stateless, so I create a cleaner view class for Category.
// And this class is the Node Type of the Category Tree
public class CategoryView : ITreeNode<CategoryView>
{
    public string Name { get; private set; }

    #region ITreeNode Memebers

    public CategoryView Parent { get; private set; }

    private List<CategoryView> _childNodes;
    public IEnumerable<CategoryView> ChildNodes {
        return _childNodes;
    }

    #endregion

    public static CategoryView CreateFrom(Category category) {
        // here I can set the CategoryView.Name property
    }
}

Все идет нормально. Однако я хочу сделать интерфейс ITreeNode повторно используемым, а для некоторых других типов дерево не должно быть доступно только для чтения. Мы не можем сделать это с помощью вышеупомянутого только для чтения ITreeNode, поэтому я хочу, чтобы ITreeNode был таким:

public interface ITreeNode<T> {
    // has setter
    T Parent { get; set; }
    // use ICollection<T> instead of IEnumerable<T>
    ICollection<T> ChildNodes { get; }
}

Но если мы сделаем ITreeNode доступным для записи, то мы не сможем сделать дерево категорий доступным только для чтения, это не хорошо.

Поэтому я думаю, что если мы сможем сделать так:

public interface ITreeNode<T> {
    T Parent { get; }
    IEnumerable<T> ChildNodes { get; }
}

public interface IWritableTreeNode<T> : ITreeNode<T> {
    new T Parent { get; set; }
    new ICollection<T> ChildNodes { get; }
}

Это хорошо или плохо? Есть ли лучшие дизайны? Большое спасибо! :)

1 ответ

Решение

Одна вещь, которую вы можете попробовать, это использовать List для ваших элементов IEnumerable, которые вы хотите только для чтения. Затем, когда вы заполняете свою древовидную структуру, вы можете внутренне вызвать метод AsReadOnly() в своем списке, который вернет ReadOnlyCollection, и потребители ваших данных не смогут изменять содержимое коллекции.

Этот подход не является ReadOnly с точки зрения интерфейса, но попытка вызвать метод, подобный Add для коллекции, потерпит неудачу и вызовет исключение.

Чтобы защитить других членов, вы можете создать собственный закрытый флаг только для чтения в классе реализации ITreeNode внутри класса, а затем установить свой флаг только для чтения на кэшированных элементах.

Что-то вроде этого...


public class TreeNode : ITreeNode
{
    private bool _isReadOnly;
    private List<ITreeNode> _childNodes = new List<ITreeNode>();

    public TreeNode Parent { get; private set; }

    public IEnumerable<ITreeNode> ChildNodes
    {
        get
        {
            return _isReadOnly ? _childNodes.AsReadOnly() : _childNodes;
        }
    }
}
Другие вопросы по тегам