C#中的跨平台类设计

时间:2010-11-22 15:30:36

标签: c# architecture cross-platform

摘要:我想知道在C#中创建跨平台(例如,桌面,Web和Silverlight)类的最佳设计,没有重复的代码,具有以下优点:每个设计。

我经常为一个应用程序域编写新的有用的类;他们没有理由不跨域工作。如何构建我的代码以使其理想地跨平台?

例如,假设我想创建一个带有interval和on-tick事件的泛型“MyTimer”类。在桌面设备中,这将使用内置的.NET计时器。在Silverlight中,我会使用DispatchTimer。

设计#1可能是“创建一个类并使用预处理器指令进行条件编译”,例如。 “#IF SILVERILGHT ......”。但是,这会导致代码不易理解,易读和易于维护。

设计#2可能是“创建名为DesktopTimer和SilverlightTimer的子类,并使用来自MyTimer的子类。”这会怎么样?

虽然这是一个微不足道的案例,但我可能有更复杂的类,例如,使用特定于平台的类(IsolatedStorage,DispatchTimer等),但不直接替换它们。

我可以使用哪些其他设计/范例?

7 个答案:

答案 0 :(得分:4)

我建议你编写Interface来简化你的平台特定代码。然后,接口确保您的代码将尊重您的接口给出的合同,否则将有代码中断(如果没有实现一个成员)。

此外,在这个库中存在您的特定计时器类,为了坚持您的示例,我将为每个平台创建一个类,因此使用Silverlight的DispatchTimer和内置的.NET计时器桌面版。

最后,您最终只会使用一个界面,只有其实施者知道如何专门处理您的底层平台的合同。

编辑#1

条件设计不是优秀设计的选择。这是一个工具,可以帮助您处理Dependancy Injection,称为Unity Application Block,并用于处理像您这样的场景。

您只使用非常通用的XML配置来“告诉”在需要此接口或该接口时必须实例化的内容。然后,UnityContainer咨询您所做的配置,并为您实例化正确的类。这确保了良好的设计方法和架构。

编辑#2

  

我对依赖注入不是很熟悉,也不熟悉Unity Application Block。你能指出一些资源或进一步解释这些资源吗?

  1. Microsoft Enterprise Library 5.0 - April 2010;
  2. Microsoft Unity 2.0 – April 2010;
  3. Microsoft Unity 2.0 Documentation for Visual Studio 2008;
  4. Are there good tutorial/walkthroughs for unity that don't use configuration files?(关于应该从Unity开始提供有价值提示的主题的问题);
  5. Specifying Types in the Configuration File;
  6. Walkthrough: The Unity StopLight QuickStart;
  7. Walkthrough: The Unity Event Broker Extension QuickStart
  8. 我认为这些资源将指导您完成学习。如果您需要进一步的帮助,请告诉我! =)

    编辑#3

      

    但无论如何,StopLight快速入门[...]似乎暗示接口到具体类的依赖关系映射是在代码中完成的(这对我不起作用)。

    实际上,您可以同时执行代码和XML依赖关系映射,选择权在您手中! =)

    以下是一些您应该启发的示例,使StopLight快速入门使用XML配置而不是编码映射。

    1. Testing Your Unity XML Configuration;
    2. Using Design-Time Configuration;
    3. Source Schema for the Unity Application Block
    4. 如果这对你没有帮助,请告诉我。然后,我将提供一个使用XML依赖关系映射的简单示例。 =)

答案 1 :(得分:1)

我认为接口在这里是一个不错的选择(定义一个定时器做什么而不实际实现它)

public interface ITimer
{
    void CreateTimer(int _interval, TimerDelegate _delegate);
    void StopTimer();
    // etc...
} // eo interface ITimer

由此,您可以得出具体的计时器:

public class DesktopTimer : ITimer
{
} // eo DesktopTimer

public class SilverlightTimer : ITimer
{
} // eo class SilverlightTimer

public class WebTimer : Timer
{
} // eo class WebTimer

然后是有趣的部分。我们如何创建合适的计时器?在这里,你可以实现某种平台工厂,它返回正确的计时器,具体取决于它运行的平台。这是一个快速而肮脏的想法(我会使它比这更动态,并且可能为多种类实现一个工厂,但这是一个例子)

public enum Platform
{
     Desktop,
     Web,
     Silverlight
} // eo enum Platform

public class TimerFactory
{
    private class ObjectInfo
    {
        private string m_Assembly;
        private string m_Type;

        // ctor
        public ObjectInfo(string _assembly, string _type)
        {
            m_Assembly = _assembly;
            m_Type = _type;
        } // eo ctor

        public ITimer Create() {return(AppDomain.CurrentDomain.CreateInstanceAndUnwrap(m_Assembly, m_Type));}
    } // eo class ObjectInfo

    Dictionary<Platform, ObjectInfo> m_Types = new Dictionary<PlatForm, ObjectInfo>();

    public TimerFactory()
    {
        m_Types[Platform.Desktop] = new ObjectInfo("Desktop", "MyNamespace.DesktopTimer");
        m_Types[Platform.Silverlight] = new ObjectInfo("Silverlight", "MyNameSpace.SilverlightTimer");
        // ...
    } // eo ctor

    public ITimer Create()
    {
        // based on platform, create appropriate ObjectInfo
    } // eo Create
} // eo class TimerFactory

正如我上面提到的,我不会每次都有一个工厂,而是建立一个通用的平台工厂,可以处理定时器,容器和其他任何你想要的东西。这只是一个例子。

答案 2 :(得分:1)

跟你认识的所有OOD一起去吧。我建议创建平台无关(Windows,Mono / destkop,web)域模型。使用抽象类来模拟依赖于平台的东西(比如Timer)。使用依赖注入和/或工厂模式来使用特定的实现。

编辑:在某些时候你必须指定要使用的具体类,但使用上述模式可以将所有代码集成到一个地方,而无需使用条件编译。

编辑:DI / Factory的一个例子。当然,您可以使用现有的框架,这将为您提供更多的动力和表现力。对于这个简单的例子,它似乎有点矫枉过正,但代码越复杂,使用模式的收益就越大。

// Common.dll

public interface IPlatformInfo
{
    string PlatformName { get; }
}

public interface PlatformFactory
{
    IPlatformInfo CreatePlatformInfo();
    // other...
}

public class WelcomeMessage
{
    private IPlatformInfo platformInfo;

    public WelcomeMessage(IPlatformInfo platformInfo)
    {
        this.platformInfo = platformInfo;
    }

    public string GetMessage()
    {
        return "Welcome at " + platformInfo.PlatformName + "!";
    }
}

// WindowsApp.exe

public class WindowsPlatformInfo : IPlatformInfo
{
    public string PlatformName
    {
        get { return "Windows"; }
    }
}

public class WindowsPlatformFactory : PlatformFactory
{
    public IPlatformInfo CreatePlatformInfo()
    {
        return new WindowsPlatformInfo();
    }
}

public class WindowsProgram
{
    public static void Main(string[] args)
    {
        var factory = new WindowsPlatformFactory();
        var message = new WelcomeMessage(factory.CreatePlatformInfo());
        Console.WriteLine(message.GetMessage());
    }
}

// MonoApp.exe

public class MonoPlatformInfo : IPlatformInfo
{
    public string PlatformName
    {
        get { return "Mono"; }
    }
}

public class MonoPlatformFactory : PlatformFactory
{
    public IPlatformInfo CreatePlatformInfo()
    {
        return new MonoPlatformInfo();
    }
}

public class MonoProgram
{
    public static void Main(string[] args)
    {
        var factory = new MonoPlatformFactory();
        var message = new WelcomeMessage(factory.CreatePlatformInfo());
        Console.WriteLine(message.GetMessage());
    }
}

答案 3 :(得分:1)

1)在自己的程序集中与特定于平台的类接口:共享程序集中的ITimer和包含WebTimer的“WebAssembly”。然后按需加载“WebAssembly.dll”或“DesktopAssembly.dll”。这将其转变为更多的部署/配置问题,并且所有内容都会编译。依赖注入或MEF在这里成为一个很好的帮助。

2)接口(再次),但是使用条件编译。这使得它不再是部署问题,而是更多的编译问题。 WebTimer周围会有#ifdef WEB_PLATFORM,依此类推。

就个人而言,我倾向于#1 - 但在一个复杂的应用程序中,很可能你最终不得不使用两者,因为.net框架的可用部分在silverlight和其他所有部分之间略有变化。您甚至可能希望在应用的核心部分采用不同的行为来解决性能问题。

答案 4 :(得分:1)

如果要将所有用户界面逻辑与正在使用的实际GUI框架分开,Model-View-Presenter模式是一种非常好的方法。阅读Michael Feather的文章“The Humble Dialog Box”,以获得有关其工作原理的精彩解释:

http://www.objectmentor.com/resources/articles/TheHumbleDialogBox.pdf

原始文章是为C ++编写的,如果你想要一个C#示例,请看这里:

http://codebetter.com/blogs/jeremy.miller/articles/129546.aspx

优点是:

  • 您将使GUI逻辑可重复使用
  • 您的GUI逻辑适用于单元测试

缺点:

  • 如果你的程序不需要一个以上的GUI框架,这种方法会产生更多的代码行,你必须处理更多的复杂性,因为你必须通过编码来决定代码的哪些部分属于视图和进入演示者的视图

答案 5 :(得分:0)

随着其他人的消化,接口就是这里的方式。我会略微改变界面从消化Moo-Juice的建议......

` //为什么这个块没有形成像代码???

public interface ITimer {
void StopTimer(); //等......

void StartTimer(); //等......

TimeSpan Duration {get;} // eo interface ITimer

}`

现在您需要将ITimer引入正在使用它的类中。最简单的方法是称为依赖注入。实现此目的的最常用方法称为构造函数注入。

因此,在创建需要计时器的类时,可以在创建计时器时将计时器传递给类。

基本上你这样做:

var foo = new Foo(new WebTimer());

由于这会很快变得复杂,你可以利用一些助手。这种模式称为控制反转。有一些框架可以帮助你,比如ninject或城堡windsor。

两者都是控制反转(IOC)容器。 (那是秘密酱)

基本上你在IOC中“注册”你的计时器,并注册你的“Foo”。当您需要“Foo”时,您会要求您的IOC容器创建一个。容器查看构造函数,发现它需要一个ITimer。然后它将为您创建一个ITimer,并将其传递给构造函数,最后将完整的类交给您。

在课堂上,您不需要了解ITimer或如何创建它,因为所有这些都被移到了外面。

对于不同的应用程序,您现在只需要注册正确的组件,就完成了......

P.s。:小心并且不要将IOC容器与服务定位器混淆......

链接:

http://ninject.org/download

http://www.castleproject.org/container/index.html

http://www.pnpguidance.net/Category/Unity.aspx

答案 6 :(得分:0)

为什么没有配置部分告诉您的图书馆主机应用程序的平台。您必须在应用程序中仅将一次设置为主机配置文件(web.config或app.config),然后根据Moo-Juice的建议使用Factory方法。您可以在库的整个功能上使用平台详细信息。