Как избежать дублирования кода статического метода в нескольких классах

У меня есть несколько классов, содержащих дублированный код, особенно члены и, что наиболее важно, статический метод, который создаст новый экземпляр класса и вернет этот экземпляр: либо ранее созданный экземпляр, зарегистрированный в словаре, либо новый экземпляр путем вызова конструктора.

Интерфейс - это не вариант, потому что у меня есть статический метод. Я попытался решить проблему, представив базовый класс, реализующий этот статический метод, но не могу найти способ правильно создать и вернуть конкретный дочерний класс.

Ниже приведен пример кода текущей ситуации с классом A и классом B, показывающий дублированный код.

public class A
{
    private static readonly IDictionary<string, A> Registry = new Dictionary<string, A>();
    public string Name { get; set; }

    public A(string name)
    {
        this.Name = name;
    }

    public static A GetA(string instanceName)
    {
        lock (Registry)
        {
            if (!Registry.TryGetValue(instanceName, out var newInstance))
            {
                newInstance = new A(instanceName);
            }
            return newInstance;
        }
    }
}

А затем в классе B снова есть имя члена и метод GetX ().

public class B
{
    private static readonly IDictionary<string, B> Registry = new Dictionary<string, B>();
    public string Name { get; set; }

    public B(string name)
    {
        this.Name = name;
    }

    public static B GetB(string instanceName)
    {
        lock (Registry)
        {
            if (!Registry.TryGetValue(instanceName, out var newInstance))
            {
                newInstance = new B(instanceName);
            }
            return newInstance;
        }
    }
}

Есть ли возможность избежать такого дублирования кода, введя базовый класс или любым другим способом?


person plm36021    schedule 03.01.2019    source источник
comment
Mabe создает базовый метод, принимая универсальный тип, а затем создает из него экземпляр. см. stackoverflow.com/questions/731452/   -  person Malior    schedule 03.01.2019
comment
Но если словарь будет статическим, тогда вы будете использовать один и тот же словарь между A и B, что может быть не тем, что вам нужно.   -  person Michał Turczyn    schedule 03.01.2019
comment
Предназначен ли конструктор public видимостью?   -  person Spotted    schedule 03.01.2019
comment
Почему вы хотите ограничить количество экземпляров на основе instanceName? Разрешено ли, что и экземпляр A, и экземпляр B с одинаковым instanceName?   -  person Spotted    schedule 03.01.2019
comment
Name может быть частью базового класса / интерфейса. Что касается GetX, вы не можете избежать его объявления, если вы не можете сделать его универсальным, тогда базовый класс - это то место, где он обычно должен быть. Вы по-прежнему можете объявить статический метод конкретного типа для вызова этого универсального метода для удобства пользователей этого типа.   -  person Sinatr    schedule 03.01.2019
comment
@Spotted На самом деле я думал, что это должно быть публично. При использовании предложения Activator.CreateInstance возникает исключение с частным конструктором.   -  person plm36021    schedule 04.01.2019


Ответы (4)


Это может быть немного чище:

public class B: RegistryInstance<B>
{
    public string Name { get; set; }

    public B(string name)
    {
        this.Name = name;
    }
}

public class A : RegistryInstance<A>
{
    public string Name { get; set; }

    public A(string name)
    {
        this.Name = name;
    }
}

public abstract class RegistryInstance<T> where T:class
{
    protected static readonly IDictionary<string, T> Registry = new Dictionary<string, T>();

    public static T GetInstance(string instanceName)
    {
        lock (Registry)
        {
            if (!Registry.TryGetValue(instanceName, out var newInstance))
            {
                newInstance = (T)Activator.CreateInstance(typeof(T), new object[] { instanceName });
                Registry.Add(instanceName, newInstance);
            }
            return newInstance;
        }
    }
}
person John Babb    schedule 03.01.2019
comment
Вы забыли добавить к Registry при создании нового экземпляра. Таким образом, он никогда не попадет в словарь. - person Anderson Pimentel; 03.01.2019
comment
@AndersonPimentel, заметил, как только выложил. Спасибо, что заметили. Кстати, отличный ответ. - person John Babb; 03.01.2019
comment
Спасибо @JohnBabb за полезный подход. - person plm36021; 03.01.2019

Вы ищете общий базовый класс?

public abstract class BaseRegistryGetter<T>
{
    private static readonly IDictionary<string, T> Registry = new Dictionary<string, T>();
    public string Name { get; set; }

    public BaseRegistryGetter(string name)
    {
        this.Name = name;
    }

    public static T GetValue (string instanceName, Func<string, T> creator) {
        lock (Registry)
        {
            if (!Registry.TryGetValue(instanceName, out var newInstance))
            {
                newInstance = creator(instanceName);
            }
            return newInstance;
        }
    }
}

А затем используйте это так:

public class A : BaseRegistryGetter<A>
{
    public A(string name) : base(name)
    {
    }

    public static A GetA(string instanceName)
    {
        return BaseRegistryGetter<A>.GetValue(instanceName, s => new A(s));
    }
}

Источник неудобного подхода к проверке наличия строкового конструктора для A можно найти здесь.

person Compufreak    schedule 03.01.2019

Я думаю, это должно сработать. Вы можете адаптировать его под свои нужды. Кроме того, в вашем коде была ошибка: вы забыли добавить Registry при создании нового экземпляра.

class Program
{
    static void Main(string[] args)
    {
        A a1 = A.GetInstance("a");
        A a2 = A.GetInstance("aa");
        A a3 = A.GetInstance("a");

        B b1 = B.GetInstance("a");
        B b2 = B.GetInstance("aa");
        B b3 = B.GetInstance("a");

        Console.WriteLine(a1 == a2); //false
        Console.WriteLine(a1 == a3); //true

        Console.WriteLine(b1 == b2); //false
        Console.WriteLine(b1 == b3); //true

        Console.ReadKey();
    }
}

public class A : Generic<A>
{
    public A(string name)
        : base(name)
    {
    }
}

public class B : Generic<B>
{
    public B(string name)
        : base(name)
    {
    }
}

public abstract class Generic<T> where T : Generic<T>
{
    private static readonly IDictionary<string, T> Registry = new Dictionary<string, T>();
    public string Name { get; set; }

    public Generic(string name)
    {
        this.Name = name;
    }

    public static T GetInstance(string instanceName)
    {
        lock (Registry)
        {
            if (!Registry.TryGetValue(instanceName, out var newInstance))
            {
                newInstance = (T)Activator.CreateInstance(typeof(T), instanceName);
                Registry.Add(instanceName, newInstance);
            }
            return newInstance;
        }
    }
}
person Anderson Pimentel    schedule 03.01.2019

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

 class Base<T> { ... }
 class A: Base<A> { ... }
 class B: A { //How does the generic base class help? }

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

public class Base
{
    static readonly IDictionary<string, Base> Registry = 
        new Dictionary<string, Base>();

    protected static Base GetBase(string instanceName,
                                  Func<Base> creator)
    {
        lock (Registry)
        {
            if (!Registry.TryGetValue(instanceName, out var newInstance))
            {
                newInstance = creator();
            }   

            return newInstance;
        }
    }

    //...
}

И теперь ваши производные типы могут препятствовать использованию строго типизированного делегированного метода:

public class A: Base
{
    public A(string instanceName)
        :base(instanceName)
    {
    }
    public static A GetA(string instanceName)
        => GetBase(instanceName, () => new A(instanceName)) as A;
}

public class B: Base
{
    public B(string instanceName)
        :base(instanceName)
    {
    }
    public static B GetB(string instanceName)
        => GetBase(instanceName, () => new B(instanceName)) as B;
}
person InBetween    schedule 03.01.2019
comment
Вы забыли добавить к Registry в своем первом примере кода? Может ли Registry вместо этого быть ConcurrentDictionary (возможно, где значение Lazy)? - person mjwills; 03.01.2019
comment
@sinatr Извини за беспорядок, мне действительно нужно лечь спать. Это примерно то, что я имел в виду. И да, еще один вариант - сделать Register общим и вызвать Activator.CreateInstance, но это зависит от отражения ... - person InBetween; 03.01.2019