如何正确地将类与框架解耦(源代码级别的解耦)

5

我正在使用Microsoft XNA框架开发一个游戏项目,尽管这个问题是一个涉及类/系统解耦的通用问题。

假设我有一个类(为了适应这个例子而简化)如下所示:

public class GameCell
{   
        public Color Color { get; set; }
}

我希望在使用多个平台时,能够尽可能地重用大量的代码,这些平台都使用C#。直接引用Color结构的问题是这种类型是在XNA程序集中定义的,这就创建了我的代码与XNA之间的强耦合(或依赖关系)。我将要使用的另一个框架可能(或可能不会)拥有自己的Color对象,具有其自己的属性和API。我希望同一源文件可以自动地“知道”使用XNA或其他框架的实现。我知道还有其他类型的解耦方法,例如IoC,但这意味着我将插入不同版本的系统/类,而不是在不同上下文中重用相同的类。这种做法是否可行?你如何建议保持这样的系统可移植性?我曾经在本机C++开发中看到过一些情况,在这种情况下,你会定义一组类来反映框架正在使用的类(例如这里-重新定义Color),因此可以在需要时“重新映射”以使用不同的类。另一个选项是通过 #IFDEF 来修改类头中的using语句(从XNA切换到其他平台)。在这种情况下,哪种是最好的选择?

你应该清楚地知道你需要在源代码级别还是在程序集级别上实现兼容性。如果只是源代码,那么可以使用条件编译。如果是二进制程序集,那就比较困难了。 - Craig Stuntz
针对这个具体需求,我希望在源代码级别上实现兼容性。我认为条件编译可能会变得混乱且难以维护。我想知道是否还有其他技术可供选择。 - lysergic-acid
个人而言,我会为您的非XNA平台重新实现XNA类型 ;) - Andrew Russell
@liortal,我认为你想要的是不可能的。当你从一个实体到另一个实体有一对一的映射时,它可能适用于简单的情况,但整体设计很可能没有这种简单的映射。通常有许多对象以复杂的方式相互作用,因此您将无法将所有这些内容带到独立于底层实现的通用接口中。除非您在其上构建一个更高级别的层。 - Roman L
1个回答

5

通常情况下,您需要创建自己的Color结构,并使用条件编译开关进行翻译。例如:

#define USE_DOTNET
//#define USE_SOMETHINGELSE

#if USE_DOTNET
using System.Drawing;
#endif

#if USE_SOMETHINGELSE
using SomethingElse.Drawing;
#endif

public struct MyColor
{
    #if USE_DOTNET
    Color TheColor;
    #endif
    #if USE_SOMETHINGELSE
    SomethingsColor TheColor;
    #endif

    public int R
    {
        get
        {
            #if USE_DOTNET
                // code to return .NET Color.R value
            #endif
            #if USE_SOMETHINGELSE
                // code to return SomethingColor.R value
            #endif
         }
    }
}

另一种方法是为.NET和其他代码分别设置不同的区域。例如:

#if USE_DOTNET
public int R
{
    get { return TheColor.R; }
}
// other properties and methods here
#endif

#if USE_SOMETHINGELSE
// properties and methods for using SomethingElse
#endif

这种方法很有效,但编写代码时必须非常小心,确保除了这些可移植层之外,不要在任何地方使用.NET Color 结构。

当我们开发需要在Windows、Mac和多个不同的游戏主机上运行的游戏时,我们在C/C++中使用了这种技术。设置可移植层很麻烦,但一旦设置好了,使用起来就很容易。

我建议您不要将可移植类和结构体的名称与.NET库或其他使用的库中的类或结构体相同。如果您这样做,会让自己困惑,并且可能会编写一些非可移植的代码,直到您开始尝试移植它才会发现。(并不是我有亲身经验...)


这种技术可以起作用,但是它强制我映射实际需要此技术的确切API及其差异。例如,Color在两个API中都有定义,并且都公开了R、G、B、A属性,因此在这种情况下,我只需要拥有你演示的代码的一半。对于其他情况,我将不得不映射所有共享类以查看它们的差异。 - lysergic-acid
另一个问题是,这为每次访问可移植层(MyColor.TheColor.R)增加了另一个方法调用的开销。 - lysergic-acid
@liortal:如果你愿意,你可以很容易地解决这个问题。不要在你的结构体中存储一个.NET Color结构,而是将R、G、B和A值复制到你的结构体中。基本上,从其他对象获取你想要的信息,并自己存储它。我之前展示了另一种方式,只是为了简单起见。如果性能是一个问题,那么你可以按任何你喜欢的方式进行优化。 - Jim Mischel
是的,刚才就指出了这一点。你提供了一些不错的起点。我实际上正在考虑使用T4模板来自动生成一些代码。例如对于颜色,XNA公开了Color.Green、Color.White等,这只是一种获取RGBA值的花哨静态方式。我可以轻松地反射到其中,以便以可移植的方式自动创建它们。 - lysergic-acid
你能举一个例子,说明这个技术如何适用于超过2个框架吗?(你说过你做过这个)。条件编译是否适用于更多的平台,还是会变得混乱不堪? - lysergic-acid
显示剩余2条评论

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