OPAMC架构来源于ECS架构的思想,用于实现面向对象绘图,采用Racket语言(Lisp语言的一个方言)实现。
ECS架构(全称Entity-Component-System,即实体-组件-系统)是一种软件架构模式,主要用于游戏开发。ECS遵循组合优于继承的原则,游戏内的每一个基本单元都是一个实体,每个实体又由一个或多个组件构成,每个组件仅仅包含代表其特性的数据(即在组件中没有任何方法),系统是处理拥有一个或多个相同组件的实体集合的工具,它只拥有行为(即在系统中没有任何数据)。实体与组件是一个一对多的关系,实体拥有怎样的能力,完全是取决于其拥有哪些组件。通过动态添加或删除组件,可以在(游戏)运行时改变实体的行为。换句话说,每个实体不是由“类型“定义的,而是由与其关联的组件定义的。组件如何与实体相关连,取决于所使用的ECS框架如何设计。
ECS的规则——“组件没有函数,系统没有状态”。
ECS架构规避了面向对象编程(OOP)中继承造成的不足——为对象强行建立父子关系,这种极度耦合产生的各种不灵活:耦合性的改动、不充分的继承、不灵活的重用、难以抉择的归类。
OPAMC架构同样采用组合的方式组织数据,但规避了ECS架构中实体数量及组件数量为预先设定的问题,使它们可以根据用户需求进行动态增减。
OPAMC架构使用对象(绘图对象,O)、属性(对象属性,P)、行为(对象行为,A)、管理器(M)、协调器(C)五个概念,将绘图对象的属性采用组合方式进行数据组织,即一切皆属性,实现面向数据编程。而在处理具体的行为、管理、协调时,采用面向对象(OOP)进行封装。
O:object,对象。指绘图操作对象,包含实体及虚体。对象包括:图元、实体、组、模型。对组的内部成员(实体、组的引用)作为对象的属性;实体的内部成员(图元)作为对象属性;图元的几何描述结构作为对象属性。
P:property,属性。指描述对象的特征的数据结构值。属性仅提供数据(数据结构值),不包含任何行为,没有函数。可以将各种属性结构分类放在不同模块内。
A:action,行为。对象的行为逻辑,负责对一组(或一个)特定对象属性进行处理,实现对象属性的控制及实现对象行为操作。行为模块之间独立存在,不互相调用。
M:manager,管理器,实现对象、属性、行为的创建、添加、修改、查询及删除(CRUD),分别对应通过对象管理器、属性管理器、行为管理器实现。管理器模块与对象、属性、行为模块分开设置,实现代码完全解偶。
C:coordinator,协调器。为避免模块的交叉引用并使程序逻辑更清晰,通过协调器实现对象管理器、属性管理器、行为管理器之间的协调关系;实现对象、属性、行为的综合管理,它也是对外的接口(更上层程序通过调用协调器程序间接管理数据及行为,而不是直接调用各数据及管理器程序)。各管理器仅完成自己范围内的简单操作,所有交叉操作均在协调器内进行。
对象管理器模块:包含对象管理器。以对象管理器为基类,可根据需要扩展出图元对象管理器、实体对象管理器、组对象管理器三个模块。
属性数据结构模块:可按属性数据结构分类设置不同模块。
属性管理器模块。
属性值管理器模块。
行为模块:行为类模块,并以行为类为基类,根据不同行为需求派生各自行为类。
行为管理器模块。
协调器模块。
OPAMC架构整体作为一个单独文件夹。其下分为如下文件及文件夹组成文件树:
-opamc(OPAMC架构文件夹)
|-property(属性文件夹)
|+所有经分类的属性模块
|-action(行为文件夹)
|+所有行为的模块
|-manager(管理器文件夹)
|-object-manager.rkt(对象管理器模块)
|-property-manager.rkt(属性管理器模块)
|-property-value-manager.rkt(属性值管理器模块)
|-action-manager.rkt(行为管理器模块)
|-coordinator.rkt(协调器模块)
由此可以看出,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)...)
对象管理器(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)索引值,这样既直观又简单。
所有属性均用结构类型表示,同类属性结构放在一个模块内。
属性模块示例一:
;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))
行为采用类来描述,以便于对不同行为的封装。行为类仅包含操作对象集合,而且该集合来自于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)) )))))) ))
每个管理器分别独立设置模块。管理器模块在OPAMC架构下可以无需改变直接使用。
包括四个模块:对象管理器模块、属性管理器模块、属性值管理器模块、行为管理器模块。
;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%))
;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%))
;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%))
;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%))
协调器独立设置模块,实现对象管理器、属性管理器、属性值管理器、行为管理器的交叉通信,避免模块之间的交叉引用,进行统一管理、统一对外接口。协调器模块在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%))
以上的对象管理器、属性值管理器、行为管理器模块中,均出现了(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)) ;……
OPAMC架构是一个伸缩性很强的以面向数据编程的软件框架,其速度不如DCS,但扩展性是其重要特点。我把它用在我本人实现的面向对象绘图的软件中,目的是为了实现”一切皆属性“的思想,让绘图处理过程逻辑上高度统一。
大家如对OPAMC架构有不同的意见或建议,欢迎进行评论。
以上内容用Racket语言的Scribble编辑并编译生成。