| 甘草 的个人资料心寄笔端 附庸风雅照片日志列表 | 帮助 |
|
|
不要题目 不要主题 只要扯淡 刚才玩OSGi,有点成效了,那个framework在我心里,真的发芽了。(发芽这个词用得好,我以为很准确地表明了当前的进度。唉,语文真不是白学的。) 为了完成技术BLOG和非技术BLOG的分离,我现在把技术都放到这里了,有感兴趣的,继续踩啊。 [URL:http://healerkx.cnblogs.com/] 其实,我很久不写blog,是因为什么呢?笔落到这里,却总结不出来一个原因。唉,或许是缺少契机?没有灵感,又或者是懒惰了吧?激情退却了。都有可能,毕竟我的blog,不是记账式的entries。于是,我可以给自己一个理由,我不是在颓废,而是厚积薄发,是在等待,是在期待?是吗?我又不是那么确定了。 blog是一个好东西,不需要准备无数个笔记本就可以把年年岁岁都记录下来,每一步的心情和感悟。或许等到我老年痴呆了,我可以靠我的blog,找寻一些曾经的记忆。 前几年,我总是会在年终的时候,进行一下总结,但是到了年底的时候,你又偏偏是什么都没有拿到,总结也没有意思,所以提前吧~。我遇到了老朋友,我总是说,我混的不好。是的,我真的觉得我自己混得不好。幸好,我从来不绝望。只是没有一个妖怪,能帮我回到过去,可以重来一次。 哦,谁会在Space的文章里面Paste一首歌啊,明日晴れるかな,要不你们自己baidu吧,我就从那上面下载下来的。 是的,《求婚大作战》的主题曲,这几天在看,我已经看了一个月的日剧了,仿佛回到了小学,初中,高中。。。那个时候,趁着父母不在家的时候,就会等着卫视中文台(现在改名叫凤凰卫视了嘛),牧濑里穗 唐泽寿明 etc...虽然那个时候,我还不太懂,到了初中,高中。。。就变成铃木保奈美。。。后来一度不看了,现在想找回来那个时候的感觉。我挺喜欢松隆子在《恋爱世纪》里变魔术的那一段的。 我的进度很慢,但是我一直在继续:是的,我就不曾放弃,学习... ...想起了大学,快毕业的那个时候,一天晚上,和开勇,Noair,在#13号楼下,是13号楼吧? 楼下都是运动器械的那里,三个人居然谈起来未来。感觉这是电视剧里面才会有的情节,而且又是那种校园励志的。可惜,我今天才开始我当时的想法,会不会太晚呢? 在CSDN的排名,终于排到199了,这个礼拜又给踢出来了,看来原来那个199的哥们怒了吧? 不过我很久不怎么回答问题了,每天看看片,玩玩游戏,时间就都过去了,何况在CSDN上回答问题,其实也不算是什么正经的学习方法。不过我N线作战,也着实有点累。 现在和赵旭,王妍,还在企图搞点东西出来,但是进度很慢,毕竟大家都是该领域的初学者,而且又是在业余时间弄,更是... ...但是怎么都不会称为苦不堪言的,毕竟我乐在其中。 Xue同学很热情,居然把我加到X~R~u~b~y的项目中了,可是我还在学习阶段呢。最近几年,断断续续的,学了一些ANTLR方面的东西,对XRuby也有了一些了解,但是都不足以真正的commit。我觉得要是真正处在那样一个开发环境中,就好了,而现在,不知道的东西,都是靠自己找,猜,学,试验,幸好Xue都会认真地回复我每一封邮件。我希望自己能在未来几年,能花更多的时间在这上面,毕竟这才是我真正感兴趣的东西。哥对Web开发觉得没劲,等哥把一个基于OSGi的Web开发框架写出来的,再也不想玩这些东西了。 求婚大作战,真的很好看,真的,山下的日语发音好可爱啊。而我也深深地为那种青春的热情而感动。大陆的电视剧看多了,TVB看得也不少,日剧虽然看得少,但是发觉日剧很精巧,短短的11集,总是给人以很多期待和感动。 在公司,做项目,感觉确实学到了很多东西,但是我总是觉得缺乏发挥的空间,幸好今年我可以参与做一个Notes的插件,嗯,和Leader说得一样,做Client的Application,能发挥的东西很多,确实。不过他老人家也让我看到了这样一点,做JSR286 Portlet,也能发挥出来很多东西,于是让我对Web开发,有所改观,于是,我现在很想把这个框架写完。 真的能回到过去吗? 设计 什么是设计,我从什么时候开始,专注于设计了?其实,我只是在这个门口向里张望,算是初窥门径了? 不知道,我以为这就是设计了,设计是什么?我怎么感觉就是用自己的表达,讲出自己的想法,然后去说服别人?当然了,肯定是没有任何暴力存在这里的,东莞仔是不会插手的,就连网络暴力都没有,愤青其实都是小小鸟。 当然了,你是要去说服别人,这才是本事,而不是去征服别人,武力征服并不总是有效的,而且常常适得其反。... ...怎么说这么多废话?说设计。... 想起我的小时候了,在纸上画出了一个机关,像是一个土地雷。不过想想,可笑,好多为了设计而设计的机关算尽。可笑,今天我试着设计软件了。不能这么玩了,一个类搞的定的,就不能再搞出一个接口。 不过,我可不是那些实现主义的人,大多数时候,我还是倾向使用接口带来优点的,因为我相信接口产生的描述能力。 这些年,我也学习了很多动态语言的东西,Dock Typing,也深深印象了我。... ...哎呀,又说得太详细了,显得我太没有深度了,嗯,咱们说点高屋建瓴的东西吧,建筑还不算高的话,就来点云遮雾罩的。 我也想当架构师! 老实说,我看Python的源码已经有很久了,只是一直没有突破,以至于也没有写什么BLOG,也是我基础差,让国内两个哥们抢了先。毕竟我把很多精力放在了电视剧上,稍后我就把我这一、两年看电视剧的观后感发上来。书归正传,虽然先前没有收获,但是也有个印象了,果然是书读百遍,其义自见了!我也不知道我是不是和说出这句经典之言的前人,是不是有着同样的感受,但是我知道,那些看书学习没搞懂的,肯定是次数不够,并且,很有可能是方法不对。 不过,具体的内容我就不写在Spaces上了,毕竟那两个哥们也拒绝向我透露消息,我也不知道他们是知道还是不知道,但是我决定,别人问我,我再说,但是不会公布在blog上。 另外,最近我也在看OSGi的启动,看到国内搜到的blog就生气,想当年,学习tomcat,也学习源码的时候,资料就少,有也是关于tomcat启动的部分的源码剖析。现在依然如此,OSGi相关的源码剖析的博客,也都是写什么启动参数的,要是我,知道的仅仅是一知半解的,我就不发,要发我就一发不可收拾。这是气死我了。 今天在CSDN上,发现一个王姓,还是刘姓的架构师,写了一篇关于C/C++语言相关的blog,恰恰是他要写的重点,有一个很重大的错误。那我只好直言不讳了。顺便又看了看他其他的文章,有一些是关于设计模式的,写得还好,不过也就是那么回事,原来让我去他们那里当个什么架构师,也不是那么不可理喻的事情,而且也很符合我MBTI的性格测定。 等我把相关的技术搞得差不多了,有了一个阶段性的成果,我就发到我的博客园上。 Ferry and Silverlight 只是初步试验,把Ferry应用于Silverlight 2开发中,于是我试着把Ferry作为Silverlight工程的一个Reference,但是报出来的错误是,不允许添加一个非Silverlight的工程作为引用。 好吧,我建立了一个Silverlight的Class工程,然后把Ferry的代码全部Copy过去,居然只遇到了一个非常容易修改的问题就OK了。 .Net里面的List<T>类,在WinForm的程序中,有Find方法可以使用,但是在Silverlight的工程里,却没有,于是我简单地遍历了一下,就弥合了这个差别。然后在Page里面拖拽了一个Button,和一个ListBox,Button发起Notification,ListBox在响应这个Notification后,Add一项,结果是非常不错的。 在试验的过程中,因为一个函数的字符串填写错了,结果导致了我看不懂的运行时的错误,这也说明了Ferry的缺点,就是缺乏在编译期间发现函数注册错误的能力。 于是,我把这个工程命名为Ferry.Silverlight。现在有适用于WinForm的Ferry,也有可以支持Silverlight开发的Ferry了,我很开心。想起当初写Ferry的时候,为了应付跨线程访问UI的问题,心中盘算要不要在项目中直接使用Control.Invoke。结果有如先见之明,我没有使用它,这样,工程就不需要以来WinForm那些Assemblies了,而是把Control.Invoke的调用,通过Delegates的转发,转移到用户的Code中去了。于是当我移植这些代码的时候,居然没有遇到阻力,而下一步,就是运用相同的手段,解决WebClient和HttpWebRequest的差别问题。 赶在美国独立纪念日,把这个重要的工作启动了。 美国人民很幸运,法国人民也很幸运,他们的民主,自有,独立,没有牺牲太多的人,就实现了,而我们这个民族,死了无数的人,才到了今天这步田地。 GAE支持Java了 算是一个好消息吧,Google App Engine支持Java了,现在可以下载SDK了,还有Eclipse的插件,Deploy App非常方便,但是你要先Sign up,毕竟现在只是一个Early Look。
SDK Classes的White List上有File,但是没有Socket。似乎比Python的限制要少? C/C++ & Python 我想,这就是未来的DEV黄金组合吧? Pz_String在词法分析的时候,以下面的代码为例。 class String
{ public: typedef unsigned char byte_t; typedef unsigned long size_t; public: String() { } }; 这里面,每一个Token的长度往往不超过7,所以这个String在处理string length的长度小于7的字符串的时候,不需要在堆上分配内存。 而且整个类的sizeof(String)只有8,远远小于VC实现的std::string的sizeof(std::string)==28。 缺点是,很多关于字符串的操作会判断一个标志位,影响了速度,但是从词法分析的角度考虑,String类的整体表现会很好。
聚合了这个结构。 union string_t
{ struct SP { byte_t* ptr; unsigned short len; byte_t flag; byte_t unused; } s2; struct SA { byte_t arr[8]; } s1; } s; 整个代码是这样的。 #ifndef __STRING_HPP__
#define __STRING_HPP__ #include "Alloc.hpp" #define CRITICAL 0x07 class String { public: typedef unsigned char byte_t; typedef unsigned long size_t; public: String() { } String(const char* str) { size_t len = strlen(str); if (len < CRITICAL) { _construct1((const byte_t*)str, len); } else { _construct2((const byte_t*)str, len); } } String(String const& _right) { size_t len = _right.length(); if (len < CRITICAL) { _construct1((const byte_t*)_right.bytes(), len); } else { _construct2((const byte_t*)_right.bytes(), len); } } String(const char* str, size_t offset, size_t len) { size_t leave = strlen(str + offset); if (leave < CRITICAL) { _construct1((const byte_t*)str + offset, (len < leave) ? len : leave); } else { _construct2((const byte_t*)str + offset, (len < leave) ? len : leave); } } size_t length() const { return (s.SP::flag) ? (size_t)s.SP::len : (size_t)s.SA::arr[7]; } const byte_t* bytes() const { return (s.SP::flag) ? (const byte_t*)s.SP::ptr : (const byte_t*)s.SA::arr; } String replace(String const& str1, String const& str2) { return String(); } String substring(size_t offset, size_t count) { return String((const char*)bytes(), offset, count); } private: void _construct1(const byte_t* ptr, size_t len) { strcpy((char*)s.SA::arr, (char*)ptr); s.SA::arr[6] = 0; s.SA::arr[7] = (byte_t)len; } void _construct2(const byte_t* ptr, size_t len) { byte_t* p = Alloc<byte_t>::alloc(len); strcpy((char*)p, (const char*)ptr); s.SP::ptr = p; s.SP::len = (unsigned short)len; s.SP::flag = (byte_t)0xff; s.SP::unused = (byte_t)0x00; } private: union string_t { struct SP { byte_t* ptr; unsigned short len; byte_t flag; byte_t unused; } s2; struct SA { byte_t arr[8]; } s1; } s; }; #endif
测试一下: int s = sizeof(String);
String s1("Hello"); String s2("Hello world"); String s3 = s2; char* p1 = (char*)s1.bytes(); char* p2 = (char*)s2.bytes(); int l1 = s1.length(); int l2 = s2.length(); int l3 = s3.length(); String s4 = s2.substring(6, 5); int l4 = s4.length(); String s5 = s2.substring(6, 6); int l5 = s4.length(); String s6 = s1.substring(0, 3); int l6 = s6.length();
Pz_memcpy刚刚写了一个fast的memcpy,叫做Pz_memcpy;至于Pz,就是Proze的缩写,而Proze是什么,你们以后就知道了。 代码随便用,no copyright。 /* Everest 3.0 写Everest 1.0的时候,主要是为了Ref类模版的实践,它本身实践了内存池的设计,实现了内存管理和自动释放,很简单,仿造了STL的allocator,但是没有考虑到线程的问题。里面还有其他一些类模版,有些类似于Duo等实现。 在Everest2.0中,我加入了Thread,Timer,Event等类。它们的模版代码不多,主要是试验了我在前面若干个BLOG文章中提到了回调模型。并在我试着写ImageStudio的时候,发挥了作用。还有在其中的Logger类模版,Singleton模版,都在实际的工程中,我提炼它们,并使用过。从面向对象关系上,我又是参考了Java的类库设计。 我在HK_Project中,把带有IO模型,作为Policy traits的Socket实现了一番,主要是为了实践MCD的理念。它参考了ACE的一些设计理念。并且也加上了其他的一些类模版,这些都是为了实践。(Everest2.5) 最近在写Everest3.0,这次要实践的是编译模型,这个编译模型是一种C++编程风格的尝试,按照这个风格和规范书写,可以真正地构建一个有单根的派生结构,并且可以规避C++缺少Package机制,而无法循环引用的问题,其中,Ref类模版,有了单线程和多线程的不同特化版本,并且它承袭了以往自动管理内存的特点。这次也把之前版本的类引入进来,并且企图将其统一的归纳到这个单根风格系统中。 critical_lock(x){ } 并且提供了这样的语法糖。 到目前为止,我在以往进行的C++工程中,没有太直接地利用这些类(而且确实不容易直接利用,代码级复用做的不是很好),因为这些类都带有很大的试验性质,不过我仅仅是为了把我掌握的C++ templates,WinSock IO模型,线程相关的知识,等等理论,理念,理想,把它们融合起来。最后你看到的,尽管依然还是玩具。但是我愿意为了这样的理想而去实践。 Nothing more to say. I like coding for ideality. C++ 和Java(2)书接上文, 上回说了一些语法上面的问题, 这次说一些更复杂的语法.
多态,
C++的动多态, 一定是有virtual关键字参与的, 而Java的非private方法, 你可以认为是一个C++的virtual函数.
class Base
{
public:
virtual void function()
{}
};
与Java的Code:
class Base
{
public void function(){}
}
基本是一致的.
然后要说的就是C++支持多继承, Java不支持多继承.
此外要强烈抗议的就是大量的教科书在"Java不支持多继承, 但是支持多接口...", 这类的言词给许多Java的学习者造成了很大的误解, 多接口和多继承只是在语法形式上相似, 但是不具有更多的可比性.
Java的接口概念, C++用纯抽象类来表示.
class IEvent
{
public:
virtual void function() = 0;
};
这个和Java的
interface IEvent
{
void function();
}
是一致的.
但是实现多接口和多继承有着很大的差别, 特别是对于设计来说.
接口表示一种约束关系, 而公有继承表示is-a的关系, 或者保护继承表示implements-with的关系, 而接口表达的语义叫做can-do.
那么, 真正来完成多继承这个任务的, 往往是聚合, 当然了, 这个时候接口也可以帮忙, 不去强调约束的关系, 但是主要的功能继承源自于聚合, 而不是实现接口.
我之前写过一篇叫做"implements-with"的文章, 更多的从设计的角度讲述了多继承的意义.
C++模版 VS Java的泛型
在表达泛型的概念上, 二者是很相似的, 但是Java不支持特化和偏特化, 这在表达上失色很多, 就是C#也是有所逊色的.
但是我很难说清楚Java现在的泛型是如何的工作原理, 不过对于编程表达来说, 并不那么重要了.
好像在语法上, 这些就是最主要的区别了, 再多的就是细枝末节和彼此有很大的差异的地方了.
此外, 根据我多年混论坛的经验看, C++确实不支持反射, 别想了. 最多是模拟一个动态对象创建就好不错, 好幸福了. C++09也没有...
C++ And Java受到Melody小同学的启发,我决定写点关于Java和C++的比较的文章。我觉得这对于当前的“时局”(动荡啊,做Java的搞C++去了,做C++的搞dojo去了)是有帮助的。 那么围绕这个有几个异同点: C++中new出来的对象,要程序员自己去delete。 而Java的对象是依靠gc去回收的,C++程序员不释放堆对象,就会造成内存泄漏。(除非是进程结束了,当然了,复杂的情况我们不讨论,有些资源在进程结束后,依然不能得到释放。这就是程序员的罪过了) 而C++通过copy constructor表达Java的clone方法。这二者很是相似。它们的语意都是用一个已经存在的对象去构造另外一个同类型的对象。<参看电影<逃出克隆岛>> class Test { Test t2(t1); Test jt = t.clone();//Java代码如是 那么现在我们不能逃避C++的引用了, 比如说: function(Test& t) Test a; Test b; Test a;
//C++ //Java function(aString); function1(&aCppString); 而Java要通过一种Wrapper类来完成这个事情。 此外就是Java的内部类和C++的内部类,有着本质的区别。 class Test 另外,C++ STL的string类,和java.lang.String有着极其相似的语义,尽量用STL的string。简单。
如果C++代码中出现了void*的话,那么它要表达的意思,无非是个Object的感念。当然了,void*没有Object那些方法。
int a = 10; int* p3 = *p2; 我们再来看看C++的引用&出现的场景: 而在Java中实际也是有解引用这一个说法的,不过很少见,是在Java的四种引用中才会被提及。 先写这么多,遇到问题了,我再补充。
关于函数(方法)的思考(2)[191]
上次说的那些东西都是小儿科, 虽然其背后也隐藏着丰富的内涵, 但是今天讨论的函数(方法)就有些晦涩了. 这是什么呢? 上一次我们提到的函数都是关于其声明和调用的, 无非是返回值和参数列表的性质. 这次我们讨论函数本身.
话要从函数指针说起, 最简单的就是void (*p)(void);了, 这里p是一个类型, 一个指针类型, 和void*, char*没有更本质的差别了. 而且它本身并不携带更丰富的类型信息了. 类型都是由编译器来保证匹配与否的, 但是实际上它是__stdcall还是__cdecl的, 都是可以被无情的转化的, 当然了, 调用时出错几乎是一定的.
void(void), 几乎很少人知道这是个什么怪胎. 这个我们就不谈了, 因为今天我们关于的就是函数类型, 我说的是泛泛的类型概念.
函数指针的缺点我们前面说过了, 类型不安全, GetProcAddress的返回值就是一个很典型的例子了. 所以通常, 我们在C++里面用函数对象(仿函数), 这绝对是一个好东西. 它比函数指针好, 类型安全, 而且能保留状态(我记得我在那篇BLOG中提过这个保留状态的).
class Add {
int operator()(int a, int b) {}
};
重载就搞定了, 而且它实际的调用效率是非常高的. 尽管它看起来非常的笨拙, 但是编译器的优化是很可怕的, 我常常看那些汇编代码的时候, 有一种想哭的感觉, 它把我熟悉的语法变得面目全非了.
还有就是C#的delegate, 它是一个优秀的设计, 它最大的优点就是强类型, 并且, 它不区分成员方法, 还是静态成员方法. 是不是区分静态成员, 是一个非常重要的指标, 不碰C++的人就不知道成员函数指针的语法是多么BT的一种语法了.
但是我还是觉得C#的做法过于中规中矩了, 我感觉这一系列语法下来, 输入了过多的字符了.
当然了, C#3.0有了Lambda了, Python也有了, 但是这种方式并非是一种很万能的做法, Lambda函数通常过于短小, 不能描述复杂的问题.
现在我们开始讨论Ruby的Block和JavaScript的function... ...hoho~
我们先看C++的sort, sort(begin_iterator, end_iterator, less_functor);
而Ruby也会有类似的写法:
[1, 2, 4, 3].sort{|a, b| a<=> b} #Here, <=>是个很Ruby的用法, 就是相当于compare(a, b), 返回-1, 0, 1.
而这里Block就是一段子程序, 它有参数. 而我们真正想强调的是Block的"对象性", 在C++里, 那个函数对象你可以传入函数指针, 也可以传入函数对象. 而关键是我们把它当作一个对象来处理, 就像是一个String对象, 一个Regexp对象. 只是它有()操作符重载而已. 而Ruby也是一样的, Block有专门的参数来指示有Block传入. 我们都是把它作为一个对象来对待的. 我们还可以用Block完成很多事情, 很方便.
p (3..100).reject{|a| (2..a-1).reject{|b| a % b == 0}.size != a -2 }
但是Block和Ruby的def方法还是有很多差别的, 而且BEGIN END块, 和{}也是存在差别的, 而且这种差别很多Ruby开发者还不清楚. 而且变量的作用域也很含混. 但是Block却给了我们一个函数直接量的印象.
var f = function() { alert (false); };
于是, 我们看到了洒脱了JavaScript的函数了, 我们要讨论的并非函数最普通的用法, 也并非是函数作为构造函数的用法, 我们讲它的直接量的意义. 同样, 在这里, 我们依然在乎的是func...后面这一串文字所表达出来的对象性. 是的, f引用了一个函数, 这个函数我们用这样的语法表达了出来.
好, 高级一些的就是Closure了, 刚才sjjf用了一个小时的时间, 终于给我把JS的Closure和数学上的闭包统一起来了, 是我太笨了, 他从数学定义, 说到了lisp, 然后说JS, 我还是不懂, 最后也不知道是怎么样就开窍了. 我为了表示感谢, 简单地把我的理解写在下面:
要先从数学的自由变量和约束变量说起.
考虑对这个算式进行积分, xy dx, x:[0->1], 这里x就是约束变量, y是自由变量, 最后的结果依赖于y的值.
而JavaScript的Closure也可以实现这样一个闭包性质的代码.
<SCRIPT> <!-- function func(e) { return function() { alert(e); } } func(false)(); --> </SCRIPT> 那么这个就是一个简单的Closure了. 问题在于它可能存在内存泄漏. 而且会引发很多莫明其妙的问题, 反正我刚刚开始学习, 还没有遇到.
所以, 我们在把函数当作是一个对象的时候, 我们需要注意的是对象的生存周期. 突然感慨C++的优点, 就是复杂, 但是非常明确. 一旦你掌握了, 就不会出现模棱两可的事情了.
func = method(a, b) { return a + b; };
那么如果我们也在方法内返回一个方法对象的话, 也要注意对象的生存周期. 发现一个编译器的差异, 这个差异很有趣哦. 帮助Rational的同事解决问题的时候, 发现这样一个问题, 以下这段代码在Windows各种主流编译器上, Linux上的(gccX我不知道), MAC OS X的GCC4上存在不同的行为. 这种行为的影响还好, 不是很大.
代码如下:
class IA
{ public: virtual void function1() const = 0; }; class IB
{ public: virtual void function2() const = 0; }; class Test : public IA, public IB
{ public: Test(){}
virtual void function1() const
{ } virtual void function2() const
{ } };
void function(Test const& t) { IB const& b1 = (IB const&)t; b1.function2(); IB & b2 = (IB &)t;//没有进行偏移,.
b2.function2(); }
另外, 指针不存在问题. 关于函数(方法)的思考 同样, 这是一篇不值得阅读的文章. 我只是自己和自己辩论, 在这个辩论中列举对于函数, 或者说方法的看法.
首先, 我把C/C++的通常称之为函数, 而Ruby的通常称之为方法, 是这样的, 函数通常给人一种数学函数的形象, 比如说f(n) = f(n - 2) + f(n - 1), C语言可以准确地刻画这一个表达. 更关键的是, 它刻画了一个多输入, 单输出的系统. 而Ruby, Matlab则不是, 它们的输入更加复杂, 输出可能并不唯一, 尽管它一样可以描述数学函数, 但是它相比C函数, 更加的具有"描述性质", 所以我更加乐意称之为方法. 实际上, 从表达上, 并没有本质区别. 只是我在行文的时候, 函数可能更多指代C/C++的function, 而方法更多指代Ruby这种method.
第一, 是关于返回值的问题, 主要讨论两个子问题:
1, 返回值类型.
函数常常返回固定类型的对象, 这很好, 函数的调用者很清楚应该如何定义一个变量去接收这个函数的返回值. 但是考虑这样一个问题, 一个max方法, 比较3和5.2两个不同类型的值, 我们可以肯定的是这个比较是理所当然的, 但是返回值却没有办法定义了.
C++很难处理这样的问题, 而且不简单优雅. 所以在这个问题上Python和Ruby的方式更加吸引人.
def max(a, b)
a > b ? a : b
end
当然了, 这一个系统需要的是一个变量不需要声明类型的环境. 这样矛盾就产生了, 很清楚, 类型不再安全了.
再考虑factory的问题.
IFactory* CreateFactory();
我们需要派生类实现这样的接口, 对于经典面向对象的程序员来说, 认为这一切是天经地义的事情, 派生, 重写. 我们把约束性认为是一种必然的. 因为在编译期, 我们就可以确定一个Factory的实现是不是实现了父类的虚方法. 而当我们调用IFactory中的方法, 比如说CreateProduct的时候, 心理上更加踏实.
这种范式的这种做法似乎就是被称之为侵入式(如果不是这样, 指出来, 这里我也有些模糊).
def createFactory
end
这样, 我们就无法约束了, 只有在运行期, 通过返回对象是否能调用createProduct才能知晓是不是"对得上"了, ("对得上"就是类型匹配适当的意思, 我在一开始写Ruby或Python代码的时候, 心理没底, 虽然我知道那个方法一定要传入一个字符串对象的, 但是我都不知道我会不会传递错了.)
但是这种做法, 我们不用强制一个Factory类去继承自某个接口类了. (不过, 通过这一轮辩论, 我察觉到的是接口的约束性)
利弊都是存在的, 然而我希望更加安全. 我更希望在一种简单的表达上, 实现这种约束, 而这种约束的语法不影响表达, 就是说, 它是可有可无的, 我们可以用最简单的语法表达出我们的意图, 如果需要的话, 我们采用其他的语法(独立于这个语法的表达)来进行约束. 它看起来如果像C++的Concept就可以了.
2. 返回多值问题.
返回多值就没有太多要说的了, 我很不喜欢返回单值的函数, 在我跟有着多年编程经验的人讨论这个问题的时候, 他们依然对int* int&这样的参数用来返回值表示不解. 而且对于Java的做法, 用一个新的class来封装返回值的做法太可恶了.
纯粹的Java程序员将其认为是一种必然, 但是显然, 它带来的是类数目的增多, 并且你需要new这样一个实例去返回, 谁知道编译器的优化会达到一个什么样的程度? 谁又知道gc的效率是怎么样的? (理论上, 我最担心的就是劝服单范式的程序员)
matlab就可以返回多值, Ruby也可以, 那一切看起来很简单, 易读, Ruby通过一种平行赋值的方式来进行这种多返回值的问题. 看起来很不错.
method function()
{
return a, b;
}
a, b = function();
第二, 参数列表.
和参数列表相关的东西很多, 首先就是重载规则, 然后是不定参数, 最后是参数默认值问题.
1. 重载, 高级语言几乎都提供这种性质, 但Ruby没有, 我们不能忽视重载带来的好处, 就是可读性.
def max_int(a, b)
def max_string(a, b)
这样的代码是让人难过的, 如果用统一的名字表达统一的想法是最好不过的了, 但是重载显然会和很多东西相冲突, 重载的选择就是最主要的矛盾了.
比如java的重载函数, 如果传递null的时候, 很多程序员都不知道会走哪个重载方法, 居然是String为参数的重载函数, 开始的时候我也怀疑过.
而且当涉及到类型转化的时候, 重载选择规则就更加难以说明白了. Ruby的表达性很好, 但是去掉了重载函数, 是否会影响这种表达呢? JavaScript也没有提供这种性质. 或许我们可以通过其他的方式来弥补这种缺憾?
method max(a, b)
{
if (typeof(a) == int && ...) return max_int(a, b);
else if (typeof(a) == string && ..) return max_string(a, b);
}
不知道大家对于这种做法会有什么样的看法? 至少在效率上是有损失的. 但是对于动态语言来说, 即便提供了重载功能, 也会在运行期探测实例的类型, 来决定走哪个重载函数. 或许不提供和提供都没有什么大不了的了.
当然了, Ruby也没有办法真正的给出重载方案, 因为Ruby的变量和参数没有类型的声明. 所以约束可能成为这一个问题的解决方案的关键.
method max(a : int, b : int)
method max(a : string, b : string)
约束信息将成为重载函数的一部分内容, 而不加约束的代码, 我们可以默认是以object为约束. 似乎这样的代码按照Java的重载规则进行就可以了.
2. 不定参数.
C/C++的不定参数是非常恶心的, 也很难使用, 你可以baidu 老迈的blog来找到相关的文章, 写得是很不错的. 我还没有真正去见识Java6的变参性质呢, 所以现在看来, Ruby的做法是相当不错的. 可惜也稍微有些难于理解.
Ruby的做法相当于需要Ruby程序员理解取地址和解引用那样的互逆操作了. 但是在使用上是非常方便的. 不过, 用一种比较笨的办法的话, 似乎还是比较容易学习的, 而且在使用上也并不笨拙.
method func(a, b)
{}
func(2, [3, 4, 5, 6]);
需要调用者多写的就是一个'['和一个']'了. 而且这是相当容易理解的. 只是我还不清楚这有没有什么不妥当的地方.
3. 参数的默认值, 是同上面两个因素可能冲突的原因, 很容易理解. 我在此不会证明这一点, 但是默认参数是可以通过不定参数的方式模拟解决的.
印象中, C#是没有提供这种性质的. 考虑一下为什么一个进化到极致的语言不提供这种性质呢?
显然, 可能造成很大程度的混淆, 所以最好不提供.
显然, 就这三者而言, 最重要的就是变参数列表了, 其他二者只是一种表达方便性的问题了. 后两者以重载为要, 默认者次之.
如何学习C++ templates 我对Java和.Net的泛型有了一个大致的了解,也试探过D语言的模板,但是总的来说,和C++ templates相比,它们根本不配与其相提并论[这里, Atry的说法更可信, 关键是我对D语言的了解都很少, 就轻易下了结论了. 我也询问过别人了, D的模板应该是更出众了.],这并非我对模板的偏好,而且在我实际的应用中发现的,我临时写这篇文章,并不打算把C++ templates的优点都说出来,只是想给刚刚接触templates的人一个简单的导读。
首先,我建议要学习STL的使用,只有会使用了STL才能知道STL的好处,并且了解模版在其中的作用。特别是其中的vector具有典型性,可以了解C++ templates在泛型方面的做法,这一点和Java,.Net的泛型很相似。紧接着就可以看侯杰那本《C++源码剖析》了,理论上,不需要对C++ Templates的所有细节有个了解,就可以看懂这本书了,主要是看这本书前面的部分,特别是关于traits的用法。这一点是非常重要的。
然后想对模板有个更深入的了解,可以看<C++ templates>,不过这本书很有可能脱销了。这本书是值得慢慢翻看的,而且恐怕也只能慢慢地看了,里面涉及到的内容几乎是在接触C++ templates之前都不会在其他的地方提到,比如POI,SFINE等等。而且这里不但要学习语法,也要熟悉一些基本的技巧,比如前面提到的traits,还有policy等等。另外书中也有关于模板的未来发展的讨论,时隔多时,马上就要见到C++09了,里面有些预言都要成真了。书在后面讨论的meta-programming也可以了解一下,这些都是Boost库的基石。[这里又忘记说的是特化和偏特化是C++ templates最重要的基础]
然后推荐的是MCD(Modern C++ Design),主要是介绍Loki库的主要技术Policy,并且前面也讲了Typelist,这些都可以加深对C++ templates在policy classes设计方面的理解。而且后面讲述的几个类,阅读后也会对自己的设计粒度产生影响。
之后就是许多介绍Boost的书籍了,大多没有中文的译本,偶尔有的不是脱销,就是被认为翻译得不好,而且Boost库适合使用,其源码未必适合阅读,因为目前的Boost源码在适应C++09之前的编译器做了很多技巧性质的处理,以后再阅读似乎更好一些。
读书是一个方面,关键是要自己动手实践,在动手的过程中,会发现许多以前未见过的编译错误,链接错误,和许多书上说可以,却确实是不能通过的地方,也有许多书上说不行,而实际上编译器已经弥合了这些问题的。而这些编译器的差异往往又只是一个小事情,更重要的是体会泛型,类型安全和其他模板的用法。最好的例子就是也试着去写一个泛型的容器,比如Array,Stack什么的。然后再怎么发展,就是自己的事情了。 Implements-with 本文写给初学者,昨晚给朋友讲继承的时候举的一个例子,可以就此体会到多重继承并非那么可恶。
开始的时候讲为什么要封装,然后讲为什么要继承,然后讲了Public继承表征is-a的关系,然后就讲到这里了,我继续说这个例子。
如果我们确认了继承可以扩展一个类,那么我们先用继承来扩展一个普通的Button类为ImageButton类,代码如下:
class ImageButton : public Button
于是我们继承了Button类的许多操作,现在我们的ImageButton类要做到在Button上绘制一幅小的Image,来改善Button的外观表现。
于是我们可能会这样写代码:
class ImageButton : public Button
{
void OnDraw(HDC dc, ... ...)
{
//Code about how to draw a image on the dc.
}
};
现在,我们有一个新的需求了,我们有一个Label,这个Label上面也是有text的,但是我们希望一个Label上也可以有Image的展现,于是我们打算做同样的扩展,然后在上面绘制一张Image。
class ImageLabel : public Label
{
void OnDraw(HDC dc, ... ...)
{
//Code about how to draw a image on the dc.
}
};
现在,我们发现Code about how to draw a image on the dc都是一个模子下来的(很有可能),也就是说是可以进行封装的,这个功能就是在一个dc上绘制一张Image。
讲到封装,是个容易的事情,我们可以用一个函数去封装这个功能,比如说写成这个样子来完成这个工作。
void OnDraw(HDC dc, ... ...)
{
DrawImage(dc, image, ... ...);
}
但是函数本身很难维护状态(这里就不讲这个了,这个要对C++的函数对象有个认识,才能体会这句话。),如果我这个Button上面的图片要有一定的规律变化,那么Function并非是最好的一种选择。
于是,我们讲这样使用继承来实现一个ImageButton类。
class ImageButton : public Button, protected DrawImagePolicy
{
};
这样,我们可以从DrawImagePolicy中获得许多好处,这个应该是很显然的,并且DrawImagePolicy的成员会维护其状态,并提供绘图的逻辑。
实际上,即便我们不采用保护继承的方式,就是说把protected变成public,在逻辑上并不会引起影响。但是带来了如下的问题。我们承认public继承表示is-a的关系,但是protected继承是implements-with的关系。但是下面的代码会指出问题的隐患。
Button* pButton = new ImageButton(); //Compile OK;[1]
DrawImagePolicy* pPolicy = new ImageButton(); //Compile OK, if public inherit form DrawImagePolicy;[2]
DrawImagePolicy* pPolicy = new ImageButton(); //Compile Error, if protected inherit form DrawImagePolicy;[3]
而从语意上来说,ImageButton是一个Button,并不是一个绘图策略,只是使用了这种绘图策略来实现自身。所以我们不该让【2】这样的代码编译通过。
实际上,这个只是理想的理论,第一,我们发现了多重继承的可爱之处,至少不要乱用,就不会引发我们痛恨多重继承的理由。至少我常常在写Java和C#的时候会常常怀念这种语法特性。第二,实际的例子中,并非一定要使用protected继承的,往往会有public继承也来表达这种关系,我以为Loki就是这样的。
template<class T1, class T2>
class MyClass, public T1, public T2
{};
这里往往使用了public继承,但是确实很勉强的认为一个MyClass的对象也是一种T1,或者是T2,T1和T2只是一个策略。
第三,像Ruby这种语言使用Mixins来完成这种表达,非常方便,有很动态。而Java也有一些“曲线”的方式来模拟这种Mixins的功能。
当然了,这种对象关系,是一种经典的关系,往往是要经过对系统中的对象不断认识才可以抽象出来的,而且不具有动态性,所以谨慎期间,可以采用其他的模式来完成这种设计,会给系统的重构带来更好的应变能力。
Fresco Fresco有一个TaskQueue,所以Queue就是要Task以队列的形式进入,然后被De-Queue的时候就是被执行的时候,往往有大量的任务经由Task被执行。所有的Task不一定需要被序列化,但是序列化的工作可以被TaskQueue和Task实现,这个并不复杂,被执行的Task是被随即加入队列的,没有前后顺序。
在设计上,不一定需要一个TaskStart。任何一个Task都可以是第一个Task,而且可以使用TaskEnd作为最后一个Task,而避免对线程进行强行中止操作。
然而,并非每一个Task都是被顺序执行的,我们有理由设计出一个紧急的Task,可以被放到任务队列的前面。当然了,实际上这种做法还不如新创建一个Thread去执行呢,但是,这样做的后果是减弱了紧急任务和其他任务的关系。
还有就是,任务和任务之间有并行的关系,也就是说,我们可以让一个任务去做1+2的工作,结果是a,另外一个任务是3+4,结果是b,另外一个任务是计算a * b的结果。这里的例子完全没有必要用并行计算的,只不过是一个例子而已。
而作为一个Task类,从设计的角度来说,我们并不好干预这个类将会被如何执行,例如说它可能和其他任务的串行或是并行关系,而只要关注这个Task做什么事情就可以了。(理论上,Task本身运行于什么线程也是不需要考虑的,由一个可以被称之为引擎的对象来复杂分配线程资源即可,而Task本身是不是需要创建线程,那是另外一回事情了。)
那么我们可以抽象出来一个称之为TaskContainer的对象,这个对象本身是执行Task的容器,作为容器它完成以下的事物,第一,运行Task,并捕捉Task运行过程中所抛出的异常。并向UI进行报告,而Task的开发者根本不需要关系如何向UI报告这一回事情(当然了,Task可以自己弹出Form,来展示自己的错误信息,但是这并不被Fresco推荐)。而且Container会在UI展示上为Task做一些工作,Fresco框架会给出一个标准Error的展示Form。第二,Container协同于其他Container的运行关系,这个也是Container最重要的功能之一,作为一个TaskContainer,只能加载一个Task,运行这个Container就是执行Task.Execute();。而这个Container和另外一个Container的关系可以是串行的,也可以是并行的。
需要详细说明的是,从A Container到B Container的执行顺序是一个串行关系,也就是A Container不完成任务,就不会触发B Container。这个很容易理解。如果A于B Container是并行关系的话,我们可以认为是从某个任务(可能是'开始任务')之后,创建了多个执行线程。两个不同的线程分别执行A Container和BContainer。这个是并行任务的发起,相对会简单很多,我们考虑并行任务的合并(Join)。这个和合并线程的概念很相似,是个等待的关系,我们设想A和B都归结于C,也就是说A执行完后,并且B也执行完后,才能执行C Container。那么在设计TaskContainer类的时候,就要处理这种Container之间的关系了。
实际上,一个Container也就是一个节点,这个节点有双向的前驱和后继,并且前驱和后继的数量不唯一。例如,A Container有两个前向节点的时候,表示A将等到前面两个并行关系的任务。如果A有两个后向节点的时候,则表示后面两个节点也是并行关系,A将同时触发它们。
实际上,这种节点关系,我认为可以表述绝大多数情况了。当然了,还有没有表述到的情况,就是A与B并发,但是任一任务结束了,就可以触发其后续的任务。那么这个关系是由后续的Container决定呢? 还是由3个Container决定的呢? 这是一个需求的问题了。或许这个问题的解决我们可以设计出一个具有其他含意的Container。也就是说,该Container可以接受多种节点输入,但是触发者一个就足够了,从代码的角度说是WaitForSingleObject还是WaitForMultiObjects的问题。于是处于抽象的考虑,Container有自身的属性,此外受到其他Container的影响。
class TaskContainer
property TaskContainer[] Back
property TaskContainer[] Prev
property bool waitForSingle?
end
第三,在界面上,作为Task的UI元素的父元素的Model,这个元素可以Load和Reject一个类型的Task。可以完成与其他任务的“连线”操作。这个类的对象可以支持这些操作。第四,这个TaskContainer可以作为和UI进行直接交互的对象,对节目进行Notify的回调操作,而Task本事就可以避免这种操作了,TaskContainer与UI进行的是异步事件交互,这种需要一个框架支持。而作为Task本身,只要一个同步的过程就可以了,这对Task的开发是个有利的因素。
于是,我们需要一个更强大的TaskQueue的来管理这些有序的Task了。当然了,不再是一个Queue了,是一个Graph,在这个Graph上有若干个TaskContainer,它们之间的关系可以被确定下来。然后我们从StartTaskContainer来遍历整个Graph。那么这个就是TaskGraph的作用了,但是用Graph这次词太计算机理论化了。
我们需要为这个Graph加入一些操作接口方法,可以使得加入的Containers的关系可以被调整。
接下来,就是我们考虑在Assembly内如何实现一个TaskContainer的事情了,问题涉及到,如何定义一个接口,面对Fresco的使用者,而隐藏一些接口,不被他们使用,而只是用来在程序集内使用。如何用一个优雅的方式表述TaskContainer的实现。
累了,先写到这里。 C# Callback Framework Final Solution(3) 终于把原理讲完了,现在提出问题,因为那个只是一个理论上的原型。
1.[多线程] 我们在实现上,认为UI线程是一个线程,实际上UI线程可能是多个线程。所以,我们要改写一些地方,主要是event那里,这里应该动态一些,也许应该由反射创建。并加入到线程映射表中。并且在注册的时候,用户不必关心当前的线程是哪个UI线程,要自动注册,并自动回调,这点不难。 2.[扩展性] 现在我们在模块之中定义好了许多Notify事件,而实际上这些事件可能由插件提供,就是说,直到写插件的时候,才能决定有什么样的回调。所以在枚举Notify下的delegates的时候需要改变。改为一种注册的方式。 3.[模块化] 命名空间需要改变,把改模块完全独立为一个单独的框架。可以在其他的系统中重用。 [AutoRegister]也要改变,这个名字过于泛泛,也不准确。 4.[命名冲突] 这个是我刚刚想到的,如果两个Delegate的声明存在于不同的模块,并且名字相同,怎么办?我不希望用重载来解决办法,那样增加了实现的难度,并且对客户的实现造成了混淆。 C# Callback Framework Final Solution(2) 现在,继续我们的第二部分。ICallback实现方案。
在说明这个之前,我们需要简单地介绍一下Tasks的系统。Task模型非常简单,我们在Task的实现中,会回调上层一些EventHandler。
interface ITaskType;
interface ITask; 嗯,这两个接口的意义在以后介绍,现在我们实现一个最简单的Task。
class DownloadFile : ITask
{
private void Download(... ...)
{
DoCallback("delegate name", params object[] objects);
} }
OK,我们来看看事情的复杂性,我们看下面的代码:
DownloadFile down1 = new DownloadFile();
DownloadFile down2 = new DownloadFile();
1. 如果,我们需要down1和down2的回调行为不一致的话,我们该怎么办?相同的Task Type具有不用的回调行为。
2. 如果,我们派生了DownloadFile类,一个派生类的callback的行为又如何?
所以,针对具体的实例,我们应该有实例方法SetCallback方法,第一个参数应该是他可能需要Delegate的名字。当这个被设置的时候,则说明实例需要有不同的回调方式,而不是采用默认的回调策略。
DoCallback方法在调用的过程中,先检查和this绑定的Callback,如果映射关系不存在,则调用默认的方法。
看到这里,觉得单继承的语言啊,就像结了婚的男人,兜了有几个零花钱,但是买点啥就没有了。
[隔夜了,继续写。越来越发现灵感来自于不经意的碰撞,昨天中午走路,并低头思考问题,撞到了一个写着STOP的牌子上了。]
回顾前面讲的部分,这部分说的是我执着于如何用一条语句来进行Notify回调,而且这个回调具有多态性[并非单纯虚函数的多态性,前面讲过的不同行为都是我考虑的范围]。
现在,我们讲述线程切换点方法的实现。
找前面这段代码:
if (null != dispmi)
{ Delegate dispd = Delegate.CreateDelegate(type, this, mi); methodDict.Add(metName, dispd); } 我们现在要讲的是基于这里的注册(Add)。向上Notify就容易多了,我们看下面的代码。
//这里是由Kernel调用完成切换方法注册的,而我没有采用把Control实例设置过来的做法,这样Kernel可以更换Control实例,但是我个人觉得这个更换是不需要的,我们需要的仅仅是随便一个运行于UI线程的Form。
public void SetNotifyThreadEntry(NotifyThreadEntry threadEntry)
{ this.threadEntry = threadEntry; } //这个是向上调用的必经之地~,它由Callback类调用。而参数Delegate d是从methodDict中查询得到的。
public void Notify(Delegate d, object[] objects)
{ threadEntry(d, objects); } 而threadEntry会调用到Kernel中的方法,如下实现在Kernel模块中。
control.Invoke(d, objects);
呵呵,都OK了吧? |
|
|