简体   繁体   中英

Composing a common interface with c# library class

Suppose I have a class:

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);
    }
    //}

This is the API for ConcurrentDictionary : https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=netframework-4.7.2 It implements the following interfaces:

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>>

Essentially my class Foo uses a subset of ConcurrentDictionary's API methods.

Now there is a requirement to use a ConcurrentTreeMap<TKey, TValue> in the client class in certain use cases. I have implemented a ConcurrentTreeMap<TKey, TValue> class myself with all the API methods the client class is requiring. This class implements IDictionary<TKey, TValue> and ICollection<KeyValuePair<TKey, TValue>>

I want my Foo client class to be able to use both ConcurrentDictionary and ConcurrentTreeMap. Something like this, simplified:

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);
        }
        //}

This would be easy for languages like Python and Go. If I had access to ConcurrentDictionary's implementation I would obviously just extract a common interface type between the two. Or I could define a composite interface of all the interfaces ConcurrentDictionary implements. But since ConcurrentDictionary is part of some base c# library, I can't do that. Should I use some type of proxy class between ConcurrentDictionary and my custom interface? That seems like a lot of boilerplate code I'd have to write.

The backing data between a treemap and a hashmap(dictionary) is quite different so I also couldn't inherit ConcurrentTreeMap from ConcurrentDictionary (most of its methods are not virtual, anyways).

Foo<TKey, TValue> is essentially a wrapper around a collection. If you want a wrapper around a different collection, then possibly the best approach would be to create an interface:

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

...and have two separate implementations of it - one using an inner ConcurrentDictionary and the other using your ConcurrentTreeMap<TKey, TValue> .

Here's one implementation - I don't know the other because I don't have your ConcurrentTree class:

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);
    }
}

That's the most common practice when we have one interface and multiple implementations. We wouldn't normally try to put both implementations in one class.

You could put them both in one class. You could have both a ConcurrentDictionary and a ConcurrentTreeMap within one class, and then maybe some flag in the constructor would tell the class which inner collection it should use.

But if we follow where that leads, what happens if you need another implementation that uses a different collection, and another, and another? That's an easy problem to solve if you're creating distinct implementations of IFoo , but it would be complex and messy if you tried to put all possible implementations in a single class.

What if this class is complex and the collection is just one small part of it, and you don't want to duplicate the rest of the code just because you want it to work with either collection type?

In that case you would still create an interface to represent the collection, and have separate implementations for ConcurrentDictionary and ConcurrentTreeMap . Then Foo would depend on that abstraction, and not on either concrete collection. Something like this:

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

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

Now Foo just depends on IFooDictionary and doesn't know if the underlying implementation is a ConcurrentDictionary or a ConcurrentTreeMap .

This somewhat lines up with what you said:

If I had access to ConcurrentDictionary's implementation I would obviously just extract a common interface type between the two.

Except that common interface isn't something you extract - it's something you create. You define the interface that describes what your other classes need to use this thing for. Then you just create an adapter or wrapper around the ConcurrentDictionary or ConcurrentTreeMap which directs the methods of the interface to the correct methods of the inner collection - just like in the example above where the AddThings method calls the TryAdd method of the inner dictionary.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM