摘要:我想知道在C#中创建跨平台(例如,桌面,Web和Silverlight)类的最佳设计,没有重复的代码,具有以下优点:每个设计。
我经常为一个应用程序域编写新的有用的类;他们没有理由不跨域工作。如何构建我的代码以使其理想地跨平台?
例如,假设我想创建一个带有interval和on-tick事件的泛型“MyTimer”类。在桌面设备中,这将使用内置的.NET计时器。在Silverlight中,我会使用DispatchTimer。
设计#1可能是“创建一个类并使用预处理器指令进行条件编译”,例如。 “#IF SILVERILGHT ......”。但是,这会导致代码不易理解,易读和易于维护。
设计#2可能是“创建名为DesktopTimer和SilverlightTimer的子类,并使用来自MyTimer的子类。”这会怎么样?
虽然这是一个微不足道的案例,但我可能有更复杂的类,例如,使用特定于平台的类(IsolatedStorage,DispatchTimer等),但不直接替换它们。
我可以使用哪些其他设计/范例?
答案 0 :(得分:4)
我建议你编写Interface
来简化你的平台特定代码。然后,接口确保您的代码将尊重您的接口给出的合同,否则将有代码中断(如果没有实现一个成员)。
此外,在这个库中存在您的特定计时器类,为了坚持您的示例,我将为每个平台创建一个类,因此使用Silverlight的DispatchTimer
和内置的.NET计时器桌面版。
最后,您最终只会使用一个界面,只有其实施者知道如何专门处理您的底层平台的合同。
编辑#1
条件设计不是优秀设计的选择。这是一个工具,可以帮助您处理Dependancy Injection
,称为Unity Application Block
,并用于处理像您这样的场景。
您只使用非常通用的XML配置来“告诉”在需要此接口或该接口时必须实例化的内容。然后,UnityContainer
咨询您所做的配置,并为您实例化正确的类。这确保了良好的设计方法和架构。
编辑#2
我对依赖注入不是很熟悉,也不熟悉Unity Application Block。你能指出一些资源或进一步解释这些资源吗?
我认为这些资源将指导您完成学习。如果您需要进一步的帮助,请告诉我! =)
编辑#3
但无论如何,StopLight快速入门[...]似乎暗示接口到具体类的依赖关系映射是在代码中完成的(这对我不起作用)。
实际上,您可以同时执行代码和XML依赖关系映射,选择权在您手中! =)
以下是一些您应该启发的示例,使StopLight快速入门使用XML配置而不是编码映射。
如果这对你没有帮助,请告诉我。然后,我将提供一个使用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
优点是:
缺点:
答案 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容器与服务定位器混淆......
链接:
答案 6 :(得分:0)
为什么没有配置部分告诉您的图书馆主机应用程序的平台。您必须在应用程序中仅将一次设置为主机配置文件(web.config或app.config),然后根据Moo-Juice的建议使用Factory方法。您可以在库的整个功能上使用平台详细信息。