一个操作系统设计问题:文件类型与相应程序相关联

4
我正在使用Unity3D和C#创建一种伪游戏操作系统(有点雄心勃勃)。 UA Crosslink 请注意,这是一个纯设计问题,您可以在不了解Unity的情况下回答。
以下是我的设计:
一个抽象的FILE,具有以下子元素:
  1. TextFile
  2. MediaFile
  3. ImageFile
还有一个抽象的Application,具有以下子元素:
  1. TextViewer
  2. MediaPlayer
  3. ImageViewer
显然,TextViewer应打开TextFileMediaPlayer应打开MediaFileImageViewer应打开ImageFile
现在,我想了一会儿,应该把“谁打开谁”的信息放在哪里呢?我应该让每个文件自己打开吗?通过在FILE中创建一个抽象的Open,并在子文件中覆盖它来实现? - 我认为这没有意义,因为一个文件并不真正知道如何打开自己,是应用程序知道如何打开文件。例如,PDF文件,如果您没有PDF阅读器(Adobe或其他),您无法打开PDF。
这意味着TextViewer应该有一个Open(TextFile text)方法,MediaPlayer应该有一个Open(MediaFile media)方法,而ImageViewer应该有一个Open(ImageFile image)方法。
这可以通过在Application的声明中使用泛型来实现:
public abstract class Application<T> where T : FILE
{
  public abstract void Open(T file);
}

然后:

public class TextViewer : Application<TextFile>
{
    public override void Open(TextFile file)
    {
        Console.WriteLine("TextViewer opening text file...");
    }
}

public class MediaPlayer : Application<MediaFile>
{
    public override void Open(MediaFile file)
    {
        Console.WriteLine("MediaPlayer opening media file...");
    }
}

public class ImageViewer : Application<ImageFile>
{
    public override void Open(ImageFile file)
    {
        Console.WriteLine("ImageViewer opening image file...");
    }
}

这是UML图的样子:

enter image description here

看起来不错...现在有趣的部分是如何绑定/关联每个文件类型与正确的应用程序打开它?例如:右键单击文本文件|获取选项列表:“打开”,“重命名”,“删除”等|选择“打开”-接下来会发生什么?
我想到了一个中介者的想法,位于FILEApplication之间-如果我们花一秒钟思考一下,像Windows这样的真正操作系统是如何做到的呢?-好吧,如果你去开始|默认程序|将文件类型或协议与程序相关联。-您将看到一个类似字典的列表,其中包含键(文件类型)和值(关联程序)-因此,我想到了做这件事,创建一个字典,通过它我注册文件类型与应用程序。这就是我卡住的地方...
首先,我应该在字典中使用什么类型的键和值?-我想了一会儿,得出结论应该是类似于<Type,Application>-但问题是,我不能只写Application,我必须指定通用的T参数:(
我通过创建一个中间类App来实现这个,然后让我的Application<T>继承它:
public abstract class App { }

public abstract class Application <T> : App where T : FILE { /* stuff... */ }

好的,现在我可以让我的字典成为<Type, App>

public static class Mediator
{
    static private TextViewer tv;
    static private MediaPlayer mp;
    static private ImageViewer iv;
    static private Dictionary<Type, App> dic = new Dictionary<Type, App>();

    static Mediator()
    {
        tv = new TextViewer();
        iv = new ImageViewer();
        mp = new MediaPlayer();

        // register what we have
        dic.Add(typeof(TextFile), tv);
        dic.Add(typeof(MediaFile), mp);
        dic.Add(typeof(ImageFile), iv);
    }

    static public void Open(FILE file)
    {
        App app = dic[file.GetType()];
        if (app == null)
        {
            Console.WriteLine("No application was assigned to open up " + file);
            return;
        }

        app. // here is where my hack falls short, no Open method inside App :(
    }
}

抱歉,操作失败了!- 你可以看到,我尽力避免类似的事情:
if (file is TextFile)
  // open with text viewer
else if (file is MediaFile)
  // open with media player
else if (file is ImageFile)
  // open with image viewer
else if etc

这对我来说就像是一场噩梦! 为了避免麻烦,我可以将我的应用程序变成单例模式,这样每个文件中:
public class TextFile : FILE
{
   public override void Open()
   {
      TextViewer.Instance.Open(this);
   }
}

其余文件同理,但我不想这样做,我不想放弃使用单例!如果我希望我的MediaPlayerTextViewer拥有多个实例而不仅仅是一个呢?
我已经苦苦挣扎了很长时间了,希望我表达的问题清晰明了。我只是想知道,如何以正确的方式将文件类型与正确的应用程序关联起来(例如在Windows中)- 如何以优雅、健壮的方式实现我所追求的目标? - 我能使用设计模式来解决这个问题吗? - 我上面的所有尝试,我想得对吗?我离解决方案近了吗?
非常感谢您提前的任何帮助。

@terrybozzio:我本来想标记unity3d,但是后来发现几乎没有相关内容——这是一个纯设计问题。所以我认为操作系统更合适。无论如何... - vexe
很可能你想要的是这个链接:http://gamedev.stackexchange.com/questions/tagged/unity - Slipp D. Thompson
2个回答

1
也许这对你有帮助,与其在应用程序类中声明一个抽象方法并创建泛型,你可以创建一个接口并实现其中的open方法,如下所示:
    public interface IFile<T>
    {
        void Open(T path);
    }

    public abstract class Application
    {
        //public abstract void Open();
    }


    public class TextViewer : Application, IFile<TextFile>
    {
        public void Open(TextFile path)
        {
            //open textfile....
        }
    }

    public class MediaPlayer : Application, IFile<MediaFile>
    {     
        public void Open(MediaFile path)
        {
            //open media file...
        }
    }

    public class ImageViewer : Application, IFile<ImageFile>
    {
        public void Open(ImageFile path)
        {
            //open imagefile....
        }
    }

谢谢您的回复,这绝对很有趣!但这只是解决问题的一半,那么您将如何解决关联/绑定问题呢? - vexe
你是指你在问题中提到的那个例子吗?右键单击文本文件和你所问的那些选项?...我真的不知道设计,但大致上,如果你有一个上下文菜单(例如)带有open()命令,如果用户点击打开菜单项,在处理程序中,你首先检查文件扩展名(这是一种快速检查方式),如果是.txt,则TextViewer.Open(TextFile path),其中路径TextFile变量是基于所选文件创建并传递给打开方法。 - terrybozzio

1

如果没有对设计进行大量思考,也不了解Unity或者文件的真正表现形式,我会采用基于接口而非通用的方法。

首先,我会定义一个接口来定义我的文件类,即IFile,并在每个文件中实现它。

public interface IFile
{

}

public class MediaFile : IFile
{
    ...
}

接下来,我将定义一个接口来定义我的应用程序类,即IApplication,并在每个应用程序中实现它。
public interface IApplication
{
    Type GetSupportedApplicationType();
    void OpenFile(IFile oFile);
}


public class MediaApplication : IApplication
{

    #region IApplication Members

    public Type GetSupportedApplicationType()
    {
        return typeof(MediaFile);
    }

    public void OpenFile(IFile oFile)
    {
        // do the work
    }

    #endregion
}

文件和应用程序之间的链接将是一个字典,其中包含文件类类型作为键和应用程序类类型作为值:
<GetType(MediaFile), GetType(MediaPlayer)>

当中介者传递要执行的文件时,它可以使用文件类型在字典中搜索适当的应用程序类型来找到适当的应用程序。
一旦找到适当的应用程序类型,您可以使用System.GetActivator创建一个实例。然后,由于您知道它实现了IApplication,因此可以在应用程序上执行诸如OpenFile(IFile)之类的方法。
唯一的技巧是创建初始字典条目。为此,我实际上会使用反射来收集实现IApplication的类列表。
一旦这样做,您就可以使用System.GetActivator创建每个类的实例,然后执行已知方法,例如返回IApplication支持的IFile实现的完整类型的GetSupportedFileType
(下面的代码采用了注册应用程序的快捷方式,这比实现反射方法更容易)
public static class Mediator
{
    static private Dictionary<Type, Type> dic = new Dictionary<Type, Type>();

    static Mediator()
    {
        RegisterApp(new MediaApplication());
    }

    static void RegisterApp(IApplication oApp)
    {
        dic.Add(oApp.GetSupportedApplicationType(), oApp.GetType());
    }

    static public void Open(IFile file)
    {
        Type appType = dic[file.GetType()];
        if (appType == null)
        {
            Console.WriteLine("No application was assigned to open up " + file);
            return;
        }

        IApplication app = (IApplication)System.Activator.CreateInstance(appType);
        app.OpenFile(file);
    }
}

太棒了!但是有一个问题:在你的MediaApplication中,你使用了Open方法并传入了一个IFile参数——这意味着我需要将它转换成MediaFile(因为我需要获取MediaFile的信息),对吗? - vexe
是的,每个OpenFile实现都需要转换为适当的类型,假设您实际上需要知道类型以执行操作。如果可能的话,处理这种情况的最佳方法是尽可能多地添加到IFile接口中。这种方法还允许单个应用程序支持多种文件类型;只需修改OpenFile以测试IFile的类型并相应地行为即可。 - competent_tech
好的,我想这个方法可以用,但是让我试试 :D - 但是你不觉得如果每个应用程序在其“打开”方法中使用正确类型的文件会更优吗?- 我的意思是,如果有人进来并输入 mediaPlayer.Open( 并看到一个 IFile 作为参数,这意味着 任何 实现了 IFile 的东西都应该能够很好地适应...(而且这有点真实,我的意思是我们将检查以确保我们获得正确的文件类型,但还是,你知道的...希望你能理解我)- 或许有一种方法可以避免这种情况? - vexe
1
选择接口作为控制元素的主要原因是易于维护、实现和单元测试,而不是抽象类。当你实现抽象类时,随着时间的推移,你的实现会变得更加僵硬,因此更加脆弱。然而,接口和抽象类并不是互斥的。你完全可以实现一个所有文件都继承的基本抽象类,并且该类是实现IFile的类。然而,使用接口允许你为不同类型的数据(例如URL)引入第二个基本类。 - competent_tech
枚举对于您的即时需求可能是可以的,但当我像这样设计时,我会尝试考虑未来的情况,并在当时想到最具有未来性的设计。例如,使用System.Type允许您轻松支持由您或其他人创建的插件以支持不同的文件类型。使用枚举将防止(或严重限制)这种能力,因为枚举要么必须在原始应用程序中注册,要么每个插件都必须同意使用某个范围内的值。 - competent_tech
显示剩余3条评论

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