重构静态类以便与依赖注入一起使用

15

我们需要在代码中使用一个具有静态方法的非托管库。我想将该库的操作引入我的代码作为依赖项。除了具有静态方法之外,该库还具有初始化方法和设置方法,这两个方法都是全局的。因此,我不能只是将其封装在实例类中,因为如果一个实例更改了设置,所有其他实例都会受到影响,如果一个实例被初始化,所有其他实例都将被重新初始化。

我考虑将它引入为单例类。这样它将在一个实例类中,但只会有一个实例,因此我不必担心更改设置或初始化。你觉得这种方法如何?我对依赖注入模式还比较陌生,我不确定单例模式是否是一个好的解决方案?如果遇到类似情况,你会采取什么样的解决方案呢?

编辑: 初始化方法还带有一个参数,因此我不能仅仅锁定方法调用并每次调用时重新初始化和更改设置。

编辑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)

你试图包装的类是静态的还是只有一些静态方法?你的包装器需要提供任何额外的功能吗? - JMan
你的想法看起来很合理。 - sll
@Jeroen,它需要为一些方法提供重载。库中的所有方法都采用数组,并且我想将它们包装在类中。该库是用C++编写的,我正在尝试包装的类是静态的,而不仅仅是函数。 - hattenn
你需要在设置中多次进行设置吗?或者在应用程序启动时可以一次性完成吗? - Steven Doggart
返回类型?我猜是void... - levelnis
显示剩余6条评论
2个回答

10

如果您只需要在启动时设置一次设置,则建议创建一个非静态的包装类,该类在其自己的静态构造函数中执行所有静态类的初始化。这样,您可以确保它只会发生一次:

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

这不仅仅是关于线程安全的问题。这样,如果一个实例更改了设置,所有其他实例也会受到影响。 - hattenn
如果您想要一个更详细的答案,您需要提供一个更详细的问题。 - Steven Doggart
我不需要更详细的答案,也不要把它当作个人攻击。我只是想澄清情况,解释为什么你的解决方案行不通。感谢您的时间。 - hattenn
两个线程肯定都能同时调用它,而且是安全的,但它们会按顺序处理。第二个线程调用它时将挂起等待,直到第一个线程对其的调用完成。由于所有实例都共享一个基础全局资源,这就是我认为你希望它工作的方式,除非像你暗示的那样,所有实例都将共享相同的设置。 - Steven Doggart
我认为你不需要单例模式。如果你使用单例模式,你将会不必要地把自己限制在一个角落里,以后就无法支持具有不同设置的不同实例。如果你不需要它们有不同的设置,只需将其作为典型的实例类,其中每个实例都采用设置,但只有第一个实例实际上使用设置来初始化底层静态类(使用“static bool initialized”)。 - Steven Doggart
显示剩余10条评论

1

我会使用一个无状态的服务类,并在每个方法调用中传递状态信息给静态类。不了解你的类的任何细节,我将给出另一个使用c#静态类的示例。

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

我不想经常重新初始化对象,而且据我所知,这种方式如果需要并行化将会很困难。感谢您的建议。 - hattenn
如果您使用单例,将来肯定无法进行并行处理。根据需要重置设置是解决该问题的唯一方法。 - Steven Doggart
无论如何,您不能并行执行需要使用不同全局状态的操作。初始化是否昂贵?如果不是,则无需关心。如果是,则仅在某些初始化参数更改时进行初始化。 - tallseth
@StevenDoggart 我不明白为什么如果是单例模式就不能并行化。我可以在同一时间内多次调用方法(除了初始化和设置方法)。如果以这种方式锁定,它将总是需要等待操作结束。 - hattenn
是的,我知道,但如果它们不需要不同的设置,那么我可以这样做。正如我在问题中所说的那样,我的最大问题不是拥有库的不同实例,而是想要能够将其包装在一个实例类中,以便我可以从中提取接口并使用依赖注入模式。 tallseth建议的这种方式,即使我只有一个对象,该方法也会被锁定,并且我绝对无法同时进行多个调用。 - hattenn
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接