1. 所有公司都已经是某种程度的数据驱动。
2. 数据驱动不是增长的充分条件,也不是必要条件。
3. 数据很贵,性价比一般。
4. 数据的目标分为三个层次:观察、描述、行动。
5. 数据只能驱动符合特定模式的问题持续改进。
6. 数据项目也是项目。
7. 没有银弹。
—
图好玩的一个戏仿,不要当真。
1. 所有公司都已经是某种程度的数据驱动。
2. 数据驱动不是增长的充分条件,也不是必要条件。
3. 数据很贵,性价比一般。
4. 数据的目标分为三个层次:观察、描述、行动。
5. 数据只能驱动符合特定模式的问题持续改进。
6. 数据项目也是项目。
7. 没有银弹。
—
图好玩的一个戏仿,不要当真。
知乎上的一个问题:http://zhi.hu/WhTD
—–
豆瓣的问题在于它太过偏执于“长尾”(1) 和“自发秩序”(2) 了。
如果你仔细看,豆瓣的大部分产品都遵循类似的思路:首先满足用户的“长尾需求”,再想办法把同质用户汇聚成组,最后相同兴趣用户的组内行为可以形成正反馈强化整个系统的运转。当然,具体到实现这个流程的方式还可以再细分,比如利用算法的(FM)或者利用社交网络(小组)来分组;“组”的概念比较弱的(书影音用户利用友邻关系和猜你喜欢形成的“隐性组”)或者形式感非常强的(小组、九点、广场、阿尔法城)。
大体上讲,虽然单个产品各有成败,但沿着这个思路去解决用户的“长尾需求”是没有大问题的。
问题在于豆瓣把这个思路贯彻到了偏执的程度。于是就有了不断重复的广场、部落、阿尔法城这些“自发生长”的理念高于用户实际需求的“玩意”,以及在有热点的垂直领域里也偏去玩草根 provider 和 consumer 的小圈子——读书的征文比赛、音乐人、同城的小型演出票务(可惜没有一个的草根电影人圈子,不然就圆满了)。甚至在运营、推广层面也践行“不运营,自生长”的策略 (3)。
但是如果按照长尾(帕累托)分布不严谨的表述“20/80法则”来看,费了这么大力气满足的可只是20%用户(对长尾里80%事物)的兴趣……
当然这其中有一些美好的假设,比如从长尾中涌现80%人会感兴趣的热门内容。的确,豆瓣里也涌现出了“失恋33天”这样的 case,但是最终把握住这些 case 的可不是“不运营”的豆瓣。至于《长尾理论》里暗示的“随着互联网的发展,用户数量/兴趣曲线会变平缓,整体长尾化”也好像并没发生。
回到问题“为什么感觉豆瓣在走下坡”,“感觉”两个字用在这里很好。豆瓣也没走下坡,书影音评论这看家的东西不还好着。只是这些年早前就不关注长尾而是玩热点的别人家已经把制造热点这个事情玩烂了,现在都去忙移动互联网和穿戴计算了。而豆瓣还在这儿纠结“长尾”和“自生长”纠结了六七年了,自然让人有种不进则退的感觉。
一定要说个原因,大概就是世界不是豆瓣想像的那个样子吧。
1. 长尾年代,http://book.douban.com/review/1095674/
2. 豆瓣是一座仁者见仁、智者见智的城市,http://www.douban.com/note/12053438/
3. 杨勃:豆瓣社区不运营 用产品来表达,http://tech.163.com/…
“About Face 3″(以下简称AF3)主要关注于交互设计(Interaction Design)。
术语“交互设计”最早出现于19世纪80年代中期。当时负责设计第一台笔记本电脑GRiD Compass的两位工业设计师Bill Moggridge、Bill Verplank首先使用了这个术语。但是直到10年后,甚至可以说到了AF的第二版于2003年出版后,这个词才成为广泛使用。
一个比交互设计应用更泛滥的词是用户体验(User Experience)。AF3中认为用户体验设计可以被划分为三部分:
按:这三部分同样符合“结构-表现-行为”这一模式。所以它们和Web技术之间有着同样的结构。
AF3认为一个好的产品团队需要四个部分的协作:设计、工程、市场、管理。其中设计决定产品的形式和行为;工程负责开发;市场面向客户;管理负责协调。
按:AF3整体的一个问题是:对设计有一种“报复式的强调”。AF3介绍的方法包含两大部分,主体部分是设计本身的原则、模式等等;但除此之外还涵盖了传统软工中的需求和分析。AF3的立论点是,设计和用户的目标密不可分,所以设计师必须极深地介入需求分析,甚至说由设计师来完成需求分析。从上面的团队组成和职责也可以看出,实际上这是一个由设计师主导的团队。
AF3提供一系列交互设计需要的:原则(Principle)、模式(Pattern)、过程(Process)。交互设计的质量依赖于语境(Context),即用户是谁、他们要做什么、他们的动机是什么。大而无当的原则可以让设计更简单,但不能让设计更好。
———————————
Chapter 1 Goal-Directed Design
工业设计师Victor Papanek定义设计为:“the conscious and intuitive effort to impose meaningful order”。
AF3认为面向人(human-oriented)的设计活动包括:
按:注意此处设计含义的泛化,内容已经成为设计的一部分。
糟糕设计的主要原因:
交互设计需要基于对用户的理解以及认知原则。因此目前广泛使用的面向任务(task)的开发过程达不成好的设计:理解用户的任务有助于对系统进行细分,但是不能帮助设计师理解用户为什么使用这个系统——即用户的目标。设计师的工作不仅仅是理解用户的任务,他们还需要辨识出最重要的用户,进而决定他们的目标是什么,以及为什么有这个目标。
按:再一次注意设计师只能的泛化。
AF3提出了“面向目标的设计过程(Goal-Directed Design Process)”,以下简称GDD。GDD方法首先专注于研究用户实际如何使用产品,进而把研究结果转化为设计方案。
GDD强调设计对产品的重要性。它认为设计既要辨识用户需求,又要关注产品的细节——换言之,设计即是产品定义。因此GDD要求设计师深度介入需求分析工作,理解用户的目标,并通过一系列系统化方法,通过使用模型,把需求转化为最终的设计。
GDDP方法包含六个主要步骤:
成功的交互设计必须时刻关于用户的目标。
交互设计不是猜谜。(Interaction design is not guesswork.)
———————————
Chapter 2 Implementation Models and Mental Models
实现模型指系统内部实现(每秒24格画面)。心智模型指用户看待系统的方式(图像在屏幕上移动)。
按:心智模型应该是心理学的一个术语,不清楚此处的应用是否准确。
实现模型和心智模型存在鸿沟。因此需要Represented模型,即(在数字产品中)设计师选择将程序功能暴露给用户的方式。
好的Represented模型更接近的心智模型(画面加速、减速),而开发人员进行设计的缺陷在于其设计结果常常更接近实现模型(每秒格数更多更少)。
机械时代和信息时代需要不同的Represented模型。使用机械产品模型作为信息系统的隐喻,往往会限制信息系统的设计。好的设计需要信息时代的思维模式。
———————————
Chapter 3 Beginners, Experts, and Intermediates
设计的一个难题是如何在统一的设计中兼顾初学者和专家。
然而大部分用户既不是初学者也不是专家,而是中等用户(Intermediates)。实际上,大部分初学者会很快变成中等用户,但是他们永远也不会变为专家。
开发人员设计的交互往往更适合专家(比如,列出所有可能的操作,不区分操作的优先级);市场人员则需要更适合初学者的交互(方便推广)。
设计需要为中等用户进行优化,这包括三方面内容:
———————————
Chapter 4 Understanding Users: Qualitative Research
按:本章提供的其实是一种广义的用户研究方法,该方法并不限于设计
定性的用户研究用来帮助设计师理解产品的领域、语境和限制,包括:
定性研究需要回答的问题包括:
定性研究包含以下工作:
—–
1. Stakeholder访谈:
2. 领域专家(Subject matter expert)访谈:
3. 客户(Customer)访谈:
4. 用户(User)访谈:
5. 用户观察:
6. 文字资料分析:分析各种文字资料,包括:市场计划、市场研究、用户调研、技术规范、竞争对手研究等等。
7. 已有产品、原型以及竞品分析
—–
其他常用的用户研究方法还有:焦点小组——脱离语境的数据未必反映真实情况、基于市场的用户分析(market demographics and market segments)——理解谁愿意买不等于得到好的设计、可用性和用户测试——在有备选方法后有意义、Card sorting、Task analysis。
———————————
Chapter 5 Modeling Users: Personas and Goals
角色模型(Personas)是作为研究结果的行为模式、心智模型以及用户目标的形式化。角色模型可以帮助设计师对用户分类,确定不同用户的需求,进而确定目标用户的类型——包含所有可能特性的产品,不能取悦任何人(a product with every possible feature pleases nobody)。
角色模型可以帮助设计师避免产品设计中一些常见的问题:
角色模型的一些基本要点:
与传统软工方法中的用户角色(Role)相比,角色模型(personas)更关注用户的目标,更倾向用描述性的话语来表达自身。Personas可能是多个具有同样行为和目标的Role;也可能是对具有不同目标的同一Role的细分。
按:我在原书上标注了这段话,但现在看意义有限。因为书中并没有定义Role获取方法,所以没法精确说明Personas和Role的区别。
当不能通过研究获得严格的角色模型时,也可以使用靠猜想获得的临时角色模型(Provisional Personas)。但使用临时角色模型具有以下风险:
目标(Goals)对于角色模型至关重要。目标是用户行为的动机,其必须由研究数据中获得。每个目标需要能够用一句简单的句子来描述。
Norman提出了三个层次的认知加工(Cognitive Processing):
基于以上三个层次的认知,存在三种类型的用户目标(User Goals):
除了用户目标外,设计还需关注客户目标(Customer goals)、商业与组织的目标(Business and organization goals)、技术目标(Technical goals)。
“好的设计”只有在用户为了某种目标使用产品时才有意义。成功的产品首先满足用户目标。
构造角色模型的步骤:
在用户建模阶段,除角色模型外,还可以获得一些辅助的模型:
———————————
Chapter 6 The Foundations of Design: Scenarios and Requirements
在GDD方法中,从定性的研究数据到设计解决方案,通过四个不断迭代的步骤来完成:
场景(Scenario)作为设计中的概念最早由John Carroll在90年代提出。Carroll的基于场景的设计(Scenario-based Design)方法中,场景以故事的形式描述用户如何完成工作——类似于电影中的故事板,进而用于创建和演示设计方案。
AF3中的GDD方法与Carroll方法的不同之处在于:在场景获取之前,角色模型,特别是用户的目标已经明确;而场景的获取依赖于之前步骤生成的角色模型(Personas-based Scenario)。
GDD方法中定义了三类场景:
场景和用例(Use cases)的不同在于:用例更加技术化,更具体地描述用户的每个操作以及系统的响应。用例的问题在于,一来用例没有详细定义系统(在设计层面上)的行为;二来用例没有优先级,因此更适合用来验证系统功能的完备。
需求(Requirement)定义产品是什么以及产品做什么。设计定义产品如何做。需求先于设计。需求不是功能(function)或者特性(feature),而是用户或者公司业务的需要(need)。
按:大白话,这也是我为什么说AF3的Part I与其说是设计方法,不如说是需求方法。
需求定义的步骤:
———————————
Chapter 7 From Requirements to Design: The Framework and Refinement
设计框架(Design Framework)定义了用户体验的整体结构。在此阶段不必考虑细节,只需要产生产品的Big Picture。纸面原型(Paper Prototype)可以作为本阶段讨论用的有效工具。设计框架包含三部分:
定义交互框架的步骤:
按:后面两部分没有很认真的看。
定义视觉设计框架的步骤:
定义工业设计框架的步骤:
功能:通过Amazon Cloud Reader,获得已购买书籍的原文。
步骤:
原理:Kindle Reader用了localStorage database来存储Pin到本地书籍,这些数据库中的内容除了被lz压缩外,没有特殊处理,所以只要把数据取出来解压缩即可。
不足:不能获得图片(图片也是被放进database的,实现起来不难,只是我懒得改了);文本是带html标签的,直接贴到支持html标签的编辑器中可能会出问题。
按:上周偶尔谈起UML,想到自己UML、UP(统一过程)的知识有5-6年没更新了,干脆趁没忘光都吐出来。吐槽文,达意第一,术语使用不严谨。
——————————————————-
为了嘲笑UML,我们必须追溯UML的初衷,于是我们必须了解现实世界和软件世界之间天然的隔阂。虽然人们对软件已经习以为常,但是软件的构造并不像其他物理工具的制造那样透明。对软件一无所知的人来说,“编程”一词或多或少包含了“魔法”的成分。
如果对软件的内在机制有所了解,就可以更容易地表达软件与现实之间的鸿沟——即,描述现实世界的自然语言和描述软件世界的机器语言之间的失配(mismatch)。现实世界的需求不能轻易地被机器语言表达,这是鸿沟所在,或者更专业地说,是软件构造复杂度的来源。
一直以来,计算机行业用两种方法降低这种复杂度。第一种方法是对机器语言进行封装——通过引入高级语言、面向对象等概念,使编程语言可以更直接地描述现实世界。
(题外话,这是为什么从数(美)学上看编程语言越来越dirty。因为虽然Lisp很美——美到某本书某页最下方巴掌大的Lisp代码包含了计算的本质,但这种美却不能帮助Lisp更好地描述dirty的现实世界。)
但因为任何编程语言本质上都和图灵机等价,所以编程语言与现实世界的接近程度是有极限的。现存的编程语言依然难以直接描述复杂的现实。
于是计算机行业用第二个办法来弥合现实与软件的隔阂,这就是建模(modelling)——通过建模将现实世界抽象为模型,使现实世界更加清晰精确;编程语言不再直接描述现实世界,而是描述现实世界的模型。
“建模”包含两部分内容:首先是行为(“建”),即如何构造模型的方法;其次是结果(“模”),即作为方法制品的模型。
UML模型是一种用于构造软件的模型,是现实世界和软件世界的有效中介物。UML模型被用来描述现实世界的结构,并保证描述结果和软件结构天然近似。
但UML不是一种建模方法,UML没有定义将现实世界转化为UML模型的建模方法。实际上,使用UML模型的建模方法被称为“Unified Software Development Process(统一软件开发过程,缩写为UP)”。
UP的官方定义语是“用例驱动,以架构为中心,迭代增量的开发过程”。UP以模型为中介,降低软件开发的复杂度,帮助人们更容易地将现实世界的需求转化为软件。UML模型仅仅是UP的制品,而且仅仅是制品中的重要部分。一个未必恰当的比喻:UP是催动UML招式的内功心法。
遗憾的是现实中UP被丢弃、被遗忘、被不为人知。于是产生了各式各样对UML的误用:
– 比如,以为UML用例图就是用例;以为画小人圈圈就描述了需求。实际上,UP中首先要用文档附以领域模型和业务模型描述需求。每个用例都是从需求中抽取,并且是包含参与者、前置条件、后置保证、主要事件流、扩展事件流的完整文字描述。用例图仅仅用来追溯索引用例的图示。
– 比如,以为UML只包含类图;以为类图是银弹;以为使用UML就是画一张事无巨细的类图。其实UP中包含构造系统类图的完整流程方法,这包括:首先抽象现实世界的概念关系构造领域模型类图。而后以用例为中介精化领域类图,从用例中抽取包含新辨识概念关系的分析类图。而分析类图又会在后面的过程中被精化为设计类图,直到被用来指导具体实现。
– 比如,因为各种误用,以为软件开发的复杂性是UML造成的,以为摆脱UML就摆脱了软件开发的复杂性。实际上,软件开发天然复杂。摆脱UML不意味着人们可以用编程语言直接描述(具备一定复杂度的)最终系统。相反人们还是会以“土法炼钢”的方式构造自己的中介物。
此处我也无意为UP布道。不过我想说当我们像上面那样使用UML并造成混乱的时候,我们不应该嘲笑UML,UML应该嘲笑我们,因为我们才是混乱的根源。
当我们嘲笑UML的时候,我们到底应该嘲笑什么?
UML或曰UP的最大问题在于:它们认为模型是解决一切问题的银弹。
实际上以建模来弥平现实与软件的鸿沟,需要经历两个步骤:首先是对现实进行抽象,将现实转化为一种更精确更易理解的模型;第二步则是将模型“具象”到软件世界,即用编程语言实现获得的模型。其中的第一个步骤中,模型作为最终制品很重要;但第二个步骤必须落实到编程语言对软件实现上。
UP没有认清这一点,所以UP没有定义出模型使用的边界,没有给出在抽象过程结束后抛弃模型的时机,也因此没有将注意力集中到编程语言对软件的实现上。相反UP要求对模型不断精化,要求模型不断接近最终的软件。
这导致UML或者更广义建模研究的终极理想成为:通过精化的模型直接生成运行软件,或者更直白地说企图用模型替代编程语言。
然而无论直观感觉,还是多年的研究结果都表明:在企图精确描述软件世界时,模型并不比编程语言更优越——即,在深入到一定细节后,表达等量的内容,模型和编程语言同样复杂(如果前者不是更复杂的话)。
所以以模型替代编程语言的所有努力看上去是注定会失败的。UML是所有这些徒劳无功的形象代言人。
因此,我想,只有当我们看到那些事无巨细的类图、极端精确的时序图,看到那些双向转换模型的纷繁规则,看到那些神似编程语言的“建模语言”时,我们才可以去嘲笑UML。
因为这些多少代表了UML/UP以为自负可以达到,但其实不可能达到的境地。
写论文或ppt的时候,偶尔需要贴一点代码。
早年用Ultraedit的时候就直接截屏。后来转到Vim之后,问题就来了。Vim的字体和文档完全不能搭,只好单独找地方着色,于是陆陆续续用过以下方案:
前两天写ppt时实在受不了了,今天痛定思痛,基于SyntaxHighlighter写了一个Online的。
有需要的就拿去用吧,http://qizhao.sakinijino.com/tools/sh.html
p.s. 目前在非webkit的浏览器下好像会重复下载brush的js,导致着色的性能问题。暂时没搞清楚原因,先凑合着吧。
总被人批评,作为合格的Garbage Paper Generator,怎么能没有自己的主页呢!
所以,现在,我有了!http://qizhao.sakinijino.com
又及:那个,所有在页面顶上看到下面这张图的,
———-Ready? Go!————–
尼玛用的是chrome皮肤的ie6, ie7有没有!!!360浏览器有没有!!!搜狗有没有!!!!腾讯套套有没有!!!有没有!!有没有!!!!!!
尼玛知不知道就因为你用ie 6搞死多少淫!!!
ie 6的bug能出本书了,你懂不懂!!!浮动双边距懂不懂!!!居中不能懂不懂!!!盒模型懂不懂!!!懂不懂!!!!
不知道神马情况,一滚屏字都没了,google都google不出神马毛病有没有!!!有没有!!!!!
我就想用个HTML5的hash change event好不好!!!!不过是让单页面支持后退按钮好不好!!!我塔玛都是为了你好,好不好!!!好不好!!!!
尼玛爱用爱伊六!!!!我得拿iframe模拟有木有!!iframe真塔玛无所不能有木有!!!一大堆巨丑的判断分支有没有!!!基于巧合的编程有没有!!!有没有啊!!!!
草泥马。
尼玛好好看看上面那句话!!!!尼玛的浏览器过时了懂不懂!!!!!你懂不懂!!!!
安全!!!!!懂不懂!!!! 快速!!!!!懂不懂!!!!最塔玛重要的!!!免费!!!!你懂不懂!!!!懂不懂!!!!!不是只有三六零才免费!!!!懂不懂!!!!
价格便宜量又足!!!!!好不好!!!!!好不好!!!!!!
尼玛用着珂萝米皮肤的爱伊六的亲们!!亲们伤得起!!!我真伤不起!!!!!!
尼玛又及:尼玛谁咆一个的升级浏览器通知图片出来!!!有没有!!!!
今儿迁主机的时候,顺便把旧文档分类整理了一下。发现自己在04年还写过这么一篇…… 居然还投给了《程序员》…… 那时候真是太有闲情逸致了,我操……
读了读,除了“检视”这种港台腔外。发现我那会儿就有把简单事写复杂的天赋…… 恩,以下愿者上钩……
——时光倒流的分割线———
动态语言中的设计模式
Grady Booch在为《Design Patterns》一书所写的序言中谈到,“软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径”。 从设计模式出现的那一天起,这众多的模式就成为面向对象软件开发过程中一笔宝贵的财富,而对设计模式的总结工作也从来没有停止过。先是GoF的《Design Patterns》中23种经典模式的编目和命名,而后广大开发人员在此基础上不断扩充。这种种工作都为人们积累了重要的设计经验。
然而尽管设计模式本身应该与具体语言实现无关,但设计模式是从实践中总结而来的,因此在成文过程中它又不可避免的与特定的语言相关联。目前由于在工业中C++、Java等静态语言占据主导地位,所以人们总结出来的设计模式通常也是基于这些语言的。
如今计算机硬件仍然依照摩尔定律所提出的趋势飞速发展,软件的复杂度也越来越高,相对而言软件开发效率却一直没有质的飞跃。因此执行效率相对低下但开发效率较高的动态语言愈发受到人们的关注。但是想在动态语言中使用目前总结出的大量设计模式却存在一定的困难,因为静态语言与动态语言的语义差异很大,如果只是照搬目前这些设计模式并不能充分发挥动态语言的语言特性,有时候把某些静态语言的模式运用到动态语言上甚至是作茧自缚、画蛇添足了。
Python作为一种完全面向对象的动态语言,因为其简洁、易用、灵活的特点已经越来越受到开发人员的青睐。以下行文中就将用Python实践几种简单的模式,以此来逐步展示Python作为动态语言所具有的语言特性对于设计模式实现的影响。
1. 运用动态类型特性实现灵活性更高的Abstract Factory
不妨让我们从《Design Patterns》中所提到的第一种模式Abstract Factory开始。
实际上同样是面向对象的语言,把C++的代码翻译成Python的代码并不难。下面Python代码就是由《Design Patterns》中提供的用于创建迷宫相关产品的抽象工厂类MazeFactory翻译得到:
class MazeFactory:
def makemaze(self):
return Maze()
def makewall(self):
return Wall()
def makeroom(self, n):
return Room(n)
def makedoor(self, r1, r2):
return Door(r1, r2)
这个类就可以用于创建Maze的基本产品了,同时我们可以继承这个类已得到其他系列产品的工厂,比如魔法迷宫EnchantedMaze的抽象工厂:
class EnchantedMazeFactory(MazeFactory):
def makeroom(self, n):
return EnchantedRoom (n)
def makedoor(self, r1, r2):
return EnchantedDoor (r1, r2)
这样我们就不再需要将有关Maze风格的代码硬编码到具体Maze的创建过程中,而当创建不同风格的Maze时只需要替换不同的Factory就行了。
不过这么简单的翻译C++代码很无趣,而且也不能充分体现Python作为动态语言为编程人员带来的便利。下面让我们来看一看Python语言中的动态特性能为我们做些什么。
略加察看后我们就会发现这样实现的MazeFactory具有一个不足之处:很难向MazeFactory里添加新种类的产品。当需要支持新类型的产品时(比如一个陷阱产品Trap以及EnchantedTrap),我们需要修改MazeFactory以及它的所有子类,这是一件很枯燥的事情。而且我们还不得不改变MazeFactory的接口,这更是我们不愿意看到的。
对于这个问题GoF的书中向我们介绍了一个比一般Abstract Factory更灵活的解决办法,即只实现一个统一的make函数,通过传递给make不同的参数来确定要创建的产品的类型。
然而对于C++这样的静态语言,实现这种解决方法很困难。因为为了确定make返回值的类型,我们不得不将所有的产品继承自一个公共基类。但是Abstract Factory考虑的只是产品的风格系列,而同一系列不同类型的产品间逻辑上可能不存在明确的公共基类——比如Maze和Wall。而且即使使用公共基类也会有导致大量的向下强制类型转换出现。这些都是我们编程时希望避免的。
此时动态语言中动态类型特性的优势充分体现出来。动态类型允许一个变量在运行时刻绑定到不同类型的对象上,也就是说所有变量——包括函数的参数和返回值——都可以是任意类型的。因此我们不必要求Maze、Room等不同类型产品具有公共基类,也能很容易就能实现这种拥有统一make函数的工厂:
class Maze:…
class Wall:…
class Room:…
…
class MazeFactory(object):(注1)
def make(self, typename, *args):
if typename == 'maze': return Maze()
elif typename == 'wall': return Wall()
elif typename == 'room': return Room(args[0])
else typename == 'door': return Door(args[0], args[1])
class EnchantedMazeFactory(MazeFactory):
def make(self, typename, *args):
if typename == 'room': return EnchantedRoom (args[0])
elif typename == 'door': return EnchantedDoor (args[0], args[1])
else: return super(EnchantedMazeFactory, self).make(typename, args)
而创建Maze的代码可能是:
mf = EnchantedMazeFactory ()
mz = mf.make('maze')
r1 = mf.make('room', 1)
mz.addroom(r1)
make函数第三个参数*args表示把从第三个开始以后所有的参数接受为一个list,产品的构造函数从这个list中取出相应的参数用于构造对象。super是内置函数,用于返回一个变量的基类对象。
这样利用Python动态类型的特性(注2),我们用很小的代价实现了一个具有更多灵活性的MazeFactory了。现在当我们要支持新的产品类型时,我们只需要添加标示新产品的参数,而保持了MazeFactory接口的稳定。
不过估计你看到这一堆if、else会感到很不爽(其实就是个switch),我也是。更好的做法是编制一个产品的字典,利用这个字典来索引要产生类型,后面的部分我们将具体介绍这个方法。
动态类型是动态语言的一个基本特性,它能简化类层次、去除不必要的强制类型转换,提高语言表达能力以及自由性。实际上有很多模式都因为静态类型检查的限制造成了不必要的类层次和强制类型转换。比如一个用于遍历包含许多不同类型元素容器的Iterator,就可能要求所有的元素都继承自同一基类以便实现getCurrentItem,而实际上这些不同类型的元素逻辑上并不需要一个公共基类。利用动态类型特性可以使这种模式的实现更加简单灵活。
2. 动态语言不需要一些静态语言中的设计模式
前面提到我们需要编制一个产品的字典,利用这个字典索引要产生类型,这样我们就可以通过类似index['room']
的代码来创建产品了。同时我还希望赋予这个MazeFactory动态配置产品类型的能力。用什么模式?Prototype?没错,这的确是运用Prototype的地方,让我们来实现它:
import copy
class MazeFactory:
def __init__(self):
self.index = {'maze': Maze(),
'wall': Wall(),
'room': Room(),
'door': Door()}
def make(self, typename,):
return copy.deepcopy(self.index[typename])
def registtype(self, typename, instance):
self.index[typename] = instance
def unregisttype(self, typename):
del self.index[typename]
copy是Python的一个内部模块,其中包含deepcopy函数用于深拷贝对象。registtype、unregisttype函数则用来动态增删产品类型。
似模似样,还不错。不过Python中有没有更好的做法呢?
首先检视一下Prototype的初衷。因为C++并不提供类一级的对象——也就是说类本身不是对象,这就给生成产品以及动态控制产品类型造成了一定的困难,Proto Type模式的出现就是为了解决这个问题。然而像Smalltalk一样,对于Python来说类本身(也包括函数,模块)也是一个对象——type类的对象,可以对对象进行的每样操作都可以使用到类上。利用这点便利,我们不使用Prototype模式就可以实现这个MazeFactory:
class MazeFactory:
def __init__(self):
self.index = {'maze': Maze,
'wall': Wall,
'room': Room,
'door': Door}
def make(self, typename, *args):
return apply(self.index[typename], args)
def registtype(self, typename, type):
self.index[typename] = type
def unregisttype(self, typename):
del self.index[typename]
apply是一个内置函数,它接受两个参数一个函数、一个参数list,它的作用是把args作为参数传给类构造函数运行。
现在当我们要添加新的产品类型时,在字典中添加新的索引值对即可。比如我要在MazeFactory中添加一个新产品Trap,只需要在index里加入:'trap': Trap
。而动态添加类型时也只需要传入相应的类即可:mf.registtype('trap', Trap)
可以看到我们已经得到了一个静态、动态都比较容易管理产品类型很灵活的工厂。
设计模式中一部分模式的作用就是让程序更灵活,拥有更多的动态特性。而动态语言可能在语言一级就已经支持了这些特性,所以这部分模式也就不需要了。Prototype是一个例子,类似的还有Template Method。实际上因为上面提到的动态类型特性,任何函数都可以接受任意类型的变量做参数,所以Template Method模式也就没有任何价值了。
3. 运行时刻修改类结构来实现Singleton
Singleton是一个非常有用的模式(注3),比如前面的MazeFactory就很有可能需要做成一个单件。让我们来尝试实现这个模式。首先还是按照C++的方法来实现一下:
class Singleton(object):
instance = None
def __new__(cls):
if cls.instance is None:
cls.instance = object.__new__(cls)
return cls.instance
类的__new__方法是一个特定的函数,它在对象创建前被执行返回一个cls类的对象。instance则负责保存单件对象。
依循C++的思路,这个程序完全正确,但是看起来缺少一点感觉。让我们查看一下Singleton对象创建的过程,第一次构造对象时创建一个新的对象,以后构造时则不再创建而只是返回第一次创建的结果。
可以看到这是一个典型的运行时刻修改类结构的例子。Python来做自是手到擒来:
class Singleton(object):
def __new__(cls):
cls.instance = object.__new__(cls)
cls.__new__ = cls._new
return cls.instance
def _new(type, cls):
return cls.instance
_new = classmethod(_new)(注4)
classmethod函数的作用是把一个成员方法变成一个类方法。
整个类的代码很简单,第一次进行构造时创建对象,同时用_new函数替换__new__函数,而以后__new__(实际上就是_new)只是返回第一次创建的对象。
能够在运行时刻修改程序结构是动态语言区别于静态语言的一个重要特点,Python中它主要表现在可以动态添加、删除、修改对象(包括类和函数)的方法和变量。这个特性可以用来帮助实现Singleton、Decorator、Strategy等一些要求运行时刻动态改变对象职能的模式。
4. 用Functional Programming简化Command模式
最后要谈到的一点就是Functional Programming。在设计模式里谈到FP可能不很切合。如果我们注意一下会发现GoF《Design Patterns》的副标题是“Elements of Reusable Object-Oriented Software”(可复用面向对象软件的基础)。实际上我们日常谈到的设计模式主要是针对OO Programming来说——因为工业中主要应用的是面向对象编程语言。而FP是区别于OOP的另一种编程风格。
但是Python作为一种面向对象的动态语言,它对FP和OOP的编程都提供了良好的支持。在OO的设计模式中运用一些Functional Programming的思想也会收获一定的好处。下面以Command模式为例讲述这一点。
因为有时候请求操作的对象不知道被请求操作对象的任何信息(打开文档的按钮对象不会知道任何文档对象的信息)。Command模式用以解决这个问题,根本目的在于把被调用操作的对象与实现该操作的对象解耦。而Command模式的实现方式是“把函数层面的任务提升到了类的层面”(注5)。因为FP中函数本身就是第一类对象,所以我们不必再像C++中那样运用继承组合来迂回地实现这一点。FP实现Command模式是非常简单的。
class Button:
def click(self):pass
这样我们就已经有了一个Button类。看起来什么都没有?别急,先让我们实现一个用于打开文档的Button看看:
opendoc = Button()
opendoc.click = doc.open
现在当opendoc这个按钮被点击时,仅仅需要执行opendoc.click,就等价调用了doc.open这个函数了。这段代码看似简单,但观察下会发现Button和Document已经被很好地解耦,只是FP的实现中间没有那么多迂回,而思路、效果与Command模式是一样的。
再来看看MacroCommand。MacroCommand的目的在于一次执行一系列的Command——也就是函数。有了FP的办法,我们也不必再用Composite模式搞得很复杂,而是利用匿名函数lambda关键字和内置FP函数map实现为:
marcocommand = lambda commands: map(lambda command: command(), commands)
map依次把commands中每个的command传给第一个参数lambda command: command()
,而这个参数本身也是一个函数,用来执行传入的command。现在如果我们要实现一个Find & Replace的Button,我们只要这么写:
findreplace = Button()
findreplace.click = lambda: marcocommand([doc.find, doc.replace])
当然Function Programming本身是很复杂的,仅仅在设计模式中一些简单的运用不能充分体现FP的思维方法和能力。但FP作为区别于OOP的编程风格,将其适当用在设计模式中,的确可以优雅简单地实现一些原本复杂的模式。而不同编程风格的尝试和融合也有利于拓宽我们的思路。
结语
通过Python的模式实践,我们可以看到动态语言的种种特性对各种设计模式的实现方式的影响,它们在简化了部分模式实现的同时更带来了较大的灵活性。当然天下没有免费的午餐,动态语言在语言一级提供更大灵活性是以执行效率的下降为代价的,个中的利弊还要广大开发人员去权衡。
本文只是浮光掠影地进行了一些简单的讲述,其中不免有许多疏漏。要想更好地掌握动态语言中设计模式的应用,必须要对各种设计模式以及动态语言的特性有着清晰的了解,并在实践中不断尝试才能做到。
注1: Python中继承自object的class表示一个type,否则是一个classobj。一些特定的操作必须是type类型才能执行,比如super函数要求传入一个type类型的参数、__new__函数也只有type类型才支持。如果需要用到这样操作,则必须让你的class继承自object。
注2: 值得注意的是Python实际上是一种强类型动态类型的语言,而不是弱类型的语言。关于强弱类型的具体解释可以参考这篇文章《Typing: Strong vs. Weak, Static vs. Dynamic》By Aahz。
注3: 实际上由于Singleton模式不能很好地适用于多线程环境,多线程环境中使用更多的可能是Singleton的变种Double-checked locking模式。不过理解了Python中Singleton实现,DCL也是大同小异,所以这里仍然用基本的Singleton模式来解说。
注4: Python2.4中提供新的语法Decorators for Functions and Methods来使类似操作更容易的实现。具体的语法和相关说明见PEP 318。
注5: 语自《Agile Software Development》By Robert Martin
2004-11-25左右成稿
话说我07年底08年初的时候,一心觉得这世界上所有的活动都该被SNS装起来,加之那会儿博士读到天天踢球。所以动心思写了个网站,目的是帮着大家组球队、约训练、约比赛、一起看球等等。
当时的野心其实比上面说的大些,想至少把校内足球这部分吃下去。可惜几个工科宅男又能干啥?上线没几个月,随着博士各种事情如山倒来,很快也就放下了。
最近整理代码时又给翻出来。现在看08年初写的东西(iphone开卖不久,fb plateform还不为人知,gae还得申请),自己也嫌界面土气、文案简直是一坨、功能设计复杂到自己都忘了怎么用…… 不过决定还是给架起来,至少等明年春暖花开,想踢球的时候还能用不是。不被运行的代码也是会哭泣的!
最后感谢雷指赞助的域名,www.wefootball.org
又及:这网站本是开源计划的一部分。只是当时后端也有马博三分之一的工作量在那,商量一下才好。但马博最近又忙得见不到人,于是非得等等才有下文了。
以下截图杀猫: