虚跟模板可以兼得?!
由某个案例引发出来...
为什么要虚
在OOP的开发过程中,往往会有大量对象的定义和使用,很多时候我们需要将这些对象持久化的保持在外部文件里面,然后再在需要的时候将其读取进来。因为对象的类型数量都很多,一个一个去实现如何写入和读取就特别冗余且笨重。常见的解决方式便是序(反序)列化,简而言之就是为对象的基类都加上serialize/deserialize的虚方法,然后子类只需要实现如何组织需要写入和读取的数据就可以了。此后我们只要有一个serializable的对象便能对其进行读写了。
为什么要模板
但是关于具体的序列化的时候,便会遇到一些跟平台相关的问题,比如不能确定这个对象最后写入到的是一个本地文件、网络流还是JSON这样的中间载体的时候该怎么办?因此我们提供给序列化接口的序列化器这时变得不那么确定了。此外我们也不希望对象的序列化接口明确的知道序列化器是什么,因为这样会让其产生依赖使得对象的使用不独立。综上,我们希望一种模板化的序列化器,只有到对象用到它的时候才会被具体的展开使用。
虚跟模板可以兼得?!
所以我们需要给对象的基类定义一个方法看上去会是:
template<typename Serializer> virual void Serialize(Serializer& serializer);
但是遗憾的告诉你这是不可能的。为什么?
首先抛开C++的标准不说,单从编译器的实现上面就能否定这一点。虚函数具体是通过虚函数表来表现其多态性的,这个虚函数表实际上是一个函数地址的数组,在建立这个虚函数表的时候所有的虚函数都需要被实现了,这样才能找到有效的函数地址。但另一方面模板方法本身是并不存在的方法,只有在被用到的时候才被具体展开并实例化。因此模板方法的“不确定”性与虚函数的“必须确定”性在理论上便已经矛盾冲突了。
解决方案
首先,为了模板化,不得不先将virtual去掉,这样要求所有对象里面的序列号方法被重新声明为:
template<typename Serializer> void Serialize(Serializer& serializer);
但是作为序列化方法的调用者,它所能拿到的是所有对象的基类的指针或者引用,它也不能直接调用到子类里面的具体方法。为此目前我能想出的办法就是向下转型,例如:
void Serialize(Base& base)
{
FooSeraizlier fooSerializer;
switch(base.GetClassId())
{
case DeviedA::ClassID:
(static_cast<DeviedA*>(&base))->Serialize(fooSerializer);
break;
case DeviedB::ClassID:
(static_cast<DeviedB*>(&base))->Serialize(fooSerializer);
break;
case DeviedC::ClassID:
(static_cast<DeviedC*>(&base))->Serialize(fooSerializer);
break;
......
}
}
无奈的是类似这样switch case的向下转型必须要手写的,因此当类型过多的时候肯定是很悲剧的,但是如果你有类似python一类的脚本工具来帮你自动生成代码的话,那就放心的使用吧。
关于脚本
一些关于脚本的琐碎的思绪。
如果说Programming Language是对应基础框架以及底层兼容的偏硬性问题的解决方案,那Script Language便是某种意义上真正面向应用功能的解决方案。Programming Language的初衷是调用平台的各种资源与功能,为了达到最好的效果往往都是贴近底层开发的,在这方面“硬”的东西比较多,而且一旦敲定,便不会容易修改。简而言之,Hardcode大概就是这样一种意思。
太硬的东西无论是开发者还是客户都所不愿意见到的,他们不希望自己买了一辆自行车,轮子只能转到100圈,除非他们都是2B。
于是乎有一种叫Data Driven的概念出现了。大约来讲它属于已经被Hardcode编译成计算机本地指令的程序的外部配置信息。程序的代码已经实现了开发过程中能够预见的大多数功能,这些功能可以接收一定的可变的参数来达到一定程度的“动态性”。这基本上是目前绝大多数项目的一种比较统一的解决方案,无论是application,service或者game。
这种开发模式有一个比较重要的平衡点需要掌握:我们需要将多少功能给做出来,又需要将多少功能可以进行外部的配置。前者过多就会导致过多的硬编码,而后者过多就会导致过于零散的功能以及过于复杂的配置。
无论如何,硬编码是我们是在不想看到的,但是如果功能做得过于原子化,那外部的配置必然会相当复杂。要知道在一个团队里面,负责这部分数据生产的不一定都是城续猿。
有时候我们不能太明白的说明,我们到底想要一个什么样具体的功能,城续猿没办法做出一个抽象的模棱两可的功能然后再暴露一些有限的参数供团队里面其他成员使用,而后者往往也说不清楚自己到底想要什么样的东西。但就算双方能达成协议,通过修改代码来实现新功能以及调试的代价往往也是比较昂贵的。
那么,为何不让他们自己去做?
于是我们需要一种,简单,可用,且能比较高效开发的方式 —— 脚本。
理论上来讲脚本也是一种编码语言,它同样有分支,有循环,有变量,只不过其所在的层次不一样。脚本其实也是Data Driven里面的数据,只不过与单纯的贴图,动画,音效等纯数据相比它更有一种自我描述性。换言之它们能自己说明自己的目的,而不是简单的被贴到某个设置好了UV的模型上面去。
城续猿可以做好一系列的相对独立的功能,这些功能之间可以拼凑组合,相互调用,往往我们会将它们称之为API,最终它们会暴露给脚本使用。
在此基础上,如果说通过Programming Language实现的框架模型是“第一次开发”,那么通过Script Language我们可以在项目中进行“第二次开发”。这也就是一种理想的“准备阶段”往“生产阶段”过渡的方式。案例例如游戏中各种角色的AI,任务,界面UI等等。
待续…
Scala和Java的互操作
最近在实践中遇到一些Scala和Java互操作的问题,记录一下
BTW: gist + wordpress的gist插件各种好使啊 果断抛弃各种syntax highlighter插件
Scala Map Java Map
下面代码是两个隐式转换,用于在两者之间互转
Scala repeated parameters Java varargs
不好好看书就急着动手的杯具……纠结了很久才知道如何正确调用可变参数列表的Java方法
如果不使用: _*这个annotation告诉scala我要展开成varargs,不论你如果toArray如果反射new出Object数组都是没有用的,整个Array或者List会被作为一个Object传入,而不是展开成多个参数传入调用方法
来scala下Scala
Scala,所谓可伸缩可扩展,于是就蛋疼试试
首先因为scala的语句块都可以返回值,因此if else完全可以取代?:三元运算符的作用,不过写起来还是后者比较爽。那就自己扩展一把好了。
class BooleanEx(b: Boolean) {
def ?[T](func: Boolean => T) = {
func(b)
}
}
class AnyEx[T](o: T) {
def or(o1: T) = { b: Boolean =>
if (b) {
o
} else {
o1
}
}
}
object Helper {
implicit def Boolean2BooleanEx(b: Boolean) = new BooleanEx(b)
implicit def Any2AnyEx[T](o: T) = new AnyEx(o)
def main(args: Array[String]) {
(1 == 1) ? (true or false)
}
}
无奈的地方是没法使用冒号作为方法名(因为是关键字),以及?属于最高优先级层,所以后面的括号省略不能……
资料收集[置顶]
在这边放一些有用的资料索引,方便自用,慢慢收集整理了
git入门推荐,中文的哦
http://progit.org/book/zh/
twitter的scala教程
http://twitter.github.com/scala_school/index.html
stackoverflow的scala tag
http://stackoverflow.com/tags/scala/info
markdown语法
http://daringfireball.net/projects/markdown/syntax
关于UI的若干纠结
话说最近哥对UI的感情那是各种的错综复杂,似乎业界使用Flash来做In Game UI逐渐成为主流。望着ScaleForm各种强大和稳健,看着自家的Homebrew的UI库,各种兴叹,无奈各种BOSS光环哥只能将错就错了。
既然说到UI不得不提到的就是走哪里都必须很吃香的PS各种版本,做UI总得画个图切个图啥的伐。不过PS各种切,Flash再各种导入,各种费时又费力。不过人家大牌厂家的东西都不是省油的灯,jsfl和jsx简直就是拯救哥于水火之中的各种救命草。(话说为什么我每次提到这些上面的人貌似有些漠视的感觉......)
简单来说Flash是支持只用导入一张完整的大图,然后基于这张大图创建若干小元件(Symbol,Movie Clip或者Graphic)的,原理就像CSS Spirit。不过如果你想人肉在Flash里面去切那你注定会各种杯具的,因为你会发现Flash的切图功能简直渣到几乎没有。
于是你必须需要jsfl了。我们可以通过jsfl来精确创建各种元件,所谓精切就是说你可以直接像素级别定为图块的各种上下左右而不是靠鼠标凭手感去割,于是:
// Get the document
var doc = fl.getDocumentDOM();
// Get the library
var lib = doc.library;
// Get the selected item in library
var libItem = lib.getSelectedItems()[0];
// Get current fill object
var fill = doc.getCustomFill();
// Set the style to "bitmap", available since CS4
fill.style = "bitmap";
// I don't know what is this neither
fill.bitmapIsClipped = false;
// Set the name of the selected item to the fill object
fill.bitmapPath = libItem.name;
// Well, to get some parameters about this bitmap, you have to instantiate one in the scene first
lib.addItemToDocument({x:0, y:0}, libItem.name);
// And then get the instance
var bitmap = doc.selection[0];
// Then you need to create a mystery matrix
var matrix = bitmap.matrix;
// The spirit's left bound
matrix.tx = -l;
// The spirit's top bound
matrix.ty = -t;
// God knows what does it mean
matrix.a = matrix.d = 20;
// Or Adobe either
matrix.b = matrix.c = 0;
// Set the matrix to the fill object
fill.matrix = matrix;
// And use it
doc.setCustomFill(fill);
// Then it is time to create the spirit
doc.addNewPrimitiveRectangle({left : 0, top : 0, right : r - l, bottom : b - t},0,false,true);
这随之而来就带来了第二个问题,我如何知道每个Spirit的各种上下左右边界,目测加人肉记录似乎可行,不过面对成百上千的各种图的时候,你只会喊破喉咙都没人能救你。 于是jsx这次向你伸出了上帝之手。
只要你在PSD里面将图按照每个Spirit一个图层的方式进行管理,并且每个图层之间相互没有重叠部分,那就可以通过jsx批量的将图层边界信息导出:
// Get the document
var doc = app.activeDocument;
// And prepare to store something
var code = "";
// Traverse all the layers
for(var i = 0; i < doc.layers.length; i++)
{
// Get current layer's bounds
var bounds = doc.layers[i].bounds;
// Concatenate the bounds info of every layer
if (code != "") code += "|";
// Make the bounds info of current layer
code += layer.name + "," + bounds[0] + "," + bounds[1] + "," + bounds[2] + "," + bounds[3];
}
然后你只需要将这生成的code里面的内容,通过各种方式整到之前的jsfl里面去,然后各种华丽你就懂得了。
速成WOW插件DEBUG-[CWLK-3.3.5-Runevolution]
最近在WOW,国服终于开ICC了嘛,怎么也得去见阿傻子一面。10人团已经开到小阿面前,作为一个DPS DK,深感和上一版本的差距,于是从邪血二天一洗成双持传染冰。但这鸟天赋打起来实在太累人,于是就开始寻觅一个符文+疾病+buff的监控插件。还真被我找到个合适的,名字就叫~当当当当~Runevolution。
=== 以上都是废话 ===
这插件有个问题……第一次进是OK的,退出游戏再进后就各种报错,各种google无果之后决定自助解决……毕竟我还是个码农的嘛。
于是先看报错日志,接着翻插件代码,看配置文件,查API。最后终于被我发现Orz的作者把父框体结构直接存入了配置文件,而实际上只应该存放父框体的名字就好了……于是妥妥的加上:GetName()就妥妥的好了,起码看上去是好了,因为咱是速成的所以也不知道有没有后遗症。
总之附件在此,送给Google。Runevolution-3.3.5
原来身份证号是可以自我验证的
在Google Reader上别人share的文章看到的,二代身份证号码可以通过一种机制去验真。同样道理的也可以基于这种方式生成伪造的身份证号码,只不过也只能逃过本地验证,联网验证肯定还是死翘翘的。
蛋疼Groovy练手一段:
id = "787416969231782985"
factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] as int[]
results = [1, 0, "x", 9, 8, 7, 6, 5, 4, 3, 2] as String[]
sum = 0
factors.eachWithIndex { factor, i ->
sum += (id[i] as int) * factor
}
id[17].toLowerCase() == results[sum%11]
各位如果和我一样蛋疼的也可以把id换成自己的身份证号码run一下看看是不是正确。另外附上同样蛋疼的生成脚本一段:
id = ""
factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] as int[]
results = [1, 0, "x", 9, 8, 7, 6, 5, 4, 3, 2] as String[]
sum = 0
random = new Random()
factors.each {
ide = random.nextInt(10)
id += ide
sum += ide * it
}
id += results[sum%11]
Project Lombok给力的new feature
唔 刚SJ同学推荐了下Project Lombok的新特性,看了下果然V5啊,从此可以和那些乱七八糟的类型声明说byebye了(虽然只是某种程度上)……
简单来说呢 就是用了Lombok后腰不酸腿不疼走路也有劲了我们就有了val,可以像这样:
val map = new HashMap<String, Object>(); // 这东西等效于这样 final HashMap<String, Object> map = new HashMap<String, Object>();
看上去就像scala类型推断似的!但其实没那么强……只能推断你的初始化表达式的类型,而且现在只支持local variable和for each loop中的声明。
真相在这里->Project Lombok,对IDE的支持现在是eclipse和netbeans(with bugs),以及构建时对maven的支持。
BTW:0.9.3的stable包里没有val,在0.10.0-逼她2这个perview包里
一个很简单的JavaBean Validator
前几天和同事聊到对于复杂表单提交上来的数据需要校验很是麻烦,想到通过annotation注解校验规则到field上会清晰方便不少,然后查到JSR303就是干这事的,有一个实现是hibernate搞的hibernate-validator。
不过既然想到了就顺手实现下试试,因为只是简单尝试,也不顾什么JSR303了,想到哪写到哪,在某需求讨论会上花了半个多小时搞出了下面这个非常简单的Vifix Validator。现在只实现了一个NotNull、一个Regex以及一个可指定自定义validator的annotation。
如果哪位有兴趣看看我的悲催代码的,下载点这里XD->VValidate。工程通过maven3构建,测试在src/test/java/TestDriver.java。