Рефакторинг статического класса для использования с инъекцией зависимости

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

Я думал о том, чтобы представить его как одноэлементный класс. Таким образом, это будет в классе экземпляра, но будет только один экземпляр, поэтому мне не придется беспокоиться об изменении настроек или инициализации. Что вы думаете об этом подходе? Я новичок в шаблоне инъекции зависимостей, и я не уверен, что одноэлементный шаблон является хорошим решением? Каким будет ваше решение в аналогичном случае?

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

Изменить 2: Вот подписи некоторых методов:

public static void Initialize(int someParameter)
// Parameter can only be changed by re-initalization which
// will reset all the settings back to their default values.

public static float[] Method1(int someNumber, float[] someArray)

public static void ChangeSetting(string settingName, int settingValue)

Ответ 1

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

public class MyWrapper
{
    public MyWrapper()
    {
        // Do any necessary instance initialization here
    }

    static MyWrapper()
    {
        UnManagedStaticClass.Initialize();
        UnManagedStaticClass.Settings = ...;
    }

    public void Method1()
    {
        UnManagedStaticClass.Method1();
    }
}

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

public class MyWrapper
{
    public MyWrapper()
    {
        // Do any necessary instance initialization here
    }

    static MyWrapper()
    {
        UnManagedStaticClass.Initialize();
    }

    static object lockRoot = new Object();

    public void Method1()
    {
        lock (lockRoot)
        {
            UnManagedStaticClass.Settings = ...;
            UnManagedStaticClass.Method1();
        }
    }
}   

Если вам нужно передать параметры инициализации в свой конструктор экземпляра класса, то вы тоже можете сделать это с помощью поля статического флага:

public class MyWrapper
{
    public MyWrapper(InitParameters p)
    {
        lock (lockRoot)
        {
            if (!initialized)
            {
                UnManagedStaticClass.Initialize(p);
                initialized = true;
            }
        }
    }

    static bool initialized = false;
    static object lockRoot = new Object();

    public void Method1()
    {
        lock (lockRoot)
        {
            UnManagedStaticClass.Settings = ...;
            UnManagedStaticClass.Method1();
        }
    }
}

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

public class MyWrapper
{
    public MyWrapper(InitParameters initParameters, Settings settings)
    {
        this.initParameters = initParameters;
        this.settings = settings;
    }

    private InitParameters initParameters;
    private Settings settings;
    static MyWrapper currentOwnerInstance;
    static object lockRoot = new Object();

    private void InitializeIfNecessary()
    {
        if (currentOwnerInstance != this)
        {
            currentOwnerInstance = this;
            UnManagedStaticClass.Initialize(initParameters);
            UnManagedStaticClass.Settings = settings;
        }
    }

    public void Method1()
    {
        lock (lockRoot)
        {
            InitializeIfNecessary();
            UnManagedStaticClass.Method1();
        }
    }
}

Ответ 2

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

public static class LegacyCode
{
    public static void Initialize(int p1, string p2)
    {
        //some static state
    }
    public static void ChangeSettings(bool p3, double p4)
    {
        //some static state
    }
    public static void DoSomething(string someOtherParam)
    {
        //execute based on some static state
    }
}

public class LegacyCodeFacadeService
{
    public void PerformLegacyCodeActivity(LegacyCodeState state, LegacyCodeParams legacyParams)
    {
        lock (_lockObject)
        {
            LegacyCode.Initialize(state.P1, state.P2);
            LegacyCode.ChangeSettings(state.P3, state.P4);
            LegacyCode.DoSomething(legacyParams.SomeOtherParam);
            //do something to reset state, perhaps
        }
    }
}

Вам нужно будет заполнить пробелы немного, но, надеюсь, вы получите эту идею. Дело в том, чтобы установить состояние на статическом объекте на минимальный промежуток времени и заблокировать доступ к нему в течение всего времени, так что ни одно другое вызывающее устройство не может быть затронуто глобальным изменением состояния. Вы должны создать новые экземпляры этого класса, чтобы использовать его, поэтому он полностью инъецируемый и проверяемый (за исключением этапа извлечения интерфейса, который я пропустил для краткости).

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

ИЗМЕНИТЬ

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

public class LegacyCodeFacadeService
{
    private LegacyCodeFacadeService() { }

    public static LegacyCodeFacadeService GetInstance()
    {
        //now we can change lifestyle management strategies later, if needed
        return new LegacyCodeFacadeService();
    }

    public void PerformLegacyCodeActivity(LegacyCodeState state, LegacyCodeParams legacyParams)
    {
        lock (_lockObject)
        {
            LegacyCode.Initialize(state.P1, state.P2);
            LegacyCode.ChangeSettings(state.P3, state.P4);
            LegacyCode.DoSomething(legacyParams.SomeOtherParam);
            //do something to reset state, perhaps
        }
    }
}