简体   繁体   中英

Choosing a Subclass without knowing the name of it in C#

Edit 1:If anyone has a better Title for that, feel free to tell me or edit it yourself.
Edit 2: Thanks for your contribution guys, the answer given is almost what I need with some tweaks and I'm thankful for the small stuff here. Really learnt much today!

Little stuff here that I'm banging my head on for a while now.
I want to create a Slideshow and want the logic in the Image objects itself.

The programm should be able to set a desired transition or just a random one so
I wanted to create a Transition Superclass with the general stuff and spezialize
it in the subclasses. So I have Transitions.cs (with no Code currently inside it)
and no derived class. I want it to be in the way of adding a single .cs file
extending Transitions.cs and not change any other code to implement a new Transition.

The Code I currently have looks something like this but I guess my
description is more helpful than the code

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

I had the idea of something along the lines of static stuff to workaround that
looks like this but it doesn't work I suppose

public class Transitions : MonoBehaviour {

    public static int TransitionID;
    protected static int SubclassCount;

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

I did look into the state design pattern but I don't want to implement it as I just need the state to be chosen once and shortlived. The Image Objects themself only have a lifetime of around a few seconds. I don't want to do the usual if-nesting or just put all the code inside the SlideImages.cs. Is there any good guidance to it or stuff that goes very indepth into inheritance and such stuff?

Appreciate all the input.

There are two straight-forward solutions to what you want to do. Your basic problem is that you want to be able to dynamically add functionality to your program but you don't want to have knowledge of what has been added in order to use it. The most hack-ish way of doing it is to use Actions instead of subclassing. When you want to add another transition you just update an actions list like this:

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

    }
}

The above approach is simple but all of the logic (or at least references to the logic depending on where you implement the actual work for the transitions) is in one place. It also has the potential to get quite messy depending on how many transitions you are adding and how many developers are touching this codebase. It also requires all functionality to be added by someone with access to the full codebase and requires a recompilation each time new transitions are added.

A more complex but much more maintainable and flexible approach in the long term is to use modules (or plugins for our purposes). Each transition in that approach is provided by either a shared module or a specific module and is a subclass of a base AbstractTransition class or an implementation of an ITransition interface depending on how you want to go about doing that. Use post-build tasks to place all of your module dlls in a single directory accessible to your main program (anyone else given permission can put transition module dlls there too). When your program launches, it dynamically loads all dlls from that directory (no recompilation required when new transitions are added as long as the right dlls are in that directory) and pulls out all of the classes implementing that interface. Each of those interface implementations are instantiated and placed into a data structure after which you can use a similar strategy to the above's PerformTransition method to perform a random one or one based on an ID instead of an index. I can edit this question with an example of that structure if you would like.

Edit: You didn't ask for it yet, but here's an example with plugins/modules.

First, create a project to load and run the transitions. This example will use a project called ModuleDemo . Give it a main method like this:

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
}

You'll notice that the method references an AbstractTransition class. Let's create a separate TransitionModule project for that now. This is the project that the modules will reference:

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

Now that we have an abstract transition class for the plugins to implement and a functioning plugin loader, we can go ahead and create a few transition plugins.

I created a Modules folder for this in my solution but it doesn't really matter.

First module in a FlipTransition project:

using System;
using TransitionModule;

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

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

Second module in a SlideTransition project:

using System;
using TransitionModule;

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

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

Note that each of those projects needs to reference the TransitionModule project but the main project doesn't need to know about any of the other projects.

Now we have 2 transition plugins and a plugin loader. Since the plugin loader is going to load the modules from a Modules directory, go to the /bin/Debug directory of the main project and make a Modules directory. Copy all of the dlls from the /bin/Debug directories of the transition plugin projects into that directory as well. All of this can be automated with post-build tasks later on.

Go ahead and run the program. You should get output like this:

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

There's a lot you can do to make this more elegant, but this is at least a simple example of how you can use a plugin-based architecture to provide what you need.

You could try playing around with abstract classes, create a base abstract class for image transitioning;

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

You then create new Transition types that inherit from this base class and have their own implementation of the TransitionToNextImageId method;

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

This allows you to build up some custom transitions however you wish.

-Edit- You would of course fill the dictionary ImageDictionary before calling the GetNextImage method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM