Creative Commons License
本Blog采用 知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议 进行许可。 —— Fox <游戏人生>

游戏人生

游戏人生 != ( 人生 == 游戏 )
站点迁移至:http://www.yulefox.com。请订阅本博的朋友将RSS修改为http://feeds.feedburner.com/yulefox
posts - 62, comments - 508, trackbacks - 0, articles - 7

简单的属性结构设计

Posted on 2008-12-28 02:44 Fox 阅读(1928) 评论(4)  编辑 收藏 引用 所属分类: T技术碎语

本文最早发布于我的个人主页

一般的RPG游戏中,玩家角色、NPC、物品、场景等一般都具有为数众多的各种属性,使用C++编码时,很容易考虑设计成为大量的成员变量和相应的存取函数:

class CObject
{
public:
    long GetAttrA(void) const { return m_lAttrA; }
    void SetAttrA(long lVal) { m_lAttrA = lVal; }
    long GetAttrB(void) const { return m_lAttrB; }
    void SetAttrB(long lVal) { m_lAttrB = lVal; }
    ...
    long GetAttrN(void) const { return m_lAttrN; }
    void SetAttrN(long lVal) { m_lAttrN = lVal; }

private:
    long m_lAttrA;
    long m_lAttrB;
    ...
    long m_lAttrN;
};

乍一看,非常清晰明了。在一个稍显复杂的项目中,想记住、甚至找到每一个属性是非常难的,况且除了属性,还有大量的逻辑处理,甚至是不同项目间的数据交互,比如将属性的数据库存取。这么做的缺点大致有这么几点:

1. 属性没有明确分门别类,尤其在多人协作、多模块编写时,往往上面一批、下面一批,甚至有重复属性、废弃属性,难于管理和把握;

2. 对于数据库存取,需要写单独的存取接口,而且一旦属性有增减、修改,存取接口要随之修改;

3. 通过接口函数进行简单的属性存取面临大量的堆栈保存,即使使用内联或宏定义,也是治标不治本。

针对上述问题,我的思路也比较简单:对基本类型数据进行二次封装。

struct tagDBAttrs
{
    long lA;
    long lB;
    ...
    long lN;
};

class CObject
{
public:
    const tagDBAttrs& GetDBAttrs(void) const { return m_DBAttrs; }
    void SetDBAttrs(const tagDBAttrs& rDBAttrs)
    {
        memcpy(&m_DBAttrs, rDBAttrs, sizeof(m_DBAttrs));
    }

private:
    tagDBAttrs    m_DBAttrs;
    string        m_strA;
    CObject*      m_pObjB;
    CShape*       m_pShapeC;
    ...
};
 

之所以提到基本类型数据,主要缘于基本类型构成的结构可以通过sizeof运算符直接确定,而像类对象等组合类型,其深拷贝、赋值、操作及赋值等逻辑则较为复杂,一般无法统一处理。

以上面的tagDBAttrs为例,对于基本类型数据处理具有非常大的优势,尤其在数据整体存取时,此外,增减基本类型属性也比较简单,而且不需要重写Get/Set接口,同时方便了对属性的分门别类处理。


此处顺便谈谈关于开发中的一些杂的体会。

对于工作不久的同学而言,拿到一个任务有可能出现以下两种情况:

1. 以快速开发为重,前期进展较快,在小型模块开发中顺风顺水,但在面对复杂任务时,因为对前期设计重视不足,后期会不断调整代码,甚至在基本功能开发结束后,因为逻辑架构问题,导致bug隐患较多较深,容易陷入泥沼,痛苦挣扎;

2. 随时随处追求代码结构的优美和执行效率的优化,精益求精,往往在前期设计上花费过多的心思,以达到较合理的逻辑结构,甚至拖延整个项目的开发进度,这种同学一般说来,代码质量较高,后期bug较少。

其实,上面说的就是我自己,我刚开始工作的时候,上面给我一个编写独立工具的任务,当时急于表现,缺乏项目实战经验,为编码而编码,逻辑结构不注重扩展性,设计不合理,在进行到后期时,面对出现的新需求无法应对,导致最后推到重来。

后来,慢慢接受了注重前期设计的观念,更因为自己的完美情结和代码洁癖,不仅经常调整自己编码中不优美的片段,甚至越俎代庖去修改其他同事的代码,追求处处的高效,耗费大量时间精力,难以自拔。

在任何一个项目中,都有轻重主次之分,也就是所谓的『8020原则』,关于『8020原则』怎么理解都行,我的理解就是,每个项目中的效率瓶颈是20%的核心代码,80%的时间和工作重心应该放到这20%的核心代码上。放到一个小的模块中也是这样,应该确定工作重心,在开发进度的限制下,对核心代码精益求精,对非效率瓶颈可以适当减少侧重。

对于新人,最难的就是从整体的高度确定20%的工作重心,这主要依赖于自身素质的提高和开发经验的积累。而对需求深入细致的分析和对逻辑精益求精的设计本身可以训练一个人分析问题、解决问题的能力。

总结一下的话,也正是我们项目组历来所倡导和坚持的:

1. 精益求精;

2. 细节决定成败,态度制约能力。

Feedback

# re: 简单的属性结构设计  回复  更多评论   

2008-12-28 09:33 by 万连文
struct tagDBAttrs
{
long lA;
long lB;
...
long lN;
};

This is POD.

# re: 简单的属性结构设计  回复  更多评论   

2008-12-28 10:01 by abettor
博主的文章好啊!类似的问题有人提过,他说他们有一个类里有上百个属性,而且命名大都是缩写(据说因为是行业术语),看起来相当痛苦。当然,最痛苦的是最后还要把这东西搞成接口,因为要在这个类上实现多台。于是,他要整出一个具有几百个方法的接口,然后再在其实现类里一一实现这些方法。简直让人吐血!

# re: 简单的属性结构设计  回复  更多评论   

2009-01-08 14:26 by getborn
其实一个类的成员函数中出现Get...Set的接口说明程序结构没有设计好。一个类除了封装数据之外就是封装行为,而Get,Set这2个是不属于对象行为的。

面向对象可以很好地解决搂主所说的第2个缺点,而Get,Set就破坏了面向对象的封装性,所以会造成第2个问题。

我原来也是写的有Get,Set,后来我发现这是从C语言带过来的毛病,要从另外的角度进行封装。比如楼主的游戏,不要单独为了某个东西的属性而1个类,要把角色,场景的行为放到类接口中,而Get,Set的操作隐藏到类里面。

# re: 简单的属性结构设计  回复  更多评论   

2009-08-17 19:24 by 李现民
把大量的get, set函数都写出来了,通常这意味了完成没有封装,或者说从访问性上这样做跟将该变量声名为public是差不多的,我觉得根本的解决方案还是要保持类的数据成员数目比较小才好

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理