如果你想转载这篇文章呢,请严格按照以下格式注明出处和作者
出处:http://www.cnblogs.com/anxin1225/p/4827294.html
作者:Anxin1225、Bianchx、Linker(其实他们都是一个人。。)
 
     行为树是一种简洁明了的整理业务逻辑的有效方法。至于他的好处,不做赘述。
     由于项目的需要,所以实现了一个非常简单的行为树,来应对我们的需求。之所以说简单,是因为我并没有实现很多控制节点,而只是实现了最基础的业务的三个节点而已。至于其他的你觉得有用的控制节点,可以自己修改出来。
     简单说说我实现的节点:基础节点、单条节点、列表节点、选择节点、顺序节点、取反节点。这几个节点分为相对较为基础的节点,和业务节点。基础的节点包括:基础节点、单条节点、列表节点。基础的节点的主要作用是定义,定义最基础的调用方法和关于子节点应该怎么样保存。业务节点包含选择节点、顺序节点和取反节点。他们的继承关系如下:基础节点是最基础的节点;单条节点和列表节点继承自基础节点;选择节点和顺序节点继承自列表节点;取反节点继承自单条节点。
 
     来简单说一下各个节点的作用
     基础节点:
          1、invoke函数,被调用时,返回true或者false
          2、destroy函数,节点被释放时会递归式的释放所有依附于此节点的子节点和曾子节点
          3、设置和获取 Describe 的函数,用于打印AITree时的结构描述
          4、设置和获取 Name 的函数,用于打印AITree时的名称描述和调用时,递归描述的打印
     列表节点:
          1、包含一个有序的子集列表,可以添加和获取子集列表的引用
     单条节点:
          1、包含一个子集节点,可以设置和获取子集节点
     选择节点:
          1、被调用时,如果没有子集节点则会直接返回false
          2、调用时,会依次从前往后执行,任何一个子集节点返回了true,则终止循环,直接返回true
          3、当所有的子集节点都没有返回true时,则会返回false
     顺序节点:
          1、被调用时,如果没有子集节点则会直接返回false
          2、调用时,会依次从前往后执行,任何一个子集节点返回了false,则终止循环,直接返回false
          3、 当所有的子集节点都没有返回false时,则会返回true
     取反节点:
          1、被调用时,如果没有子集节点则直接返回false
          2、存在子集节点时,则会调用子集节点,并且将结果取反并返回
 
实现了这些节点之后就可以实现以下图示的大部分功能(手比较残,又加上身边没有工具,所以用文字的表示吧)
先简单解释一下这个图什么意思,第一个是节点的名字,注入的时候写的,可以是中文也可以是英文,这个无所谓,毕竟只有这个地方在用。第二个参数是当前实例的描述,如果是用来帮助你理解这个树的
 
再简单解释一下,这段逻辑是什么意思。这个是一个宠物的逻辑,如果附近有金币呢,他就取捡金币;如果没有金币呢并且很长时间没有捡到金币并且很长时间没有回到主人身边了,那就回到主人身边,否则就随便走走。
 
其实这个逻辑真的挺简单的,如果,是按照普通的方式来写的话。就会在各种状态之间判断条件然后各种跳转执行。这样能实现,不过后期的维护可能更加费劲一些,如果使用配置行为树则相对简单一些,在修改的时候只需要添加新的分支或者减掉原来的分支就可以了。逻辑也相对更加清晰。
 
然后简单说明一下,怎么在我的这个小玩意里边扩展自己的东西。
1、在AITreeNodeType添加一个新的枚举,主要是用来确定Id用的,注入的时候用的(至于什么是注入一会再说)
2、然后继承比较基本的节点,一般情况下继承最基础的三个就好,最常用的就是AINodeBase,那我们就那AINodeBase来举例
3、然后实现virtual bool invoke(int level = 0, bool isLog = false);方法,level代表从根节点开始这是第几层调用,一般用作Log的时候前边有几个空格,isLog代表是否打印Log,你完全可以忽视这两个参数不管,当然你要实现对应的功能最好遵守这两个参数的约定,当然不遵守我也没有意见。
4、在类中添加一个私有的static AINodeRegister<类名> reg;然后在Cpp文件中编写AINodeRegister<类名> AINodeReleaseSkill::reg(NodeId, NodeName);来实现注入,第一个参数是之前你获得的Id,第二个参数是对应的节点名,可以不是类名,不过我推荐你还是用类名,只有查找的时候好找
 
可能放上一段代码更直观一些
//回到主人身边
class AINodeGotoOwnerSide : public AINodeBase
{
private:
static AINodeRegister<AINodeGotoOwnerSide> reg; public:
virtual bool invoke(int level = , bool isLog = false);
};
AINodeRegister<AINodeGotoOwnerSide> AINodeGotoOwnerSide::reg(ANT_GOTO_OWNER_SIDE, "AINodeGotoOwnerSide");
bool AINodeGotoOwnerSide::invoke(int level, bool isLog)
{
return rand() % > ;
}
 
说完了累的扩展,应该简单说一下什么是注入了,简单点说,就是我写了一个公开的帮助函数,用来接受Id跟一个创建节点的函数指针,然后把它们保存在的字典中,你需要调用的时候,我就从字典里边找找当初注入的函数指针,然后调用它,给你一个实例。至于为什么要写一个静态的AINodeRegister泛型类,是因为静态的初始化实在程序启动的时候会初始化,应用这个特性,我们就可以在初始化的时候把,想要初始化的内容注入到内存中。
 
其实说到这个地方,主要的逻辑已经基本上说的差不多了。还有一些其他的方面,比如说树的组装如何处理,如果是挨个编写他们之间的引用应该也会很麻烦。并且,使用这种结构处理业务逻辑的时候,业务内容就会分的乱七八糟什么地方都有,调试也可能会成为问题。
 
实现Id跟类型之间的关联之后就可以通过描述类型来创建类了,最后的实现如下
 
 AINodeDescribe des[] = {
AINodeDescribe(, , ANBT_SELECT, "根节点"), AINodeDescribe(, , ANBT_SEQUENCE, "是否拾取金币的判定节点"),
AINodeDescribe(, , ANT_RELEASE_SKILL, "附近是否存在金币"),
AINodeDescribe(, , ANT_PICKING_UP_COINS, "捡取金币节点"), AINodeDescribe(, , ANBT_SEQUENCE, "是否回到主人身边的判定节点"),
AINodeDescribe(, , ANT_RELEASE_SKILL, "是不是很长时间没有见到金币了"),
AINodeDescribe(, , ANT_PICKING_UP_COINS, "是不是很长时间没有回到主人身边了"),
AINodeDescribe(, , ANT_PICKING_UP_COINS, "回到主人身边的执行节点"), AINodeDescribe(, , ANT_PICKING_UP_COINS, "没事随便逛逛吧"),
}; int desCount = sizeof(des) / sizeof(AINodeDescribe); vector<AINodeDescribe> des_vtr;
for (int i = ; i < desCount; ++i)
{
des_vtr.push_back(des[i]);
} AINodeBase * rootNode = AINodeHelper::sharedHelper()->CreateNodeTree(des_vtr); 
AINodeDescribe初始化的时候接受四个参数:当前Id,父节点Id,当前节点创建的树节点具体类型,当前节点实例的描述。其中父节点如果是0的时候则会被当做根节点返回,这个一点要有一个哦,不然会直接返回NULL,并且申请的所有节点都会造成内存泄露。
 
起始这个地方可以吧参数都写到文件中,然后通过文件来进行初始化,不过,我这个地方只是为了演示用,所以直接写死也没有关系,不过你在用的时候,我推荐你写一个读取文件配置的方法,效果会更好。(因为你可以吧这段的逻辑整理直接做一个编辑器,让策划来进行对应的内容的整理。)
 
对了,这个地方,你可能是按照自己的想法来描写的这个文件,但是实际的执行结果可能跟你的想法并不一样,你可以进行如下处理来进行验证
    cout << "\n状态结构组织图 \n" << endl;
AINodeHelper::sharedHelper()->printAITree(rootNode); cout << "\n状态结构组织图 \n" << endl;
 
输出的结果呢,就是最上边那张图了
 
 
剩下的还存在一个问题,那就是调试问题了,我不可能在这么多内容中下断点,那跟下毒没啥区别。所以我们需要有一种方式来打印各个节点的运行结果。这个我的处理如下
    for (int i = ; i < ; ++i)
{
cout << "调用树开始" << endl; rootNode->invoke(, true); cout << "调用树结束" << endl;
}
其中invoke的第一个参数的意思为最基础的节点的届位,第二个参数为是否打印Log,如果不想调试的话,两个参数都不要填就可以。
 
 贴一下相关的文件

AITree.h

//
// AITree.h
// KPGranny2
//
// Created by bianchx on 15/9/15.
//
// #ifndef __KPGranny2__AITree__
#define __KPGranny2__AITree__ #include <stdio.h>
#include <vector>
#include <map>
#include <string> #include "AITreeNodeType.h" #pragma mark =============== public Helper Action ================== class AINodeBase; typedef AINodeBase * (* BaseNodeCreate)(); struct AINodeDescribe
{
AINodeDescribe()
{
memset(this, 0, sizeof(AINodeDescribe));
} AINodeDescribe(int id, int pId, int typeId, char * describe = NULL)
:Id(id)
,ParentId(pId)
,AINodeTypeId(typeId)
{
memset(Describe, 0, sizeof(Describe)); if(describe != NULL && strlen(describe) < sizeof(Describe) / sizeof(char))
{
strcpy(Describe, describe);
}
} int Id; //当期节点Id
int ParentId; //父节点Id
int AINodeTypeId; //智能节点类型Id
char Describe[256]; //节点名称
}; class AINodeHelper
{
private:
static AINodeHelper * m_nodeHlper; std::map<int, BaseNodeCreate> m_type2Create;
std::map<int, std::string> m_type2Name; public:
static AINodeHelper * sharedHelper(); void registerNodeCreate(int type, BaseNodeCreate create);
void registerNodeName(int type, std::string name); AINodeBase * CreateNode(int type); //创建节点 AINodeBase * CreateNodeTree(std::vector<AINodeDescribe> des, void * host = NULL); void printAITree(AINodeBase * node, int level = 0);
}; template <class T>
class AINodeRegister
{
public:
static AINodeBase * CreateT()
{
return new T();
} AINodeRegister(int type, std::string name = "")
{
AINodeHelper * helper = AINodeHelper::sharedHelper(); helper->registerNodeCreate(type, &AINodeRegister::CreateT); if(name != "")
helper->registerNodeName(type, name);
}
}; #pragma mark ================== 具体的内容 ================= enum AINodeBaseType
{
ANBT_SELECT, //选择节点
ANBT_SEQUENCE, //顺序节点
ANBT_NOT, //取反节点
}; class MemoryManagementObject
{
private:
int m_mmo_referenceCount; public:
MemoryManagementObject()
:m_mmo_referenceCount(1)
{
} virtual ~MemoryManagementObject()
{ } int getReferenceCount();
void retain();
void release();
}; class AINodeBase : public MemoryManagementObject
{
protected:
std::string m_nodeName;
std::string m_nodeDescribe; public:
AINodeBase()
:m_host(NULL)
,m_nodeName("AINodeBase")
,m_nodeDescribe("")
{
} virtual ~AINodeBase() { } void * m_host; //AI的宿主 virtual bool invoke(int level = 0, bool isLog = false) { return false; } virtual void destroy(); virtual void setDescribe(std::string describe);
virtual std::string getDescribe(); virtual void setName(std::string name);
virtual std::string getName();
}; //列表节点
class AIListNode : public AINodeBase
{
protected:
std::vector<AINodeBase *> m_childNodes; public:
virtual void addChildNode(AINodeBase * node);
virtual std::vector<AINodeBase *> & getChildNodes();
virtual void destroy();
}; //单条节点
class AISingleNode : public AINodeBase
{
protected:
AINodeBase * m_childNode; public:
AISingleNode()
:m_childNode(NULL)
{ } virtual void setChildNode(AINodeBase * node);
virtual AINodeBase * getChildNode();
virtual void destroy();
}; //选择节点
class AISelectNode : public AIListNode
{
private:
static AINodeRegister<AISelectNode> reg; public:
virtual bool invoke(int level = 0, bool isLog = false);
}; //顺序节点
class AISequenceNode : public AIListNode
{
private:
static AINodeRegister<AISequenceNode> reg; public:
virtual bool invoke(int level = 0, bool isLog = false);
}; //取反节点
class AINotNode : public AISingleNode
{
private:
static AINodeRegister<AINotNode> reg; public:
virtual bool invoke(int level = 0, bool isLog = false);
}; #endif /* defined(__KPGranny2__AITree__) */

  AITree.cpp

//
// AITree.cpp
// KPGranny2
//
// Created by bianchx on 15/9/15.
//
// #include "AITree.h" #include <iostream>
#include <sstream> #define COMMAND_LINE 0
#define COCOS2D 1
#define AI_DEBUG 1 #if COCOS2D
#include "cocos2d.h"
#endif using namespace std; AINodeHelper * AINodeHelper::m_nodeHlper(NULL); AINodeHelper * AINodeHelper::sharedHelper()
{
if(m_nodeHlper == NULL)
m_nodeHlper = new AINodeHelper(); return m_nodeHlper;
} void AINodeHelper::registerNodeCreate(int type, BaseNodeCreate create)
{
m_type2Create[type] = create;
} void AINodeHelper::registerNodeName(int type, std::string name)
{
m_type2Name[type] = name;
} AINodeBase * AINodeHelper::CreateNode(int type)
{
AINodeBase * nodeBase = NULL; do
{
map<int, BaseNodeCreate>::iterator iter = m_type2Create.find(type); if(iter == m_type2Create.end())
break; nodeBase = (*iter).second(); if(nodeBase == NULL)
break; map<int, string>::iterator iter_name = m_type2Name.find(type);
if(iter_name != m_type2Name.end())
{
string & name = (*iter_name).second;
nodeBase->setName(name);
}
}while(0); return nodeBase;
} AINodeBase * AINodeHelper::CreateNodeTree(vector<AINodeDescribe> des, void * host)
{
if(des.size() == 0)
return NULL; #if COMMAND_LINE && AI_DEBUG
cout << "CreateNodeTree all count = " << des.size() << endl;
#endif #if COCOS2D && AI_DEBUG
CCLOG("CreateNodeTree all count = %d", (int)des.size());
#endif map<int, AINodeBase *> m_type2Create; AINodeBase * rootNode = NULL; for(vector<AINodeDescribe>::iterator iter = des.begin(); iter != des.end(); ++iter)
{
AINodeDescribe &item = (*iter);
AINodeBase * node = CreateNode(item.AINodeTypeId); #if COMMAND_LINE && AI_DEBUG
cout << "CreateNodeTree " << item.AINodeTypeId << endl;
#endif #if COCOS2D && AI_DEBUG
CCLOG("CreateNodeTree %d", item.AINodeTypeId);
#endif if(node == NULL)
continue; node->m_host = host; //注入宿主 if(strlen(item.Describe) != 0)
{
node->setDescribe(item.Describe);
} m_type2Create[item.Id] = node; if(item.ParentId == 0)
{
rootNode = node;
}
else
{
do
{
AINodeBase * parentNode = m_type2Create[item.ParentId];
if(parentNode == NULL)
break; AIListNode * listParentNode = dynamic_cast<AIListNode *>(parentNode);
if(listParentNode != NULL)
{
listParentNode->addChildNode(node);
break;
} AISingleNode * singleNode = dynamic_cast<AISingleNode *>(parentNode);
if(singleNode != NULL)
{
singleNode->setChildNode(node);
break;
} } while (0);
}
} return rootNode;
} void AINodeHelper::printAITree(AINodeBase * node, int level)
{
ostringstream oss; for (int i = 0; i < level; ++i)
{
oss << "\t";
} oss << node->getName() << " " << node->getDescribe() << " " << node; #if COMMAND_LINE
cout << oss.str().c_str() << endl;;
#endif #if COCOS2D
CCLOG(oss.str().c_str());
#endif do
{
AIListNode * listNode = dynamic_cast<AIListNode *>(node);
if(listNode != NULL)
{
vector<AINodeBase *> & childs = listNode->getChildNodes();
if(childs.size() > 0)
{
for (std::vector<AINodeBase *>::iterator i = childs.begin(); i != childs.end(); ++i)
{
printAITree(*i, level + 1);
}
} break;
} AISingleNode * singleNode = dynamic_cast<AISingleNode *>(node);
if(singleNode != NULL)
{
AINodeBase * child = singleNode->getChildNode();
if(child != NULL)
{
printAITree(child, level + 1);
}
}
} while (0); } int MemoryManagementObject::getReferenceCount()
{
return m_mmo_referenceCount;
} void MemoryManagementObject::retain()
{
++m_mmo_referenceCount;
} void MemoryManagementObject::release()
{
--m_mmo_referenceCount; if(m_mmo_referenceCount <= 0)
{
delete this;
}
} //最根层节点
void AINodeBase::destroy()
{
#if COMMAND_LINE && AI_DEBUG
cout << "destroy " << getName() << " " << this << endl;
#endif #if COCOS2D && AI_DEBUG
CCLOG("destroy %s %p", getName().c_str(), this);
#endif release();
} void AINodeBase::setDescribe(std::string describe)
{
m_nodeDescribe = describe;
} string AINodeBase::getDescribe()
{
return m_nodeDescribe;
} void AINodeBase::setName(string name)
{
m_nodeName = name;
} string AINodeBase::getName()
{
return m_nodeName;
} //列表节点
void AIListNode::addChildNode(AINodeBase * node)
{
m_childNodes.push_back(node);
} std::vector<AINodeBase *> & AIListNode::getChildNodes()
{
return m_childNodes;
} void AIListNode::destroy()
{
if(m_childNodes.size() > 0)
{
for(vector<AINodeBase *>::iterator iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
(*iter)->destroy();
}
} AINodeBase::destroy();
} //单条节点
void AISingleNode::setChildNode(AINodeBase * node)
{
if(m_childNode != node)
{
if(m_childNode != NULL)
m_childNode->destroy(); m_childNode = node;
}
} AINodeBase * AISingleNode::getChildNode()
{
return m_childNode;
} void AISingleNode::destroy()
{
if(m_childNode != NULL)
{
m_childNode->destroy();
} AINodeBase::destroy();
} //选择节点
AINodeRegister<AISelectNode> AISelectNode::reg(ANBT_SELECT, "AISelectNode"); bool AISelectNode::invoke(int level, bool isLog)
{
bool success = false; do
{
if(m_childNodes.size() == 0)
break; for(vector<AINodeBase *>::iterator iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
AINodeBase * node = (*iter);
bool inv = node->invoke(level + 1, isLog); #if (COMMAND_LINE || COCOS2D) && AI_DEBUG
ostringstream oss; for (int i = 0; i < level; ++i) {
oss << " ";
} oss << node->getName() << " invoke " << inv;
#endif #if COMMAND_LINE && AI_DEBUG
if(isLog)
{
cout << oss.str().c_str() << endl;
}
#endif #if COCOS2D && AI_DEBUG
if(isLog)
{
CCLOG("%s", oss.str().c_str());
}
#endif if(inv)
{
success = true;
break;
}
}
} while (false); return success;
} //顺序节点
AINodeRegister<AISequenceNode> AISequenceNode::reg(ANBT_SEQUENCE, "AISequenceNode"); bool AISequenceNode::invoke(int level, bool isLog)
{
bool success = true; do
{
for(vector<AINodeBase *>::iterator iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
AINodeBase * node = (*iter);
bool inv = node->invoke(level + 1, isLog); #if (COMMAND_LINE || COCOS2D) && AI_DEBUG
ostringstream oss; for (int i = 0; i < level; ++i) {
oss << " ";
} oss << node->getName() << " invoke " << inv;
#endif #if COMMAND_LINE && AI_DEBUG
if(isLog)
{
cout << oss.str() << endl;
}
#endif #if COCOS2D && AI_DEBUG
if(isLog)
{
CCLOG("%s", oss.str().c_str());
}
#endif if(inv == false)
{
success = false;
break;
}
}
} while (false); return success;
} //取反节点
AINodeRegister<AINotNode> AINotNode::reg(ANBT_NOT, "AINotNode"); bool AINotNode::invoke(int level, bool isLog)
{
bool success = false; do
{
if(m_childNode == NULL)
break; success = !(m_childNode->invoke(level + 1, isLog));
} while (false); #if (COMMAND_LINE || COCOS2D) && AI_DEBUG
ostringstream oss; for (int i = 0; i < level; ++i) {
oss << " ";
} if(m_childNode != NULL)
{
oss << m_childNode->getName() << " invoke " << !success;
}
else
{
oss << "no child";
}
#endif #if COMMAND_LINE && AI_DEBUG
if(isLog)
{
cout << oss.str() << endl;
}
#endif #if COCOS2D && AI_DEBUG
if(isLog)
{
CCLOG("no child");
}
#endif return success;
}

  

 
 
可能这个描述还不是很明确,你可以给我留言,我尽量给你解答 

C++ 版本的 行为树的简单实现的更多相关文章

  1. 『zkw线段树及其简单运用』

    阅读本文前,请确保已经阅读并理解了如下两篇文章: 『线段树 Segment Tree』 『线段树简单运用』 引入 这是一种由\(THU-zkw\)大佬发明的数据结构,本质上是经典的线段树区间划分思想, ...

  2. 【ARM-Linux开发】内核3.x版本之后设备树机制

    内核3.x版本之后设备树机制 Based  on  Linux  3.10.24  source  code  参考/documentation/devicetree/Booting-without- ...

  3. 《机器学习Python实现_10_10_集成学习_xgboost_原理介绍及回归树的简单实现》

    一.简介 xgboost在集成学习中占有重要的一席之位,通常在各大竞赛中作为杀器使用,同时它在工业落地上也很方便,目前针对大数据领域也有各种分布式实现版本,比如xgboost4j-spark,xgbo ...

  4. RDIFramework.NET V2.7 Web版本升手风琴+树型目录(2级+)方法

    RDIFramework.NET V2.7 Web版本升手风琴+树型目录(2级+)方法 手风琴风格在Web应用非常的普遍,越来越多的Web应用都是采用这种方式来体现各个功能模块,传统的手风琴风格只支持 ...

  5. LA、Remember the Word (字典树, 简单dp)

    传送门 题意: 给你一个初始串 S,strlen(s) <= 3e5  然后给你 n 个单词. n <= 4000,  每个单词的长度不超过 100 : 问你这个初始串,分割成若干个单词的 ...

  6. 动态树(Link-Cut-Tree)简单总结(指针版本)

    Link-Cut-Tree(动态树 LCT) 1.定义 1. 偏爱子节点: 一颗子树内最后访问的点若在该子树根节点 X 的某个儿子节点 P 的子树中,则称 P 为 X 的偏爱子节点. 偏爱边:连向偏爱 ...

  7. C#.NET 大型通用信息化系统集成快速开发平台 4.0 版本 - 用户权限树的实现 -- 权限递归树

    业务系统里经常会需要计算类似的树形权限树的业务需求 1:往往会有一些需求,a 对 b 有权限, b对c 有权限, 等等. 2:还需要很直观的看到,整个权限的树形关系,一目了然的那种. 3:程序调用简单 ...

  8. POJ 3630 Phone List(trie树的简单应用)

    题目链接:http://poj.org/problem?id=3630 题意:给你多个字符串,如果其中任意两个字符串满足一个是另一个的前缀,那么输出NO,否则输出YES 思路:简单的trie树应用,插 ...

  9. 比特币区块结构Merkle树及简单支付验证分析

    在比特币网络中,不是每个节点都有能力储存完整的区块链数据,受限于存储空间的的限制,很多节点是以SPV(Simplified Payment Verification简单支付验证)钱包接入比特币网络,通 ...

随机推荐

  1. Quartz 2D在ios中的使用简述一:坐标体系

    Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境,官方文档:Quartz 2D Programming Guide. 一.坐标体系 这样的坐标体系就导致我们使用Quart ...

  2. Reactive Extensions(Rx) 学习

    Bruce Eckel(著有多部编程书籍)和Jonas Boner(Akka的缔造者和Typesafe的CTO)发表了“反应性宣言”,在其中尝试着定义什么是反应性应用. 这样的应用应该能够: 对事件做 ...

  3. ASP.NET Core中使用URL重写

    ASP.NET Core 1.1 Preview 1 中新增了 URL Rewriting middleware ,终于可以进行 URL 重写了,实际使用体验一下. 首先要将 ASP.NET Core ...

  4. AngularJS基础入门初探

    一.AngularJS简介 1.1 什么是AngularJS (1)一款非常优秀的前端JS框架,可以方便实现MVC/MVVM模式 (2)由Misko Hevery 等人创建,2009年被Google所 ...

  5. .NET基础知识点

    .NET基础知识点   l  .Net平台  .Net FrameWork框架   l  .Net FrameWork框架提供了一个稳定的运行环境,:来保障我们.Net平台正常的运转   l  两种交 ...

  6. 摇钱树运营小工具UI设计.vsd

    去年,我负责公司的一个互联网投融资平台——摇钱树.系统运营过程中,业务和客服那边不断的反馈一些事情让技术这边协助实现.例如,土豪客户忘记登录密码后懒得自己重置,更愿意选择搭讪客服MM:再比如,客户多次 ...

  7. Tornado框架中视图模板Template的使用

    上文的程序中有这样一段: class MessageHandler(tornado.web.RequestHandler): def get(self): self.write(''' <htm ...

  8. Liferay7 BPM门户开发之38: OSGi模块化Bndtools、Maven、Gradle开发构建入门

    前言 OSGi是目前动态模块系统的事实上的工业标准,它适用于任何需要模块化.面向服务.面向组件的应用程序.Eclipse如此庞大和复杂的插件体系,就是基于OSGi.Liferay也是基于OSGi.OS ...

  9. Plant Design Review Based on AnyCAD

    Plant Design Review Based on AnyCAD eryar@163.com Abstract. AVEVA Review is used to 3D model visuali ...

  10. Android 生成LayoutInflater的三种方式

    通俗的说,inflate就相当于将一个xml中定义的布局找出来. 因为在一个Activity里如果直接用findViewById()的话,对应的是setConentView()的那个layout里的组 ...