跨多个静态 类 调用一个公共方法?

Calling a common method across multiple static classes?

快速说明

此 post 中的代码构建在内部构建的 DirectX-11 引擎之上,这意味着它遵循以下严格模式:

Initialize
while (Running) {
    Update
    Render
}

但是,不要让这吓到你,因为问题与 DirectX 代码无关,而是 static classes 和方法。


概览

我有一个名为 RenderObject 的 class,其中包含一个名为 Initialize 的方法。此方法负责构建对象的网格、分配纹理、着色器等。

public class RenderObject {
    public virtual void Initialize() { }
}

我还有一些 static class 包含可重复使用的资产,例如常用纹理、着色器、模型和网格。这样我以后就不必重新加载它们了。所有这些 static classes 还包含一个名为 Initialize 的方法,该方法负责创建这些可重用资产。对于这个问题,我将仅限于 Textures class.

public static class Textures {
    public static Texture2D Dirt { get; private set; }
    public static Texture2D Grass { get; private set; }
    public static void Initialize() {
        Dirt = new Texture2D(...);
        Grass = new Texture2D(...);
    }
}

最后,我有一个名为LoadingSystem的class,它负责加载可重用资产和初始化对象。我在我引擎的Initialize方法里面初始化这个class,然后在引擎的Update方法中分别调用class'Update方法。 LoadingSystemUpdate 方法负责使用 Queue 加载和初始化对象,这对于提供流畅的视觉反馈很有用。

 public class LoadingSystem {
     public bool Loading { get; private set; } = true;
     private Queue<RenderObject> objectsToRender;
     public void AddForLoad(RenderObject obj) => objectsToRender.Enqueue(obj);
     public void Update() {
         if (objectsToRender.Count > 0) {
             RenderObject obj = objectsToLoad.Dequeue();
             obj.Initialize();
         } else Loading = false;
     }
 }

问题

我想在这些 static class 上使用与 RenderObject 队列相同的进程调用方法 Initialize。目前我被迫做:

CurrentMessage = "Loading Textures";
Render();
Present();
Textures.Initialize();
Progress = ++objectsLoaded / objectsToLoad;
CurrentMessage = "Loading Shaders";
Render();
Present();
Shaders.Initialize();
Progress = ++objectsLoaded / objectsToLoad;
CurrentMessage = "Loading Models";
Render();
Present();
Models.Initialize();
Progress = ++objectsLoaded / objectsToLoad;

我已经将它简化为一个处理消息重复设置的方法,并调用 RenderPresent 但这仍然很乏味,应该通过 Update 每个对象一次方法,以与代码的其余部分保持一致。


我的想法

我知道 static class 不能从 class 继承或实现 interface 所以我想知道是否有办法提供 static class 并以类似的方式调用其 Initialize 方法;即使这意味着创建一个单独的方法来完成它。

我目前考虑了两个方案:

第一个选项的问题是我有 12 static classes 并且必须更新进度和反馈消息、引发事件并为每个重新渲染场景。

第二个选项的问题是这些 static classes 只包含 static 属性,因此根据定义应该是 static 因为没有必要曾经继承它们或创建它们的实例。


问题

有没有办法跨多个静态 classes 调用通用方法?

虽然我希望有比这更好更详细的答案;我想出了一个基本的解决方案。我创建了一个单独的 Queue<Type>,并在其中添加了 static 类。然后,我使用以下内容调用他们的 Initialize 方法:

Type t = typesToInit.Dequeue();
t.GetMethod("Initialize").Invoke(null, new object[] { 0 });

这很好用而且很干净,但我不禁想知道是否有更好的方法来做到这一点?

I have currently considered two options:

  • Load static classes individually.
  • Convert static classes to instance classes and call them with the queue.

第三种折衷方法与您上面的第二个想法相关,但使用称为单例模式的设计模式。与静态 classes 一样,您的进程中只能有一个,每个人都得到同样的东西,但是与静态 classes 不同的是,单例可以实现接口,甚至可以继承其他 class es.

对于这个例子,我将使用接口方法。

public interface IInitializable
{
    void Initialize();
}

接口所做的只是强制其实现者具有 Initialize 方法。

我的下一步是创建一个 Singleton class。有几个规则可以实现单例模式。你的class必须封印。它的构造函数必须是私有的。它必须有一个静态方法或 属性 到 return 单个实例。 method/property 必须是线程安全的。

我已经使用 Lazy 为我完成繁重的工作

public sealed class Foo : IInitializable
{
    public void Initialize()
    {
        // Initialize my foo
    }

    private Foo()
    {
    }

    private static Lazy<Foo> fooLazy = new Lazy<Foo>(() => new Foo());

    public static Foo Instance => fooLazy.Value;
}

与您使用静态 classes 所做的事情有一些细微差别。如果 Foo 是静态的 class,你会调用 Foo.Initialize(); 因为它是 Singleton,你会调用 Foo.Instance.Initialize();

任何其他方法或属性很可能是非静态的。

综合起来,您可以编写这样的代码。您的队列不需要知道它持有的 classes。你其实并不在乎。你只想知道它有 Initialize() 方法

   public class YourClass
    {
        private Queue<IInitializable> objectsToLoad = new Queue<IInitializable>();

        public void Enqueue(IInitializable obj)
        {
            this.objectsToLoad.Enqueue(obj);
        }

        public void LoadOrUpdate()
        {
            // Update Method
            if (objectsToLoad.Count > 0)
            {
                IInitializable obj = objectsToLoad.Dequeue();
                obj.Initialize();
            }
            else
            {
                // Loading complete.
            }
        }
    }

这个class可以这样使用

    YourClass yourClass = new YourClass();
    yourClass.Enqueue(Foo.Instance);
    yourClass.LoadOrUpdate();