您的位置:首页 > 编程语言

面向组件,状态机,消息驱动,三合一的编程模型

2016-07-28 17:17 330 查看
在实践中,发现面向组件,状态机,消息驱动。如果整合起来的模型,能够更为自然和简单的进行抽象。当然这些都是以面向对象为基础,更进一步的抽象扩展。本文,先会分别介绍一下,面向组件,状态机,消息驱动的各自特点。然后,介绍如何整合三者。最后,给出代码示例。

第一,面向组件

在游戏开发中有些引擎会使用基于组件的架构。比如unity3d就是其中的典型。基于组件,有以下特性。

基类Componet负责组件的生命周期和状态管理。
Entity对象,也就是unity里面的GameObject,负责管理Component组件。Entity对象拥有任意多个组件,这里是组合模式。
所有的功能可以组件化,就是具体的功能继承Componet来实现,作为一个组件,可以复用到Entity对象上。
Entity做为一个功能的集合抽象,与其他Entity可以发生交互,或者消息交换。Entity的功能大多来自于可复用的Componet,功能绑定的力度需要具体由设计者决定。
Entity可以再运行时,动态的增加或者减少Componet,或者修改Componet的状态。
面向组件架构,倾向使用组合模式,通过组件来复用功能。其实,就是把通过继承得到的功能复用,拆散到组件里,然后组合起来使用。面向对象有以下几个问题。

为了一个功能去继承,就获得了父类所有其它无用,甚至不想关的功能,造成冗余。
当继承链超过3层的时候,对象职能无法保持单一,不便于记忆和使用。
继承可以操作父类的某些属性,在继承链中各自对象的操作,可能带来潜在的冲突。
面向对象一般是通过一个角度切入进行抽象,如果需要进行多层面的角度去抽象,面向对象很难划分对象结构去进行描述。

面向对象中的对象,是一种视角的抽象描述。我们如何去划分这个对象,至关重要,以及人为的去限制对象的边界。但现实世界,是多个角度,横看成岭侧成峰的,当用一个视角抽象对象的时候,换一个视角原来的抽象就会不兼容或是面目前非。

面向组件,这里的组件是抽象力度更小描述。具有原子性和单一功能性。很难再去分割,或是换个角度去理解。这就保证了组件的复用不会带来副作用。组合模式相比较继承,带来了隔离性,不会传递继承链上的属性和功能和潜在的副作用。

第二,状态机

状态机是容易理解的,但有惊人的作用。

任何事物都会有状态
状态机是一种视角,通过变化的切入点,来抽象和描述
如果说组件或是对象,是一种静态上的描述。那么状态机就是从动态角度去描述。
当我们用代码去抽象现实的时候,现实是变化的,有了状态机就可以捕捉这些变化,描述变化。

第三,消息驱动

有了组件化的静态描述,和状态机的动态描述,那么剩下的就是交互。消息的传递和处理,用来驱动状态的变化,状态的变化通常是属性的变化所表现出来的。有了消息驱动,我们就能让一切都运转起来,让抽象的描述变化起来,可以交互。

消息驱动,一般利用观察者模式,消息订阅,或是消息轮询来实现。

=============================================================================

如果,我们把以上3个整合起来,做为一个最基本的结构。可以想象,一个原子化的组件实现了一个单一功能,有自己的状态变化,能够发送消息,也能够接受处理消息。我们把这些组件自由的组合起来。那么就可以描述任何系统,抽象任何现实,只要不断的丰富组件,自由的发挥想象去设计构建功能对象,完善交互。

下面,来看看我的实现,使用C语言实现的,但了解了这个思想可以很容易,用任何语言来构建。

首先,我们看状态对象。

struct ComponentState
{
/**
* Bind data can not get from context
*/
void* userData;

/**
* Key in Component's stateMap
*/
int   id;

/**
* When ComponentState active first called
*/
void (*OnEnter)  (Component* component);

/**
* When ComponentState end last called
*/
void (*OnExit)   (Component* component);

/**
* Active ComponentState called per frame
*/
void (*Update)   (Component* component, float deltaTime);

/**
* When message received called
* if return true means consumed event then will stop event pass
*/
bool (*OnMessage)(Component* component, void* sender, int subject, void* data);
};


这是一个组件的状态对象,提供了组件一个状态形式。每个状态拥有以下功能。
唯一标识id,用来让状态机管理查找删除的。
进入状态回调OnEnter
退出状态回调OnExit
每帧调用处理逻辑的回调Update
用来接收消息的回调OnMessage

其次,组件对象如下。
struct Component
{
/**
* Bind data can not get from context
*/
void*                        userData;

/**
* Order in parent when add
* changed it and reorderAllChildren will sort by zOrder
*/
int                          zOrder;

/**
* When append child
* child zOrder auto increment by appendZOrder add last child zOrder
* default 20
*/
int                          appendZOrder;

Component*                   parent;

/**
* Current active state, default empty state with stateId 0
*/
ComponentState*              curState;

/**
* Previous active state, default empty state with stateId 0
*/
ComponentState*              preState;

/**
* Children mapped by Component zOrder
*/
ArrayIntMap(Component*)      childMap   [1];

/**
* Component notification observers
*/
ArrayIntMap(Component*)      observerMap[1];

/**
* All ComponentStates mapped by ComponentState's id
*/
ArrayIntMap(ComponentState*) stateMap   [1];
};

组件是一个递归的树结构,也就是说每个组件有父组件,有子组件。父组件只有一个parent,子组件是一组ChildMap。子组件通过zOrder排序存储,可以调节子组件的顺序。
组件整合了状态机自己所有的状态都存放在stateMap中。每个组件有一个curState和preState,也就是说组件一定处在某个状态,并且可以切换状态。这个状态就是上面的ComponetState。
由于组件能够发布自己的消息,所以有一组观察者存放在observerMap,组件会对观察者发送特定的事件。

最后,就是消息处理和状态切换
typedef struct
{
/**
* Add observer to sender, will receive notification by sender
*/
void (*AddObserver)            (Component* sender, Component* observer);

/**
* Remove observer from sender
*/
void (*RemoveObserver)         (Component* sender, Component* observer);

/**
* Call self and children's ComponentState update
*/
void (*Update)                 (Component* component, float deltaTime);

/**
* Call self children's ComponentState onMessage
* if return true means consumed event then will stop event pass
*/
bool (*SendMessage)            (Component* component, void* sender, int subject, void* data);

/**
* Notify sender all observer's ComponentState onMessage
*/
void (*Notify)                 (Component* sender, int subject, void* data);

/**
* Change Component current active State
*/
void (*SetState)               (Component* Component, int stateId);

/**
* Add Component in Component which create with state id
*/
ComponentState* (*CreateState) (Component* Component, int stateId);
}
_AComponent_;


这里,代码只留相关的部分。

可以对组件添加状态,切换状态。
添加消息观察者,消息观察者。
组件状态中OnMessage是处理消息的函数,消息可能来自发布者,也可能来自父类的消息发送
SendMessage 就是父类想子类发送消息
Notify 就是发布者,向订阅者发送消息

以上,就是所有的思路,完整实现的代码就不贴。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: