通过定义接口来实现API无关
如果你遇到一个实现他自己的渲染引擎的游戏程序员,你可能会问一个非常重要的问题:“你用的是什么API?”但是当你对于一个视频游戏编程了解的更多的话,你会认识到其实这个问题并不重要。
提示
只有在遇到比如预期的操作系统和目标平台之类的特定限制的时候,API问题才会变得重要。例如没有DirectX的Linux操作系统,或者带有特定的内嵌操作系统的视频游戏平台。
诀窍是避免对于由于使用如DirectX等的API而产生的依赖。为此,使用API无关的接口类,为隐含实现了接口的代码提供接口。实际上,这个隐含实现使用了特定的API(比如DirectX),但是真正的诀窍是,你可以用使用了其他不同的API(如OpenGL)的实现来代替这个实现。好消息是这并不像看上去的那么难。实际上,在第三章,“引擎的构造”中,我展示了怎么做。与此同时,不涉及Direct3D VS. OpenGL等无谓的讨论基于特定API的实现。
现在,你同样应该知道,在C++中定义的接口就像类的声明一样,因为接口是C++中的只包含公共成员函数的抽象基类,是一个纯虚类。这意味着这些函数仅仅被声明而没有被实现。只不过,一个接口仅仅是一个对象的模板,该模板包含一个真正的对象必须要实现的一些函数。不要误把此处的单词模板在C++编程中用到的模板。
注意
如果一个基类仅仅通过委托给其他类的实例来实现该基类的函数,你也可以把该基类称为接口或者适配器。这种方法用来向用户隐藏实现细节,因为第二个类只能通过基类获知。
目前抽象基类的说法,对于没有接口编程的人听起来比较陌生。从图2.1中,你可以看到一个称作Iobject的接口。 IObject是在文件IObject.h中声明的。这个接口由三种不同的实现所支持。这三个分别是CObjectA , CObjectB ,和CObjectC ,他们通过相同的函数调用提供完全相同的功能。当然,这些函数是在接口中定义的,尽管这些函数可以得到同样的结果,但是这些函数在这三个对象中的实现是不同的。
图2.1 一个接口声明了多个函数,这些函数可以被使用了该接口的应用程序调用。但是,这些函数实际上是在实现了该接口的类中实现的。
在图的左侧,可以看到使用接口的应用程序。该应用程序并不知道CObjectA, CObjectB,or CobjectC,也不负责照管他们。应用程序仅仅与接口进行交流,接口负责完成应用程序想让它完成的任务,比如渲染一些图元。
但是,现在的情况如同狗在追逐自己的尾巴一样。作为一个程序员你会问自己,如何确实地实现这个基类和一个适当的派生类,而没有被陷在如下的情况之中:
CApplication *pMyApp = new CApplication();
// assume CApplication::m_pObject is of type IObject* and initialized
pMyApp->m_pObj = new CObjectA();
你不能通过Iobject创建一个实例,因为没有那个接口类的实现,所以你只能通过这三个可以创建实例的类中创建一个实例。如上图所示,你还想通过一个该接口类型的指针引用该对象。因此,这三个可以创建实例的类必须通过该接口类派生。问题是,应用程序必定要调用类的构造函数,所以该应用程序必须要知道这三个类,至少也要知道其中一个。
要避免这样的问题,从而使应用程序可以通过接口直接创建派生类的对象。在第三章,你会了解到如何实现一个管理器,该管理器创建了派生类而不用应用程序看到他们,并且返回应用程序所知道的该接口类型的指针给应用程序。