【问题标题】:Choosing a Subclass without knowing the name of it in C#在 C# 中选择子类而不知道它的名称
【发布时间】:2017-11-24 10:28:31
【问题描述】:

编辑 1:如果有人对此有更好的标题,请随时告诉我或自己编辑。
编辑2:感谢你们的贡献,给出的答案几乎是我需要的一些调整,我很感谢这里的小东西。今天真的学到了很多!

这里有一些小东西,我现在正在研究一段时间。
我想创建一个幻灯片,并希望图像对象本身的逻辑。

程序应该能够设置所需的过渡或只是随机的,所以
我想用一般的东西创建一个过渡超类并进行特殊化
它在子类中。所以我有 Transitions.cs(目前里面没有代码)
并且没有派生类。我希望它妨碍添加单个 .cs 文件
扩展 Transitions.cs 并且不更改任何其他代码来实现新的 Transition。

我目前拥有的代码看起来像这样,但我猜我的
描述比代码更有帮助

public class SlideImages : MonoBehaviour {

    Image image;
    Image nextImage;
    int tracker;

    private void Transition(int ID)
    {
        /*Something to choose a transition based on the ID
        *Transitions.cs is the superclass of all different transitions
        *f.e. Ken-Burns Effect, or scattered Transition which all extend from it
        */
    }

    ~SlideImages()
    {
        //TODO: Pop and Push
    }
}

我想到了一些类似于静态的东西来解决这个问题
看起来像这样,但我想它不起作用

public class Transitions : MonoBehaviour {

    public static int TransitionID;
    protected static int SubclassCount;

    protected static void SetID()
    {
        TransitionID = Transitions.SubclassCount;
        Transitions.SubclassCount++;
    }
}

我确实研究了状态设计模式,但我不想实现它,因为我只需要选择一次状态并且是短暂的。图像对象本身只有几秒钟左右的生命周期。我不想做通常的 if 嵌套,或者只是将所有代码放在 SlideImages.cs 中。有没有什么好的指导或者非常深入的继承之类的东西?

感谢所有输入。

【问题讨论】:

  • 随机值上的 switch case 会根据值返回特定的转换类型呢?
  • 我不太清楚您要做什么。 听起来你想要一个(可能是抽象的)Transition 类型,然后是从它继承的各种类型。但是在你的代码中发生了什么?那么“根据id选择过渡”是什么意思?如果每个图像都有一个Transition 对象(无论实现类型如何),那么它不能只调用该对象所需的逻辑吗?
  • @Isuka 必须有更好的方法来做到这一点,因为如果我想插入另一个转换,我必须手动更改代码。把它想象成一个基于网络的建筑软件。客户可以根据自己的需要编写自己的转换代码(或以这种方式编写的任何其他代码),然后将其上传到文件夹中。然后一切都被构建并自动实现而无需更改代码
  • @David 是的,目前我没有从它继承任何东西,因为我不想编写甚至不起作用的代码。图像没有Transition 对象,但更像是与派生类关联的数字。不一定是数字,只是用来标识子类的东西它必须以某种方式从另一个文件中调用逻辑,该文件应该是 Transitions.cs 的一部分,因为我想在行为中设置一些相似性和边界
  • @Firro:所以你需要Transition 对象的静态集合之类的东西,并且每个图像都会根据 ID 从该集合中提取?如果您有List<Transition>,那么您可以简单地执行以下操作:transitions.Single(t => t.ID == id),其中transitions 是列表,id 是您要在其中查找的 ID。

标签: c# inheritance plugins module subclass


【解决方案1】:

对于您想要做的事情,有两种直接的解决方案。您的基本问题是您希望能够为程序动态添加功能,但您不想知道为了使用它而添加了什么。最骇人听闻的做法是使用Actions 而不是子类化。当您想添加另一个转换时,您只需更新如下操作列表:

public static class Transitions
{
    private static Action[] TransitionStrategies = new Action[]
    {
        () => { /* One way of performing a transition */ },
        () => { /* Another way of performing a transition */ },
        () => { /* Keep adding methods and calling them here for each transition type */ }
    }

    public static void PerformTransition(int? transitionIndex = null)
    {
        int effectiveIndex;

        // if the transition index is null, use a random one
        if (transitionIndex == null)
        {
            effectiveIndex = new Random().Next(0, TransitionStrategies.Length);
        }
        else
        {
            effectiveIndex = transitionIndex.Value;
        }

        // perform the transition
        TransitionStrategies[effectiveIndex]();

    }
}

上述方法很简单,但所有逻辑(或至少对逻辑的引用,取决于您在哪里实现转换的实际工作)都在一个地方。它也有可能变得非常混乱,具体取决于您添加了多少过渡以及有多少开发人员正在接触这个代码库。它还要求所有功能都由有权访问完整代码库的人添加,并且每次添加新转换时都需要重新编译。

从长远来看,一种更复杂但更易于维护和灵活的方法是使用模块(或用于我们目的的插件)。该方法中的每个转换都由共享模块或特定模块提供,并且是基本AbstractTransition 类的子类或ITransition 接口的实现,具体取决于您要如何进行。使用构建后任务将所有模块 dll 放在主程序可访问的单个目录中(任何其他获得权限的人也可以将转换模块 dll 放在那里)。当您的程序启动时,它会动态加载该目录中的所有 dll(只要在该目录中添加了正确的 dll,就无需重新编译添加新的转换)并拉出所有实现该接口的类。这些接口实现中的每一个都被实例化并放入数据结构中,之后您可以使用与上述PerformTransition 方法类似的策略来执行基于ID而不是索引的随机一个或一个。如果您愿意,我可以使用该结构的示例来编辑此问题。

编辑:你还没有要求它,但这里有一个插件/模块的例子。

首先,创建一个项目来加载和运行转场。此示例将使用一个名为 ModuleDemo 的项目。给它一个这样的主要方法:

static void Main(string[] args)
{
    // create a list to hold the transitions we load
    List<AbstractTransition> transitions = new List<AbstractTransition>();

    // load each module we find in the modules directory
    foreach (string dllFilepath in Directory.EnumerateFiles("Modules", "*.dll"))
        // this should really read from the app config to get the module directory                
    {
        Assembly dllAssembly = Assembly.LoadFrom(dllFilepath);

        transitions.AddRange(dllAssembly.GetTypes()
            .Where(type => typeof(AbstractTransition).IsAssignableFrom(type))
            .Select(type => (AbstractTransition) Activator.CreateInstance(type)));
    }

    // show what's been loaded
    foreach (AbstractTransition transition in transitions)
    {
        Console.WriteLine("Loaded transition with id {0}", transition.TransitionId);

        // execute just to show how it's done
        transition.PerformTransition();
    }

    Console.Read(); // pause
}

您会注意到该方法引用了一个AbstractTransition 类。现在让我们为此创建一个单独的TransitionModule 项目。这是模块将引用的项目:

namespace TransitionModule
{
    public abstract class AbstractTransition
    {        
        public readonly int TransitionId;
        public abstract void PerformTransition();

        protected AbstractTransition(int transitionId)
        {
            TransitionId = transitionId;
        }

        // you can add functionality here as you see fit
    }
}

现在我们已经有了一个用于插件实现的抽象转换类和一个正常工作的插件加载器,我们可以继续创建一些转换插件。

我在我的解决方案中为此创建了一个Modules 文件夹,但这并不重要。

FlipTransition 项目中的第一个模块:

using System;
using TransitionModule;

namespace FlipTransition
{
    public class FlipTransition : AbstractTransition
    {
        public FlipTransition() : base(2)
        {
        }

        public override void PerformTransition()
        {
            Console.WriteLine("Performing flip transition");
        }
    }
}

SlideTransition 项目中的第二个模块:

using System;
using TransitionModule;

namespace SlideTransition
{
    public class SlideTransition : AbstractTransition
    {
        public SlideTransition() : base(1)
        {
        }

        public override void PerformTransition()
        {
            Console.WriteLine("Performing slide transition");
        }
    }
}

请注意,每个项目都需要引用 TransitionModule 项目,但主项目不需要了解任何其他项目。

现在我们有 2 个过渡插件和一个插件加载器。由于插件加载器将从Modules 目录加载模块,因此转到主项目的/bin/Debug 目录并创建一个Modules 目录。将过渡插件项目的/bin/Debug 目录中的所有 dll 也复制到该目录中。所有这些都可以在以后通过构建后任务自动完成。

继续运行程序。你应该得到这样的输出:

Loaded transition with id 2
Performing flip transition
Loaded transition with id 1
Performing slide transition

您可以做很多事情来使它更优雅,但这至少是一个简单的示例,说明如何使用基于插件的架构来提供您需要的东西。

【讨论】:

  • 我刚回家,让我仔细读一下,我会回来的
  • 顺便说一下,您可能应该使用Guid.NewGuid() 来生成每个模块声明的转换ID,而不是ID 的整数。这样您就可以保证每个模块注册一个唯一的转换 id,而不必知道哪些 id 已经注册。使用它来生成 id,然后将 id 硬编码到转换实现中(您可能希望它在运行之间保持不变,以便您可以存储相关设置/等)。
  • 好的,我仔细看了一遍,很难理解它的要点。我理解这个概念,但有很多东西(实际上几乎所有东西)对我来说都是全新的。我必须深入研究它,但是是的,这就是我想要的。我需要研究一下大会是什么,我学到和喜欢的大多数东西,95% 的在线课程实际上只是基本的东西,这些东西实际上很有帮助,所以我真的很感谢你。
  • 如果您有任何具体问题,请随时在此处发布,或者我们可以继续聊天。如果您希望我发表更多评论,我也可以这样做。一旦你开始了解它,一旦框架到位,开发新的过渡就非常简单和方便。
  • 我也可以为您将示例解决方案放在 github 上,如果这样可以让您更轻松地学习。
【解决方案2】:

您可以尝试使用抽象类,为图像转换创建一个基本抽象类;

public abstract class ImageTransition
{
    protected int imageId { get; set; }
    public Dictionary<int, Image> ImageDictionary { get; set; }

    protected abstract void TransitionToNextImageId();

    public Image GetNextImage()
    {
        TransitionToNextImageId();
        return ImageDictionary[imageId];
    }
}

然后,您创建从该基类继承的新转换类型,并拥有自己的 TransitionToNextImageId 方法实现;

public class InTurnImageTransition : ImageTransition
{
    protected override void TransitionToNextImageId()
    {
        if(this.imageId < ImageDictionary.Count)
            this.imageId ++;
    }
}

public class RandomImageTransition : ImageTransition
{
    protected override void TransitionToNextImageId()
    {
        imageId = new Random().Next(0, ImageDictionary.Count);
    }
}

这使您可以随心所欲地构建一些自定义过渡。

-编辑- 您当然会在调用 GetNextImage 方法之前填写字典 ImageDictionary。

【讨论】:

  • 让我仔细阅读一下,它看起来肯定比我尝试过的任何东西都更有希望
猜你喜欢
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-30
  • 1970-01-01
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多