Составление общего интерфейса с классом библиотеки c # - PullRequest
0 голосов
/ 29 ноября 2018

Предположим, у меня есть класс:

public class Foo<TKey, TValue> {
    private readonly ConcurrentDictionary<TKey, TValue> m_dict;
    public Foo() {
        m_dict = new ConcurrentDictionary<TKey, TValue>();
    }
    public void AddThings(TKey key, TValue val) {
        m_dict.Add(key, val);
    }
    public void RemoveThings(TKey key) {
        m_dict.Remove(key);
    }
    //}

Это API для ConcurrentDictionary: https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=netframework-4.7.2 Он реализует следующие интерфейсы:

public class ConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, IDictionary, ICollection, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>

По сути, мой класс Fooиспользует подмножество методов API ConcurrentDictionary.

Теперь существует требование использовать ConcurrentTreeMap<TKey, TValue> в клиентском классе в определенных случаях использования.Я сам реализовал класс ConcurrentTreeMap<TKey, TValue> со всеми методами API, которые требуются клиентскому классу.Этот класс реализует IDictionary<TKey, TValue> и ICollection<KeyValuePair<TKey, TValue>>

Я хочу, чтобы мой клиентский класс Foo мог использовать как ConcurrentDictionary, так и ConcurrentTreeMap.Примерно так, упрощенно:

public class Foo<TKey, TValue> {
        private readonly IConcurrentDictionary<TKey, TValue> m_dict;
        public Foo(IConcurrentDictionary dict) {
            m_dict = dict;
        }
        public void AddThings(TKey key, TValue val) {
            m_dict.Add(key, val);
        }
        public void RemoveThings(TKey key) {
            m_dict.Remove(key);
        }
        //}

Это было бы легко для таких языков, как Python и Go.Если бы у меня был доступ к реализации ConcurrentDictionary, я бы, очевидно, просто извлек бы общий тип интерфейса между ними.Или я мог бы определить составной интерфейс всех интерфейсов, которые реализует ConcurrentDictionary.Но поскольку ConcurrentDictionary является частью некоторой базовой библиотеки c #, я не могу этого сделать.Должен ли я использовать некоторый тип прокси-класса между ConcurrentDictionary и моим пользовательским интерфейсом?Кажется, мне нужно написать много шаблонного кода.

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

1 Ответ

0 голосов
/ 29 ноября 2018

Foo<TKey, TValue> по сути является оберткой вокруг коллекции.Если вы хотите обертку вокруг другой коллекции, то, возможно, лучшим подходом будет создать интерфейс:

public interface IFoo<TKey, TValue>
{
    void AddThings(TKey key, TValue val);
    void RemoveThings(TKey key);
}

... и иметь две его отдельные реализации - одну с использованием внутреннего ConcurrentDictionary идругой использует ваш ConcurrentTreeMap<TKey, TValue>.

Вот одна реализация - я не знаю другую, потому что у меня нет вашего ConcurrentTree класса:

public class ConcurrentDictionaryFoo : IFoo<TKey, TValue>
{
    private readonly ConcurrentDictionary<TKey, TValue> _dictionary 
        = new ConcurrentDictionary<TKey, TValue>();

    void AddThings(TKey key, TValue val)
    {
        _dictionary.TryAdd(key, val);
    }

    void RemoveThings(TKey key)
    {
        _dictionary.TryRemove(key);
    }
}

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

Вы можете поместить их обе в один класс.Вы можете иметь и ConcurrentDictionary, и ConcurrentTreeMap в одном классе, а затем, возможно, какой-нибудь флаг в конструкторе скажет классу, какую внутреннюю коллекцию он должен использовать.

Но если мы поймем, к чему это приведет, что произойдет, если вам понадобится другая реализация, использующая другую коллекцию, и другую, и другую?Эту проблему легко решить, если вы создаете отдельные реализации IFoo, но она будет сложной и запутанной, если вы попытаетесь поместить все возможные реализации в один класс.

Что если этот класс сложныйи коллекция - это лишь небольшая его часть, и вы не хотите дублировать остальную часть кода только потому, что хотите, чтобы он работал с любым типом коллекции?

В этом случае вы все равно создадите интерфейс для представления коллекции и будете иметь отдельные реализации для ConcurrentDictionary и ConcurrentTreeMap.Тогда Foo будет зависеть от этой абстракции, а не от какой-либо конкретной коллекции.Примерно так:

public class Foo<TKey, TValue>
{
    private readonly IFooDictionary<TKey, TValue> _dictionary;

    public Foo(IFooDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }
}

Теперь Foo зависит только от IFooDictionary и не знает, является ли базовая реализация ConcurrentDictionary или ConcurrentTreeMap.

Этонесколько совпадает с тем, что вы сказали:

Если бы у меня был доступ к реализации ConcurrentDictionary, я бы, очевидно, просто извлек бы общий тип интерфейса между ними.

За исключением этого общего интерфейсаэто не то, что вы извлекаете - это то, что вы создаете.Вы определяете интерфейс, который описывает, для чего другие ваши классы должны использовать эту вещь.Затем вы просто создаете адаптер или оболочку для ConcurrentDictionary или ConcurrentTreeMap, которая направляет методы интерфейса на правильные методы внутренней коллекции - как в примере выше, где метод AddThings вызывает TryAddметод внутреннего словаря.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...