1 OPAMC的概念
OPAMC架构来源于ECS架构的思想,用于实现面向对象绘图,采用Racket语言(Lisp语言的一个方言)实现。
ECS架构(全称Entity-Component-System,即实体-组件-系统)是一种软件架构模式,主要用于游戏开发。ECS遵循组合优于继承的原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法),系统是处理拥有一个或多个相同组件的实体集合的工具,它只拥有行为(即在系统中没有任何数据)。实体与组件是一个一对多的关系,实体拥有怎样的能力,完全是取决于其拥有哪些组件。通过动态添加或删除组件,可以在(游戏)运行时改变实体的行为。换句话说,每个实体不是由"类型"定义的,而是由与其关联的组件定义的。组件如何与实体相关连,取决于所使用的ECS框架如何设计。
ECS的规则------"组件没有函数,系统没有状态"。
ECS架构规避了面向对象编程(OOP)中继承造成的不足------为对象强行建立父子关系,这种极度耦合产生的各种不灵活:耦合性的改动、不充分的继承、不灵活的重用、难以抉择的归类。
OPAMC架构同样采用组合的方式组织数据,但规避了ECS架构中实体数量及组件数量为预先设定的问题,使它们可以根据用户需求进行动态增减。
OPAMC架构使用对象(绘图对象,O)、属性(对象属性,P)、行为(对象行为,A)、管理器(M)、协调器(C)五个概念,将绘图对象的属性采用组合方式进行数据组织,即一切皆属性,实现面向数据编程。而在处理具体的行为、管理、协调时,采用面向对象(OOP)进行封装。
2 OPAMC的组成
-
O:object,对象。指绘图操作对象,包含实体及虚体。对象包括:图元、实体、组、模型。对组的内部成员(实体、组的引用)作为对象的属性;实体的内部成员(图元)作为对象属性;图元的几何描述结构作为对象属性。
-
P:property,属性。指描述对象的特征的数据结构值。属性仅提供数据(数据结构值),不包含任何行为,没有函数。可以将各种属性结构分类放在不同模块内。
-
A:action,行为。对象的行为逻辑,负责对一组(或一个)特定对象属性进行处理,实现对象属性的控制及实现对象行为操作。行为模块之间独立存在,不互相调用。
-
M:manager,管理器,实现对象、属性、行为的创建、添加、修改、查询及删除(CRUD),分别对应通过对象管理器、属性管理器、行为管理器实现。管理器模块与对象、属性、行为模块分开设置,实现代码完全解偶。
-
C:coordinator,协调器。为避免模块的交叉引用并使程序逻辑更清晰,通过协调器实现对象管理器、属性管理器、行为管理器之间的协调关系;实现对象、属性、行为的综合管理,它也是对外的接口(更上层程序通过调用协调器程序间接管理数据及行为,而不是直接调用各数据及管理器程序)。各管理器仅完成自己范围内的简单操作,所有交叉操作均在协调器内进行。
3 OPAMC模块划分
-
对象管理器模块:包含对象管理器。以对象管理器为基类,可根据需要扩展出图元对象管理器、实体对象管理器、组对象管理器三个模块。
-
属性数据结构模块:可按属性数据结构分类设置不同模块。
-
属性管理器模块。
-
属性值管理器模块。
-
行为模块:行为类模块,并以行为类为基类,根据不同行为需求派生各自行为类。
-
行为管理器模块。
-
协调器模块。
4 OPAMC文件树结构
OPAMC架构整体作为一个单独文件夹。其下分为如下文件及文件夹组成文件树:
-opamc(OPAMC架构文件夹)
|-property(属性文件夹)
|+所有经分类的属性模块
|-action(行为文件夹)
|+所有行为的模块
|-manager(管理器文件夹)
|-object-manager.rkt(对象管理器模块)
|-property-manager.rkt(属性管理器模块)
|-property-value-manager.rkt(属性值管理器模块)
|-action-manager.rkt(行为管理器模块)
|-coordinator.rkt(协调器模块)
由此可以看出,OPAMC架构由:一个架构,三个文件夹(属性文件夹、行为文件夹、管理器文件夹),四个管理器(对象管理器、属性管理器、属性值管理器、行为管理器),一个协调器等组成。
5 OPAMC架构整合
5.1 OPAMC架构数据
-
对象(objects):(hash (对象id/o . (set 属性id/p)...)
-
属性(properties):(hash (类型type . (hash 属性id/p . (set 对象id/o))...)...)
-
属性值(property-values):(hash (类型type . (hash (属性id/p . 属性值)...)...)
-
行为(actions):(hash (类型type . 行为id/a)...)
5.2 OPAMC操作
-
对象管理器(object-manager):创建对象、销毁对象;取得对象散列表;取得属性集合;添加属性、移除属性。
-
属性管理器(property-manager):添加属性、删除属性;取得对象集合;添加对象、移除对象。
-
属性值管理器(property-value-manager):注册属性类型;取得属性值散列表;添加属性值、销毁属性值、取得属性值、设置属性值。
-
行为管理器(action-manager):注册行为;取得行为散列表;取得行为;取得对象集合;添加对象、移除对象;判断行为类型是否存在。
-
协调器(coordinator):协调manager/o、manager/p、manager/pv、manager/a四个管理器。创建对象、销毁对象、取得对象散列表;注册属性、添加属性、移除属性、取得属性、取得属性值、更新属性;注册行为、添加行为、取得行为散列表、取得行为。
管理器的操作严格控制在简单、直接范围的操作,凡是交叉判断、交叉操作均在协调器内实现。
数据的索引:将对象、属性值、行为均分配全局唯一id值,通过id值进行索引。由于Racket语言中结构(struct)和类(class)均为一级值,因此对于用类型(type)进行索引分类的,直接用结构类型值及类类型值作为类型(type)索引值,这样既直观又简单。
6 OPAMC实现代码
6.1 属性代码示例
所有属性均用结构类型表示,同类属性结构放在一个模块内。
-
属性模块示例一:
|----------------------------|
| ;transform-property.rkt |
| ;变换属性。 |
| |
| #lang racket |
| |
| (provide |
| (struct-out transform)) |
| |
| (struct transform |
| (position rotation scale)) | -
属性模块示例二:
|-------------------------|
| ;geometry-property.rkt |
| ;几何属性。 |
| |
| #lang racket |
| |
| (provide |
| (struct-out point) |
| (struct-out line) |
| (struct-out arc)) |
| |
| ;点: |
| (struct point (x0 y0)) |
| ;线段: |
| (struct line (p0 p1)) |
| ;圆弧: |
| (struct arc (pc p0 p1)) |
6.2 行为代码示例
行为采用类来描述,以便于对不同行为的封装。行为类仅包含操作对象集合,而且该集合来自于action%基类,以确保行为操作对象均针对所有对象(如果要对对象进行分类则需要另行处理)。一般每一个行为类独占一个模块。
-
行为基类模块示例:
|-----------------------------------------------------|
| ;action.rkt |
| ;行为基类。 |
| ;行为(action)负责对一组具有特定属性(property) ;的对象(object)表进行处理。 |
| ;这里的"特定"是指对象应该包含所有该行为需要的属性。 |
| |
| #lang racket |
| |
| (provide action%) |
| |
| ;定义行为类: |
| ;本类为基类,每个行为类都可以从这个基类继承, |
| ;从而使得行为管理器(action-manager)可以保留指向行为的引用列表。 |
| (define action% |
| (class object% |
| (super-new) |
| |
| (field |
| ;对象集合: |
| [objects (mutable-set)]) |
| |
| ;添加对象: |
| (define/public (add-object id/o) |
| (set-add! objects id/o)) |
| |
| ;移除对象: |
| (define/public (remove-object id/o) |
| (set-remove! objects id/o)) |
| |
| ;取得对象集合: |
| (define/public (get-objects) |
| objects) |
| )) | -
行为派生类模块示例:
|--------------------------------------------------------|
| ;draw-action.rkt |
| ;绘制行为。 |
| |
| #lang racket |
| |
| (require "action.rkt" |
| "../coordinator.rkt" |
| "../property/transform-property.rkt" |
| "../property/display-property.rkt") |
| |
| (provide draw-action%) |
| |
| ;定义绘制行为类: |
| (define draw-action% |
| (class action% |
| (super-new) |
| (inherit-field objects) |
| |
| (field [w 30] |
| [coordinator (make-coordinator)]) |
| |
| (define/public (draw dc) |
| (set-for-each |
| objects |
| (lambda (id/o) |
| (let ([p/transform |
| (send coordinator get-property-value transform |
| (send coordinator get-property id/o transform))]) |
| (when p/transform |
| (let ([pos (transform-position p/transform)] |
| [ro (transform-rotation p/transform)] |
| [sc (transform-scale p/transform)]) |
| (send dc set-origin (send pos get-x) (send pos get-y)) |
| (send dc set-scale sc sc) |
| (send dc set-rotation ro) |
| (send dc draw-rectangle |
| (- w) (- w) |
| (* 2 w) (* 2 w)) |
| )))))) |
| )) |
6.3 管理器代码
每个管理器分别独立设置模块。管理器模块在OPAMC架构下可以无需改变直接使用。
包括四个模块:对象管理器模块、属性管理器模块、属性值管理器模块、行为管理器模块。
6.3.1 对象管理器模块
|--------------------------------------------|
| ;object-manager.rkt |
| ;对象管理器。 |
| ;每个对象包含属性标识集合。 |
| |
| #lang racket |
| |
| (require "../arithmetic/guid.rkt") |
| |
| (provide |
| make-object-manager) |
| |
| ;对象散列表,每一个键值对代表一个对象。其: |
| ;键为对象标识值;值为对象属性标识值集合。 |
| (define objects (make-hash)) |
| |
| ;定义对象管理器类: |
| (define object-manager% |
| (class object% |
| (super-new) |
| |
| ;创建对象: |
| (define/public (create-object) |
| (let ([id/o (create-guid-string)]) |
| (hash-set! objects id/o (mutable-set)) |
| id/o)) |
| |
| ;销毁对象: |
| (define/public (destroy-object id/o) |
| (unless (hash-empty? objects) |
| (hash-remove! objects id/o))) |
| |
| ;取得对象散列表: |
| (define/public (get-objects) |
| objects) |
| |
| ;取得属性集合: |
| (define/public (properties id/o) |
| (hash-ref objects id/o (lambda () #f))) |
| |
| ;添加属性: |
| (define/public (add-property id/o id/p) |
| (let ([ps (properties id/o)]) |
| (when ps |
| (set-add! (properties id/o) id/p)))) |
| |
| ;移除属性: |
| (define/public (remove-property id/o id/p) |
| (let ([ps (properties id/o)]) |
| (when ps |
| (set-remove! (properties id/o) id/p)))) |
| )) |
| |
| ;创建对象管理器: |
| (define (make-object-manager) |
| (new object-manager%)) |
6.3.2 属性管理器模块
|--------------------------------------------|
| ;property-manager.rkt |
| ;属性管理器。 |
| ;每个属性包含对象标识集合。 |
| |
| #lang racket |
| |
| (provide make-property-manager) |
| |
| ;属性散列表。其中: |
| ;键为属性标识值;值为对象标识集合。 |
| (define properties (make-hash)) |
| |
| ;定义属性管理器类: |
| (define property-manager% |
| (class object% |
| (super-new) |
| |
| ;添加属性: |
| (define/public (add-property id/p) |
| (hash-set! properties id/p (mutable-set))) |
| |
| ;删除属性: |
| ;删除属性需满足该属性没有对象需要。 |
| (define/public (delete-property id/p) |
| (when (set-empty? (objects id/p)) |
| (hash-remove! properties id/p))) |
| |
| ;取得对象集合: |
| (define/public (objects id/p) |
| (hash-ref properties id/p (lambda () #f))) |
| |
| ;添加对象: |
| (define/public (add-object id/p id/o) |
| (let ([os (objects id/p)]) |
| (when os |
| (set-add! os id/o)))) |
| |
| ;移除对象: |
| (define/public (remove-object id/p id/o) |
| (let ([os (objects id/p)]) |
| (when os |
| (set-remove! os id/o))) |
| (when (set-empty? (objects id/p)) |
| (delete-property id/p))) |
| )) |
| |
| ;创建属性管理器对象: |
| (define (make-property-manager) |
| (new property-manager%)) |
6.3.3 属性值管理器模块
|--------------------------------------------|
| ;property-value-manager.rkt |
| ;属性值管理器。 |
| |
| #lang racket |
| |
| (require "../arithmetic/guid.rkt") |
| |
| (provide make-property-value-manager) |
| |
| ;属性类型散列表,通过属性注册产生。其: |
| ;键为属性结构类型;值为属性值散列表。 |
| ;属性值散列表,通过添加产生,其键为属性标识值;值为属性值。 |
| (define types (make-hash)) |
| |
| ;定义属性值管理器类: |
| (define property-value-manager% |
| (class object% |
| (super-new) |
| |
| ;注册属性类型: |
| (define/public (regist-type type) |
| (hash-set! types type (make-hash))) |
| |
| ;取得指定类型属性值散列表: |
| (define/public (get-values type) |
| (hash-ref types type (lambda () #f))) |
| |
| ;添加属性值: |
| ;如果指定属性类型不存在,则注册该属性类型。 |
| (define/public (add-value type value) |
| (unless (has-type? type) |
| (regist-type type)) |
| (let ([values (get-values type)] |
| [id/p (create-guid-string)]) |
| (hash-set! values id/p value) |
| id/p)) |
| |
| ;销毁属性值: |
| (define/public (destroy-value type id/p) |
| (let ([values (get-values type)]) |
| (unless (hash-empty? values) |
| (hash-remove! values id/p)))) |
| |
| ;取得属性值: |
| (define/public (get-value type id/p) |
| (let ([values (get-values type)]) |
| (if values |
| (hash-ref values id/p (lambda () #f)) |
| #f))) |
| |
| ;设置属性值: |
| (define/public (set-value type id/p value) |
| (let ([values (get-values type)]) |
| (hash-set! values id/p value))) |
| |
| ;查找属性值: |
| ;如果找到,返回属性id,否则返回#f. |
| (define/public (find-value type value) |
| (let ([values (get-values type)] |
| [id/p #f]) |
| (when values |
| (hash-for-each |
| values |
| (lambda (k v) |
| (when (equal? v value) |
| (set! id/p k))))) |
| id/p)) |
| |
| ;判断属性是否为指定属性类型? |
| (define/public (is-type? type id/p) |
| (hash-has-key? (get-values type) id/p)) |
| |
| ;判断是否存在指定属性类型? |
| (define/public (has-type? type) |
| (hash-has-key? types type)) |
| )) |
| |
| ;创建属性值管理器: |
| (define (make-property-value-manager) |
| (new property-value-manager%)) |
6.3.4 行为管理器模块
|-----------------------------------------|
| ;action-manager.rkt |
| ;行为管理器。负责维护已注册的行为(action)。 |
| |
| #lang racket |
| |
| (require "../arithmetic/guid.rkt") |
| |
| (provide make-action-manager) |
| |
| ;行为散列表。其: |
| ;键为行为类标识;值为行为类对象值。 |
| (define actions (make-hash)) |
| |
| ;定义行为管理器类: |
| (define action-manager% |
| (class object% |
| (super-new) |
| |
| ;注册行为: |
| (define/public (regist-action type) |
| (hash-set! actions type (new type))) |
| |
| ;取得行为散列表: |
| (define/public (get-actions) |
| actions) |
| |
| ;取得行为: |
| (define/public (get-action type) |
| (hash-ref actions type (lambda () #f))) |
| |
| ;取得行为包含的对象集合: |
| (define/public (get-objects type) |
| (let ([a (get-action type)]) |
| (when a |
| (send a get-objects)))) |
| |
| ;添加对象: |
| (define/public (add-object type id/o) |
| (let ([a (get-action type)]) |
| (when a |
| (send a add-object id/o)))) |
| |
| ;移除对象: |
| (define/public (remove-object id/o) |
| (hash-for-each |
| actions |
| (lambda (k v) |
| (send v remove-object id/o)))) |
| |
| ;判断类型是否存在: |
| (define/public (has-type? type) |
| (hash-has-key? actions type)) |
| )) |
| |
| ;创建行为管理器: |
| (define (make-action-manager) |
| (new action-manager%)) |
6.4 协调器代码
协调器独立设置模块,实现对象管理器、属性管理器、属性值管理器、行为管理器的交叉通信,避免模块之间的交叉引用,进行统一管理、统一对外接口。协调器模块在OPAMC架构下可以无需改变直接使用。
|---------------------------------------------------------|
| ;coordinator.rkt |
| ;协调器。 |
| ;负责完成对象管理器、属性管理器、属性值管理器、行为管理器之间交叉的行为, |
| ;避免模块之间的交叉引用。 |
| |
| #lang racket |
| |
| (require "manager/object-manager.rkt" |
| "manager/property-manager.rkt" |
| "manager/property-value-manager.rkt" |
| "manager/action-manager.rkt") |
| |
| (provide make-coordinator) |
| |
| ;定义协调器类: |
| (define coordinator% |
| (class object% |
| (super-new) |
| |
| (field |
| [manager/o (make-object-manager)] |
| [manager/p (make-property-manager)] |
| [manager/pv (make-property-value-manager)] |
| [manager/a (make-action-manager)]) |
| |
| ;对象相关方法:----------------------------------------- |
| ;创建对象: |
| (define/public (create-object) |
| (send manager/o create-object)) |
| |
| ;销毁对象: |
| (define/public (destroy-object id/o) |
| (send manager/o destroy-object id/o) |
| (send manager/p remove-object id/o) |
| (send manager/a remove-object id/o)) |
| |
| ;取得对象散列表: |
| (define/public (get-objects) |
| (send manager/o get-objects)) |
| |
| ;属性相关方法:------------------------------------------- |
| ;注册属性: |
| (define/public (regist-property type) |
| (unless (send manager/pv has-type? type) |
| (send manager/pv regist-property type))) |
| |
| ;添加属性: |
| ;如果value在属性列表中不存在,则添加。 |
| (define/public (add-property id/o type value) |
| (let ([id/p (send manager/pv find-value type value)]) |
| (unless id/p |
| (set! id/p (send manager/pv add-value type value)) |
| (send manager/p add-property id/p)) |
| (send manager/o add-property id/o id/p) |
| (send manager/p add-object id/p id/o))) |
| |
| ;移除属性: |
| (define/public (remove-property id/o id/p) |
| (send manager/o remove-property id/o id/p) |
| (send manager/p remove-object id/p id/o)) |
| |
| ;取得属性: |
| (define/public (get-property id/o type) |
| (let ([id/p #f]) |
| (for ([id (send manager/o properties id/o)]) |
| #:break (not (send manager/pv is-type? type id)) |
| (set! id/p id)) |
| id/p)) |
| |
| ;取得属性值: |
| (define/public (get-property-value type id/p) |
| (send manager/pv get-value type id/p)) |
| |
| ;更新属性: |
| (define/public (update-property type id/o id/p value) |
| (let ([id/v (send manager/pv find-value type value)] |
| [n (set-count (send manager/p objects id/p))]) |
| (cond |
| ;给定属性值已经存在,将该存在的属性设置给对象: |
| [id/v |
| (send manager/p remove-object id/p id/o) |
| (send manager/p add-object id/v id/o) |
| (send manager/o remove-property id/o id/p) |
| (send manager/o add-property id/o id/v)] |
| ;属性仅属于一个对象,仅改变属性值: |
| [(= n 1) |
| (send manager/pv set-value type id/p value)] |
| ;属性属于多个对象,新建立属性给对象: |
| [(> n 1) |
| (let ([id/v (send manager/pv add-value type value)]) |
| (remove-property id/o id/p) |
| (send manager/p register-property id/v) |
| (send manager/p add-object id/v id/o) |
| (send manager/o add-property id/o id/p))]))) |
| |
| ;行为相关方法:-------------------------------------------- |
| ;注册行为: |
| (define/public (regist-action type) |
| (unless (send manager/a has-type? type) |
| (send manager/a regist-action type))) |
| |
| ;添加行为: |
| (define/public (add-action id/o type) |
| (send manager/a add-object type id/o)) |
| |
| ;取得行为散列表: |
| (define/public (get-actions) |
| (send manager/a get-actions)) |
| |
| ;取得行为: |
| (define/public (get-action type) |
| (let ([as (get-actions)]) |
| (if (hash-empty? as) |
| #f |
| (hash-ref as type)))) |
| )) |
| |
| ;创建协调器对象: |
| (define (make-coordinator) |
| (new coordinator%)) |
6.5 id标识代码
以上的对象管理器、属性值管理器、行为管理器模块中,均出现了(require "../arithmetic/guid.rkt"),其中"arithmetic"是用来放置数学计算模块的文件夹,其中的guid.rkt是用来生成id索引标识的函数generate-guid所在的模块文件。
值的说明的是,这里采用的为随机生成的16个字符索引标识值,实际采用的id索引标识有各种选择,比如IFC规范的GUID为是二进制长度为128位的数字标识符,UUID一般为32个字符的字符串等。
该文件的主要内容如下:
|---------------------------|
| ;guid.rkt |
| ;生成唯一标志(GUID)。 |
| |
| #lang racket |
| |
| (require racket/random) |
| |
| (provide generate-guid) |
| |
| ;...... |
| |
| ;作为guid的另一个选择,通过随机产生字节码: |
| (define (generate-guid) |
| (crypto-random-bytes 16)) |
| |
| ;...... |
7 总结
OPAMC架构是一个伸缩性很强的以面向数据编程的软件框架,其速度不如DCS,但扩展性是其重要特点。我把它用在我本人实现的面向对象绘图的软件中,目的是为了实现"一切皆属性"的思想,让绘图处理过程逻辑上高度统一。
大家如对OPAMC架构有不同的意见或建议,欢迎进行评论。
以上内容用Racket语言的Scribble编辑并编译生成。