面向组件,状态机,消息驱动,三合一的编程模型
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语言实现的,但了解了这个思想可以很容易,用任何语言来构建。
首先,我们看状态对象。
这是一个组件的状态对象,提供了组件一个状态形式。每个状态拥有以下功能。
唯一标识id,用来让状态机管理查找删除的。
进入状态回调OnEnter
退出状态回调OnExit
每帧调用处理逻辑的回调Update
用来接收消息的回调OnMessage
其次,组件对象如下。
组件是一个递归的树结构,也就是说每个组件有父组件,有子组件。父组件只有一个parent,子组件是一组ChildMap。子组件通过zOrder排序存储,可以调节子组件的顺序。
组件整合了状态机自己所有的状态都存放在stateMap中。每个组件有一个curState和preState,也就是说组件一定处在某个状态,并且可以切换状态。这个状态就是上面的ComponetState。
由于组件能够发布自己的消息,所以有一组观察者存放在observerMap,组件会对观察者发送特定的事件。
最后,就是消息处理和状态切换
这里,代码只留相关的部分。
可以对组件添加状态,切换状态。
添加消息观察者,消息观察者。
组件状态中OnMessage是处理消息的函数,消息可能来自发布者,也可能来自父类的消息发送
SendMessage 就是父类想子类发送消息
Notify 就是发布者,向订阅者发送消息
以上,就是所有的思路,完整实现的代码就不贴。
第一,面向组件
在游戏开发中有些引擎会使用基于组件的架构。比如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 就是发布者,向订阅者发送消息
以上,就是所有的思路,完整实现的代码就不贴。
相关文章推荐
- Java入门
- python 微信公众号-回调模式验证url
- Java +安卓 定时任务
- Java数据库连接池的几种配置方法(以MySQL数据库为例)
- Delphi无法正确动态调用C++ dll库的几个原因
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- python的numpy模块安装问题
- BeautifulSoup使用一两则(不定期补充)
- php基础知识
- STM8L15x系列单片机独立看门狗配置
- javaweb学习总结(二十三)——jsp自定义标签开发入门