Io语言

“动态语言”现在是一个很时髦的术语,常用来表示那些编程语言十分地具有弹性而且很合适“敏捷”开发环境。在这种趋势背后有一些很有趣和很强大的语言,Io就是其中一个十分重要的语言。

Io最早发布在2002年,它混合了Lua、Lisp、Smalltalk和其他一些语言的某些方面的一个小语言。而起初的大想法是要把Io作为一个完整的可视化编程语言的基础,就像Self一样。

概览

Io属于“解释型”语言家族(像Perl、Python等等),这种语言的代码并不是通过编译而至直接解释并运行的。和很多“解释型”语言一样,Io也是高度动态的:一个程序可以迅速地生成并解释新的代码。同时,Io通过采用了Lisp的“代码即数据”的模型让这个特性向前迈进了一步,它可以让任何Io程序访问和处理它自身在内存中的对象模型。毫无疑问,这也让Io成为一个自省的语言。

并发编程在很多应用中都是一个新兴的被关注的方面,尤其是大多数用户界面和Web应用。Io从Act1中获得了灵感并使用了“参与者”(Actor)的概念。参与者介于线程和延续之间,它可以让你更容易编写你的并发应用,而原有的基于线程或者是进程编程的负担则不复存在。

简介清晰在Io的语法和他的对象模型中是显而易见的。语法可读性很好,而且看上去很好地结合了Lisp和Smalltalk。Io的对象模型是基于原型的。一个基于原型的对象模型不使用类来表示泛化和特化,取代的是创建特定的对象来用作“模型”或是“原型”并通过他们来创建新的对象。在这种编程范型中,对象是从原型中克隆出来的而不是从一个类实例化得来的。

对象模型是基于消息的,这表示方法调用甚至是变量访问都是通过发送动态的消息来完成的,而不是“硬布线”的(像C++和Java那样)。同时,Io对象响应消息的方式可以在运行时进行改变,这也是一个很典型的“动态”特性。

Io的足迹很小可以很方便地嵌入C或者是C++应用中。这方面它最有趣的特点之一是他的Objective-C的桥接器,这在集成上达到了一个令人惊奇的程度,在Io中使用你的Objective-C对象几乎不要写任何粘合代码。

Io分析

长处

Io的主要的优点来自他简洁的设计:它是一个可以很快学会的语言因为他有一个简单而且一致的语法、语义和API。由于Io的足迹小,所以它十分合适嵌入式编程。Io的另一个长处是它的速度。它能在性能上胜过很多其他解释型语言,这让他成为密集工作的一个理想选择。

Io的另一个长处——从一个嵌入的角度来看是很有意思的——是你可以重新定义几乎任何运行机制。Io语法中的任何东西都已经转化成了消息,而消息是可以在运行时重新定义的。从这方面来看,实际上你就可以改变里面任何东西来满足你的需求。

同时,Io对于并发编程的解决方案对于Web应用和GUI脚本编程也极具吸引力。

弱点

尽管Io有很多有趣和先进的特性,然而它还是十分年轻。到目前为止它还没有什么值得称赞的开发工具诸如文档生成器或者是代码检查器等等,而且在解释其上面仍然有一些问题。尽管如此,随着Io社群的不断壮大,更多的代码被制作共享,这个“青少年问题”会最终消失。

范例程序

参考

从Trails和Firebird开始

作者:Chris
翻译:ShiningRay @ Nirvana Studio

0. 前言

在Trails的首页上有一段Trails的指导视频,它比本文说得更为详细。你可以看看它,另外根据Firebird设置一节中的内容,设置一下和Firebird相关的东西。

1. 什么是Trails?

Trails是一个领域驱动开发框架,它使用了Hibernate、Spring和Tapestry。其中,Hibernate 被用作数据访问层,Tapestry用来给用户显示数据。而Spring 则是把Hibernate和Tapestry连接在一起。

Trails自带了几乎所以必需的jar文件——你只需要安装一下Firebird的数据库驱动,它可以在 http://firebirdsql.sf.net上下载。

1.1 Hibernate

Hibernate是一个O/RM工具。O/RM的意思是:对象关系映射。O/RM可以让你把 java 对象映射到后台数据库中。Hibernate使用了XDoclet来指明映射信息所需的信息,以便在数据库中存储复杂的对象。而XDoclet使用了javadoc注释来告诉Hibernate如何映射对象。例如:

[code lang=”java”]/**
* @hibernate.class table=”PERSON”
*/

public class Person {
}
[/code]

这段代码会告诉Hibernate要将一个指定的对象(在这里是Person)映射到PERSON表。

Hibernate 的主页是:www.hibernate.org.

1.2 Tapestry

Tapestry 是(简而言之)一个Web框架。更确切地说,Taspetry是一个基于组件的Web框架,它将表示和逻辑清晰地分离开来了。

在Tapestry中,一个页面和一个.html文件相关,这个文件负责这个页面的外观,和一个Java类,它负责对.html文件提供数据,还有一个.
page或者是一个.jwc文件(这由你是要作为一个真正的页面还是一个单独的组件),这两个负责把前两个层次连接起来。

组件是通过ognl语言来访问的,形式如:

jwcid 指明了使用的组件——这里是一个PageLink,它是一个HTML表现中的<a></a>标签的一个组件。page属性指明了你要链接的页面的名称。

Tapestry 的主页在jakarta.apache.org/tapestry

2. 安装Trails。

首先从trails.dev.java.net下载Trails.

当前的版本是0.5.1。先把下载的文件解压缩(在https://trails.dev.java.net上有一个很棒的演示,教你如何利用Trails来写应用程序)。

解压缩trails,并进入新建的文件夹。更改build.properties文件以匹配你的tomcat路径,
然后做以下这些事情:

通过运行下面的指令来创建trail.jar(我在我的机器上必须这样做)

3. 写一个新的应用程序

3.1 创建一个新的trail应用:

这是在问你,把工程的根目录放在哪里(在MacOS X上我输入的是/Users/chris/Desktop/projects——Windows用户可能要输入诸如c:\path\to\new\project的路径)

输入工程名称

然后,一个目录包括所有必须的jar文件就被创建好了。

3.2 启动

3.3 创建一个新的 “Java Project”

  • 输入工程名称
  • 选择 “Create project at external location”(在外部位置创建工程) 并指向新创建的文件夹。

  • 点击 “next”
  • 点击 “add folders”
  • 选择 “src”,然后点击OK

  • 点击 “finish”

新创建的工程就会被导入到Eclipse中。

4. 设置

4.1 改变tomcat的主目录

  • 打开Eclipse中的open build.properties 文件并将tomcat.home改称你的tomcat的目录。

4.2 更改数据库驱动:

  • 打开文件 context/WEB-INF/hibernate.properties 并把其中的设置改为::

4.3 在主机上创建一个新数据库(这里是127.0.0.1)

并且在 FIREBIRD_HOME/aliases.conf 中添加一个别名,这样写:

在生产系统中,你还要为以后要连接的用户更改用户名和密码 :-)

4.4 安装Firebird数据库驱动

firebird.sf.net下载Firebird的JCA-JDBC驱动,解压缩并把jar文件安装到CATALINE_HOME/common/lib下(详细内容参见 JayBird FAQ)。

5. 开始编码

5.1 创建一个领域对象(Domain Object)

领域对象可以简单地说成是POJO(Plain Old Java Objects),它是Hibernate用来在数据库中存放数据的。

  • 在Eclipse中选择src文件夹并点击 File -> New -> Class(文件->新建->类)
  • 输入一个包名(de.test.data)
  • 在Name输入框中输入类名:Person
  • 点击 “finish”

成功新建并打开了一个类。

5.2 告诉Hibernate要使用的表格:

把表格名称作为XDoclet的hibernate.classabove标签添加到类的说明中。XDoclet任务以 @ 符号开始,后面跟着一个名空间(在这里是hibernate)和一个动作,指明要做什么(更多信息参见hibernate.sf.net)。

XDoclet标签 @hibernate.class table=”PERSON” 将告诉Hibernate把这个对象中的数据存储在一个叫做PERSON的表中。

表格的名称无需和类名相匹配——你也可以指定一个MY_PERSON作为名称 :-)。

5.3 创建属性:

  • 先输入以下内容:

  • 在“Outline”(大纲)中选择PersonID并右键点击它。
  • 选择Source -> Generate Getters and Setters
  • 两个都选中并点击OK

现在已经有了getter和setter例程,而且已经添加了注释。然后我们添加下面的注释:

这将告诉Hibernate使用表格的PERSON_ID列来存储表格的主键,同时通过使用一个生成器来自动新建这个字段的值。
键将由SQ_PERSON_ID生成器来生成。

在大纲中选择name并且右键点击它(选择 source
-> generate getters and setters,选择Name和SurName然后点OK :-) )

这次,我们不再需要主键——我们仅仅需要一个字段来存放姓名。所以我们继续并添加以下javadoc注释:

[code lang=”java”] /**
* @hibernate.property column=”LAST_NAME” not-null=”true”
*/
public String getName() {
return Name;
}
[/code]

以及surname属性

由名称属性而来的getter上的列是必须出现的,因为我们要覆盖默认的行为。Hibernate会假设一个叫“NAME”的字段——但这在Firebird中是一个关键字,所以我们必须另外选择一个名称。

记住:Hibernate标记必须总是设置在getter上——而不是setter。

如果需要,我们可以通过在Hibernate的XDoclet标记中添加not-null=”true”属性来设置一个null值——默认值为false。

5.4 创建查询表

现在提示有一个错误——是LSalutation。

这时把你的鼠标移到LSaltutation上并且按下Ctrl+1(在Apple Macintosh上你要按Command + 1)。然后会出现以下对话框: 

输入要创建的文件的包名,点击“finish”,然后做以下几步动作:

  • 给他一个ID
  • 个它一个名称
  • 生成getter和setter
  • 并且指明给表格设置的类名,以及ID和属性字段

现在,又是另一个对象可以被Hibernate映射了。对于Hibernate,这已经可以用了,但对于Trails,还少个东西:

  • 一个 equals() 和一个 toString() 方法

toString() 方法是用来在列表视图中能正确显示属性,而equals方法是用来标示对象的。所以只要添加以下代码到LSalutation类中:

返回到Person.java中并且为Saltuation属性创建getter和setter。

由于我们要给salutations使用另一个表中的数据,所以这里我们要用另一个属性。所以这里我们要用一个多对一的关系。也就是,我们要把javadoc注释改成:

现在Hibernate已经了解如何引用salutation类了。

这就是我们要写的全部代码了。 :-)

6. 构建应用程序:

打开build.xml文件,右件点击war目标并选择 ant -> build

这将调用ant来构建.war文件(Trails的指南说,使用deploy目标可以部署程序——这不能在我的机器上运行)。然后我把project.war文件复制到TOMCAT_HOME/webapps中去。

7. 访问应用程序:

打开浏览器并输入127.0.0.1:8080/project_name

现在,享受一下浏览和输入数据的乐趣吧 :-)

无任何担保——Chris

SmaCC 指南

这是一个用于演示一些SmaCC(Smalltalk编译器的编译器)的简要指南。在这个例子中,我们会逐步开发一个简易的计算器。

如果你已经做过这种东西,你可以先 载入代码 。你载入了代码之后,你需要打开SmaCC解释器生成器。在VisualWorks 和 VisualAge中,它在Tools菜单下。Dolphin的在一个额外的工具目录中。它会打开一个类似下面的窗口:

SmaCC Window

我们第一个计算器相对比较简单。它只要能读取两个数字并把它们相加。开始之前,我们首先要告诉扫描程序如何辨认一个数字。数字由一个或多个数字打头,后面可能还有一个小数点加上0或者更多的数字。扫描程序对这个标记的定义是:

把这行代码输入界面上的scanner标签页中。让我们逐个看每一个部分:

<number>

指出记号的名字。在<>中的名称必须是合法的Smalltalk变量名。

:

分隔记号名称和记号定义。

[0-9]

匹配任何一个在’0’到’9’(一个数字)范围中的字符。

+

匹配前面的表达式一次或多次。在这种情况下,我们要匹配一个或多个数字。

( … )

标示子表达式组。

\.

匹配 ‘.’ 字符(. 在正则表达式中有特殊的含义,使用 \ 来转义)。

*

匹配前一个表达式零次或多次。

?

匹配前面的表达式零次或一次。(也就是,前面表达式是可选的)。

;

终止一个记号说明。

我们不想去关心我们语言中的空白符,所以我们需要定义什么是空白符并且忽略它,输入下面的记号说明:

\s 会匹配任何空白字符(空格、制表符、换行、回车等等)。然后我们怎么告诉扫描程序去忽略它呢?
如果你看一下SmaCCScanner类,你会发现一个叫做’whitespace’的方法。如果一个扫描程序有一个方法的名称和某个标记一样,那么一旦扫描程序匹配了这类标记就会调用这个方法。正如你所见,whitespace方法会吃掉空白符。同样还有一个’comment’方法会作类似的处理。

说到我们的语法,现在让我们来定义它吧。在Parser表格中输入以下语法说明:

这基本上指出了一个表达式可以是一个数字或者是另一个表达式加上一个数字。

我们现在应该可以编译一个分析器了。切换到Compile标签页。你要输入扫描器和分析器的类的名称。这里我们相应使用CalculatorScanner 和CalculatorParser。当类名输完之后,我们就准备编译分析器了。点击 ‘Compile LARLR(1)’
按钮(你应该总是点这个按钮除非你知道你要做什么。一般来说,他会生成比另一个选项更小的分析器)。这时就会生成新的CalculatorScanner和CalculatorParser的Smalltalk类同时会编译这两个类中的一些方法。所有的SmaCC编译出来的方法会按照”generated-*”的格式。你不可以更改这些方法因为每次你重新编译他们都会被覆盖。

不管SmaCC何时创建新类,这些类都会被放到默认的应用程序/包中。如果你使用的是VisualAge,你要确保默认应用程序是开放的版本而且SmaCCRuntion应用程序已经安装(prereq)。

如果你已经生成了扫描器和分析器类,你可以通过类名旁边的”…”按钮来载入他们的定义。如果在出现的对话框中你回答”Yes”,那么在Scanner/Parser标签页中的文本就会被替换为上一次编译过的定义(假设”Generate definition comments”在上一次编译中被选中了)。

现在我们要测试我们的分析器了。进入“test”面板,输入“ 3 + 4”(不要加双引号),并且点击“parse”按钮;你会看到分析器正确分析了它。如果你点击“Parse and Inspect”你会看到一个检视器(inspector),里面有一个包含了被解析的记号的顺序集合(OrderedCollection)。这是因为我们没有指明当分析器在解析的时候要怎么处理记号。你也可以是如一个不正确的内容。例如尝试解析“3 + + 4”或者“3 + a”。应该会出现一个错误信息。

现在我们要定义当我们分析我们的表达式的时候要产生的动作。当前的情况是,我们的分析器仅仅验证表达式是一些相加的数字。一般来说你要创建一些结果来表示你已经解析了什么内容(比如,一个棵分析树)。然而,在这个情况下,我们不关心结构,我们只关心结果(表达式的值)。在我们的例子中,你需要把语法定义改成如下:

括号中的文本是Smalltalk代码,当应用规则的时候,就会执行这个代码。有一个数字的字符串会被替换成相应的解析节点。在第一个Expression的规则中,’1’会被替换成匹配Expression的ParseNode同时’3’会被替换成匹配Number的ParseNode。在规则中的第二个东西是’+’记号。因为我们已经知道它是什么了,所以我们对他不感兴趣。编译新的分析器。现在当你从Test面板中执行’Parse and Inspect’时,你应该看到这个结果:7。

前面的代码有一个问题是如果你需要更改一个规则,你可能也要跟着修改规则内的代码。例如,假设你你在规则的开头添加了一个新的记号,那么你就要更改所有在Smalltalk代码中的引用了。我们可以通过使用命名表达式来减少这种问题。在规则的每个部分后面,我们可以指明它的名称。名称是通过单引号来标明的,它同样必须是一个合法的Smalltalk变量名。象下面这个:

它和前面解析的语言的结果是一样的,但它同时让你更容易维护的你分析器。让我们现在扩展我们的语言并加入减法功能。这里是新的语法:

你编译了这个代码之后,’3 + 4 – 2’就会返回’5’了。下面,再让我们加入乘法和减法:

这时我们遇到一个问题.如果你计算” 2 + 3 * 4“,最后的结果将是 20。这个问题是因为在标准的数学中,乘法比加法有更高的优先级。我们的语法是严格按照从左到右的方式运算的。这个问题一般的解决方法是定义加法的非终结符来强制计算的顺序。这个解决方法的语法类似:

这时候如果你编译这个语法,你会看到” 2 + 3 * 4 “的计算结果是14,正如我们所期望的那样。现在,正如你可以想象的,当优先级规则的数量增加时,语法也越来越复杂(例如,C语言)。我们可以使用歧义语法和优先级规则来简化这种情况。这里有一段使用优先级来限制计算顺序的一段语法:

注意我们更改了语法所以操作符两边都是Expression。我们在语法顶部添加的两行表示“+”和“-”是从左至右运算的而且优先级相同,同时他们的优先级比“*”和“/”低。类似的,第二行表示“*”和“/”有同样的优先级。这个形式的语法通常更加直观,特别是当有很多优先级要处理的时候。我们再来一个例子,现在加入指数运算和括号:

当你编译了这个语法之后,你就可以正确计算” 3 + 4 * 5 ^ 2 ^ 2“得到2503了。由于这个指数操作是右结合的,所以这个表达式是象这样计算的3 + (4 * (5 ^ (2 ^ 2)))。我们也可以计算带括号的表达式。例如,计算 ” (3 + 4) * (5 – 2) ^ 3 “将得到189。

为什么继承是有害的?

通过把具体的基类转变成接口来改进你的代码

作者:Allen Holub  翻译:ShiningRay @ Nirvana Studio

摘要

大多数优秀的设计师避免出现继承(extends描述的关系),就像躲避瘟疫似的。你的代码80%应该完全以接口的方式来书写,而不是继承具体的基类。其实,Gang of Four 这本关于设计模式的书(以下简称GoF)很大程度上关于如何把类继承转变成接口实现。本文将叙述为什么设计师们会有这种古怪的信条。(2,300 words;2003 年 8月 1日)

译注:本文其实已经有人翻译,当时没有具体了解就开始翻译了,如果另一位译者看到这篇文章,希望不要理解为我抄袭的。

extends关键字是很有害的;也许不仅仅是在Charles Mason的级别上,还坏到了只要可能都应该避免的程度。GoF中详细讨论了把类继承(extends)如何转变成接口实现(implements)。

优秀的设计师的大部分代码都是根据接口写的,而不是根据具体的基类。本文将会讲述为什么设计师们会有这种古怪的癖好,同时也将介绍一些基于接口的编程基础。

接口 VS 类

我曾经参加了一个Java用户小组会议,那次刚好是James Gosling(Java的发明者)作特邀演讲人。在那次难忘的Q&A对话(提问)上,一个人问他:“如果你可以重新将Java搞一遍,你会做哪些修改?”“我会去掉类,”他回答道。在笑声渐渐消失之后,他解释了真正的问题不是类的本质,而是类继承(extends关系)。接口实现(implements关系)却是完美的。只要有可能,你们就应该避免类继承。

弹性的丧失

为什么你应该避免类继承?第一个问题是明确的使用具体类的名称会把你框在特定的实现中,让以后的更改会十分困难。

当代,敏捷开发方法学的核心是设计和开发同步。你在完全详细描述程序之前,就开始编写代码了。这种技术完全违背了传统的理念——设计应该在编程之前完成——但是很多成功的项目已经证实了,用这个方法,你可以比传统流水线作业更快速地开发高质量的代码(同时付出很有效)。然而,在并行开发的核心是,弹性的概念。你必须以这种方式来写你的代码,以便你可以尽可能以无痛的方式加入新发现的需求到现有的代码中。

你只要实现确实需要的特性,而不是实现那些可能需要的特性,但要用一种可以适应变化的方法。如果你没有这种弹性,并行开发明显是不行的。

接口编程正是这个弹性接口的核心。要了解为什么,先让我们看看如果你不使用接口会发生什么。考虑以下代码:

现在假设一个紧急的新需求,需要进行更快速的查找,已经暴露出来了,这样LinkedList就达不到要求了,你就要把它换成HashSet。在现有的代码中,因为你必须同时修改f()还有g()(它用一个LinkedList作为参数),因此更改不是局限在一处的,还有一切传列表给g()的地方。

现在把代码改成这样:

现在我们要把链表改成哈希表就只把new LinkedList()改成new HashSet()。就完成了。不需要更改其他的地方。

另外一个例子,比较一下代码:

以及:

g2()方法现在遍历Collection的派生对象以及你从Map中得到键和值。事实上,你可以写一个不断产生数据的迭代子而不是遍历一个集合。你可以写很多不同的迭代子,比如可以从测试台中或者一个文件中不断给出信息。这就是这里最重要的弹性所在。

耦合

关于类继承的一个更加关键的问题是耦合——程序中不期望的一个部分对另一个部分的依赖。全局变量提供了一个经典的例子来说明为什么强耦合会造成很多问题。例如,如果你更改了全局变量的类型,所有使用这个变量的函数(也就是,对这个变量有耦合)就会受到影响,这样所有这样的代码必须被检查、修改和重新测试。此外,所有使用这个变量的函数也会通过这个变量产生耦合。也就是,一个函数可能会不正确地更改了这个变量从而造成了其他函数的行为,如果变量的值在某些特殊的时间被更改的话。这个问题在多线程的程序中特别突出。

作为一个设计者,你应该力争做到最低的耦合度。当然你不可能完全消除耦合,因为一个类的对象调用另一个对象就是一种松散耦合的形式。你不可能写出一个一点耦合都没有的程序。尽管这样,你可以通过绝对服从OO的原则(最重要的是一个对象的实现细节应该对使用它的对象是隐藏的)来相当可观地最小化耦合。例如,一个对象的实例变量(非常量的成员字段),总是应该为私有private。这没有任何例外的情况(你可以偶尔很有效地使用protected方法,但是protected实例变量是相当讨厌的)同样的原因,你也绝不能使用 set/get 函数——他们只是另一种让字段变成公共的稍复杂方式而已。(虽然返回处理过的对象而不是一个基本类型的值的访问函数在某些情况下还是合理的,如果返回的对象的类是设计中的一个关键的抽象的话。)

这里我不是在卖弄学问。我发现了一个OO方式的严格性、快速代码开发、和简单的代码维护之间的直接的相关性。无论什么时候我违反了一个核心的OO原则比如隐藏实现细节,我只能结束代码的修改(通常是因为这个代码不可能进行调试)。我没有时间重写程序,所以我只能遵循这些规则。我关注的是完全实际的内容——我对为了设计而设计没有兴趣。

脆基类问题

现在,我们把耦合的概念应用到继承上。在一个使用实现-继承系统中,派生类对基类有十分紧密的耦合,同时这个闭合的连接是不受欢迎的。设计师们因此给这种行为起了一个绰号——“脆基类问题”。基类是被认为十分脆弱的,因为你可以通过一个表面上十分安全的方法修改一个基类,但这个新的行为,当被派生类继承的时候,可能会造成派生类运行出错。你不能简单孤立地通过检查基类的方法来判断你对基类的改变是不是安全;你也必须查看(并测试)所有的派生类。此外,你必须检查所有同时使用了基类和派生类对象的代码,因为这些代码可能会被新的行为所破坏。对关键的基类的小小的改变都会导致整个程序无法运行。

我们来一起检验这个脆基类和基类耦合这两个问题。下面的类扩展了Java的ArrayList类,来模拟栈的行为:

甚至像这样简单的一个类,都存在着问题。思考一下如果用户利用继承直接使用ArrayListclear()方法来把所有的元素都从栈中弹出去,会发生什么:

这个代码可以成功地编译,但是由于基类并不知道任何关于栈指针的信息,Stack对象现在处在一个不确定的状态。下面再调用push()会把新的条目放到索引2种(栈指针stack_pointer当前的值),这样栈看上去就有三个元素了——但底下两个已经被垃圾收集了。(Java类库中的Stack类就是这种问题,所以不要用)

对于不需要的方法继承,一种解决方式是,对于Stack要重写所有ArrayList的可能修改数组状态的方法,这样覆盖的函数可以正确处理栈指针或者抛出一个异常。(removeRange()方法是一个较好的抛出异常的候选。)

这个方法有两个缺点。第一,如果你覆盖所有的东西,基类就实际上成为了一个接口,而不是一个类。如果你不使用任何继承的方法,类继承就毫无意义。 第二,也是更为重要的一点,你并不希望一个栈能支持所有ArrayList的方法。比如,那个讨厌的removeRange()方法没什么用处。实现一个没用的方法的唯一合理的方式,就是让他抛出一个异常,这样他就不可能被调用了。这个方法却将一个编译时错误变成了运行时错误。这并不好。如果方式只是没有被声明,那么编译器会直接扔出一个“未找到方法”的错误。如果这个方法存在但是他抛出异常,你就不会发现错误直到程序运行的时候。

一个更好的解决方法是封装一个数据结构而不使用继承。这下面是一个Stack的改进过的新版本:

目前为止还不错,但是还要考虑到脆基类的问题。让我们假设你想创建一个Stack的变体,可以跟踪运行一段时间之后栈出现过的最大值。一种可能的实现如下:

新的类运行得很好,至少目前这样。但不幸的是,代码暴露了push_many()方法是通过调用push()来完成它的任务的。首先,这个细节看起来还不算一个糟糕的选择。他简化了代码,同时你可以获得派生类的push()版本,即使当Monitorable_stack是通过一个Stack类型的引用也能完成,所以,high_water_mark的更新是正确的。

某一天,有个人也许会运行一个测试工具并且发现了Stack还不够快,而且他要被频繁地使用。你可以重写一个不使用ArrayListStack,由此改进Stack的性能。下面是最新的版本:

注意push_many()不再是重复调用push(),而是采用了块传送。新版本的Stack运行很好;事实上,它要比原先的版本更 。但很不幸,派生类 Monitorable_stack 就不能再正常工作了,因为如果调用的是push_many()那么他不能正确跟踪栈的使用情况了(派生类的push()版本不再被继承了的push_many()所调用,所以他不会再更新high_water_mark)。现在Stack就是一个脆基类。正如上面显示的,事实上不可能仅仅靠小心就能消除这类问题。

值得注意的是如果你使用接口继承,就不会有这种问题,因为不会继承任何功能,就不会产生不良影响。如果Stack是一个接口,同时通过Simple_stackMonitorable_stack来实现,那么代码就会更加强壮。

在表0.1种,我提供了一个基于接口的解决方案。这个方法和类继承的方法有相等的弹性:你可以根据Stack抽象来写你的代码而不用担心你要具体处理那种类型的栈。由于这两种实现都必须提供公共接口中的所有方法,要出现问题也很难。我也有且仅有一次从写相同的基类代码中获益,因为我使用了封装而不是派生。从负面,我必须通过一个封装类中的细小的访问器方法去访问默认的实现。(例如Monitorable_Stack.push(...)(41行)必须要调用Simple_stack的中等价的方法。)程序员总是抱怨写这种一行就完了的代码,但是仅仅就写这么额外的一行的代价就可以消除潜在的巨大Bug。

表 0.1. 使用接口消除脆基类

框架(Frameworks)

关于脆基类的讨论如果不提到基于框架的编程,就不会是完整的讨论。像MFC(微软基础类库)这种框架已经成为一种建立类库的流行手段。虽然MFC正在急流勇退,但MFC的结构已经深深扎根在无数微软的车间——这里面的程序员都认为微软的方法就是最好的方法。

一个基于框架的系统,一般都是以半成品类的库作为起始,这些半成品的类不会完成所有事情,而是要依赖于派生类提供未完成的功能。Java中的一个典型的例子就是Componentpaint()方法,它其实只算一个占位符;而派生类则必须提供真正的版本。

你可以,但是一个整个类框架都依赖于基于派生的自定义是极其脆弱的。基类也很脆弱。当我用MFC编程的时候,每次微软发布一个新版本的MFC,我都必须重写自己的应用程序。代码会经常编译但接下来却不能正确工作因为一些基类的方法改变了。

所有的Java包都可以即开即用(Out of box)且运行良好。你无需扩展任何东西来让他们执行功能。 即开即用的结构比基于派生的框架要好。它更容易维护和使用,并且即使Sun提供的类改变了他的实现也不会让你的代码处于危险中。

脆基类的总结

一般来说,最好能避免继承具体基类和extends关系,而使用接口和implements关系。凭我的经验,代码中最少有80%应该是完全用接口的方式来写。比如,我从来不引用一个HashMap ;我通常会用指向Map接口的引用。(这里的“接口”是广义的。一个 InputStream也算一个有效的接口,你可以看看是如何使用它的,虽然他在Java中是描述为一个抽象类。)

你加入越多的抽象,弹性就会越好。在今天的商业环境中,通常在程序开发时需求就在不断变化,弹性是十分必要的。 此外,大多数敏捷开发方法(比如Crystal方法和极限编程)完全不能正常运作,除非代码是先用抽象写得。

如果你仔细研究Gang of Four的模式,你就会发现他们中很多都是提供了各种消除类继承的方法,而偏重于使用接口,这也是大多数模式的一个共性。我们一开始就要明白一个重要的事实:模式是被发现的,而不是被发明的。当你看了那些写得好的、易于维护可以很好运行的代码,模式自然就会浮现出来。这告诉我们这么多优秀的代码都会以各种方式避免类继承。

本文是从我即将发表的书中节选出来的,书暂时命名为《 Holub on Patterns: Learning Design Patterns by Looking at Code 》,将会在今年秋季通过Apress (www.apress.com)出版。

关于作者

Allen Holub 从1979年开始从事计算机产业的工作。他目前是一个顾问,通过对行政人员提供建议、培训以及设计、编程服务,来帮助企业不需要再软件上浪费钱。他撰写了8本书籍,包括 Taming Java Threads (Apress, 2000) 和 Compiler Design in C (Pearson Higher Education, 1990), 并且在加州大学伯克利分校教学。请在他的网站 ( http://www.holub.com ) 查询更多关于他的信息。


资源

JavaScript Templates

翻译:ShiningRay @ Nirvana Studio

(译者:JsWiki的第一个版本是用ASP+JavaScript写的,中间就用到了它)

对于Web应用的开发者,来自 TrimPathJavaScript Templates 模版引擎是一个轻量的 APL / GPL 开源 组件,他让你的程序运行在Web浏览器中的时候可以直接基于模版编程(像PHP/ASP/JSP)。

  • The JST 引擎是完全用标准JavaScript写成的。

  • 他支持一套丰富的模版标记语法,和FreeMarker, Velocity, Smarty十分相似。

  • 对于大量的硬编码的字符串连接和主流的DOM/DHTML处理来说,JST是一个更容易阅读的替代方案。

借助JavaScript Templates来创建你自己的rich web application(像GMail/OddPost/Bloglines)。

更多信息:

JST 10 分钟简介

首先,在我们的HTML页面中,我们载入TrimPath JST组件到浏览器中

接下来,建立一些结构化JavaScript数据——仅仅是一些对象和数据

下面,这是一个JST模版的例子,它可以指导如何“渲染”数据。我们把我们的JST模版放到HTML页面的隐藏的<textarea>标签中:

这里是显示如何利用 API进行模版处理的代码

返回的结果是:

除了可以从 <textarea>元素中获取我们的模版之外,我们也可以从JavaScript的字符串中获取:

你也许想立即看看这个,JST demo page.


我应该把JST的模版放在哪里?

在上面的介绍中,我们把我们JST模版放在了HTML页面的隐藏的<textarea>标签中。

  • 当然,你可以在一个页面中有多个 <textarea> 元素,来保存不同的JST模版。

    • 一般的语法是:<textarea id="my_template_jst" style="display:none;"> ... 模版内容 ... </textarea>

    • 把JST的<textarea>放入<form>中必须要谨慎,除非你想在点击提交时把他们发送到服务器。

  • 为何使用 <textarea>?

    • <textarea> 元素有一个很好的特性是不会把它内部的代码innerHTML的格式搞乱。

      • <textarea>的innerHTML的稳定性十分重要因为JST语法允许在任何奇怪的位置放入一些控制流标记(如 if/elseif/for),甚至在HTML标签中,例如:

        • <option value="${country.name}" {if country.name == currCountry}selected{/if}>

      • 就和大多数服务器端模版语言一样,JST的语法并不是真正的HTML/XHTML/XML。因此,使用<textarea>来存放JST模版是最好的。

    • 关于<textarea>要注意: 浏览器会转化<textarea>的内容中的 < 和 > 变成 &lt; 和 &gt;。

      • 所以,TrimPath.parseDOMTemplate() 和 TrimPath.processDOMTemplate() 会自动把 < 和 > 转化回来,这样你仍然可以使用这个模版。

还有,你也可以把 *.jst 文件放在Web服务器上,并且通过XMLHttpRequest 或者是隐藏的iframe把他们载入到浏览器中。

服务器端 JST 执行

我们设计JST时不仅让她可以在浏览器中运行同时还可以在任何单独的JavaScript解释器中运行(例如 Mozilla Rhino 或者是 SpiderMonkey)。核心的 JST 引擎不会依赖关键的DOM/DHTML/浏览器。

PHP 对比 PERL

本文是 http://tnx.nl/php – 如果你要复制他,请保持这个链接。

翻译:ShiningRay @Nirvana Studio

目录

参数和返回值极其矛盾

要展示这个问题,下面有一个函数列表,里面的函数用来匹配用户定义的内容:(也许甚至那些用PHP的人才会使用这个文档,只是用来查看该用哪个函数:P)

  匹配 替换使用 大小写敏感 返回的数字 数组参数 返回匹配 s/m/x标志 偏移(-1=结尾)
ereg ereg   所有 数组 0
ereg_replace ereg 字符串 所有 0
eregi ereg   所有 数组 0
eregi_replace ereg 字符串 所有 0
mb_ereg ereg[1]   所有 数组 0
mb_ereg_replace ereg[1] 字符串/表达式 所有 0
mb_eregi ereg[1]   所有 数组 0
mb_eregi_replace ereg[1] 字符串 所有 0
preg_match preg[2]   皆可 一个 数组 0
preg_match_all preg   皆可 所有 数组 0
preg_replace preg 字符串/表达式 皆可 无/所有 0
str_replace str 字符串 所有 数字 0
str_ireplace str 字符串 所有 数字 0
strstr, strchr str   一个 子串 0
stristr str   一个 子串 0
strrchr str   一个 子串 -1
strpos str   一个 索引 n
stripos str   一个 索引 n
strrpos char[3]   一个 索引 n
strripos str   一个 索引 -1
mb_strpos str[1]   一个 索引 n
mb_strrpos str[1]   一个 索引 -1

这种问题还存在在其他的函数组里,不仅仅是匹配的这部分而已。

(在Perl中,所有这些功能都可以通过四个简单的操作符来完成。)

[1] 用于处理多字节字符
[2] PCRE regex: 所谓的“Perl兼容”的正则表达式。
[3] 在PHP5中也是字符串str

PHP对大小写不敏感的操作使用不同的函数

(这个可能会有两方面的争论。有些人认为提供不同的函数更好,即使这意味着又要记很多名词了)

在Perl中,你可以使用两个lc() 或者是 /i 标志,而PHP通常会提供一个大小写敏感的变量。而且,大小写不敏感的那些版本的函数名的命名方式也不一致。

Perl: $foo cmp $bar lc $foo cmp lc $bar
PHP: strcmp($foo, $bar) strcasecmp($foo, $bar)
     
Perl: index($foo, $bar) index(lc $foo, lc $bar)
PHP: strpos($foo, $bar) stripos($foo, $bar)
     
Perl: $foo =~ s/foo/bar/ $foo =~ s/foo/bar/i
PHP: $foo = str_replace('foo', 'bar', $foo) $foo = str_ireplace(...)
PHP: $foo = ereg_replace('foo', 'bar' ,$foo) $foo = eregi_replace(...)

PHP的函数命名方式的不一致

  • 大小写不敏感的函数有一个’i’或者’case’在函数名的不同的位置。
  • 毫无表现上的规律 有下划线的 和 没下划线的:
    Perl的核心函数名则没有含有下划线的。
  • PHP 有 unlink 、link 和 rename 和系统调用一致,但是 touch 的系统调用是 utime, 不是 touch。
  • 同时你也无法确定单词的顺序:
    • 宾语 动词:base64_decode, iptcparse, str_shuffle, var_dump
    • 动词 宾语:create_function, recode_string

    Perl的核心函数都是“动词 宾语”结构的除了替代的 dbm* 函数。(注意里面的 sys 是一个前缀,而不是一个宾语。同时 flock 和 lstat
    是根据系统调用命名的。shm* 和 msg* 是库函数调用)

  • “to” 还是 “2”?

    ascii2ebcdic, bin2hex, deg2rad, ip2long, cal_to_jd (jdto*, *tojd), strtolower,
    strtotime,

PHP没有词法范围

Perl 有词法范围和动态范围。PHP则没有。

对于为什么词法范围很重要的解释,可以参考 Coping with Scoping.

  PHP Perl
超全局(Superglobal) [1]
全局(global)
函数局部
词法域(块局部)
动态域

[1] Perl有一些变量总是在main:: 命名空间中。这些类似于PHP的超全局变量。
[2] 在子过程的块中使用一个词法变量,就可以作为一个函数的局部变量。

PHP的主名空间中函数太多

(使用编译了所有核心分发包中的可用扩展的核心库,我们使用了2003年11月发布的版本)

注意,Perl的一些函数有简短的等价语法:

[1] 来源:PHP Quick Reference
[2] 来源:perldoc perlfunc

PHP缺少抽象令 TIMTOWTDI* 走向糟糕的极端

*(There Is More Than One Way To Do It,有不止一种方式来完成它)

为什么PHP有3079个函数但是Perl却只有206个?在PHP中,常常有好几个十分相似的函数。在Perl中,你要了解和记住的要少很多。

另外一个重要的因素是模块的使用,尤其是DBI模块——它用来提供数据库支持,而不是把很多特性塞进内核,占用了空间却很少用到。

(不常用的模块不计算在内(所以这里排除了PHP的PEAR和Perl的IO::File)). 如果核心没有提供类似的功能,那么这些模块也会算在里面。为了简便起见,内部的工作方式将会忽略。)

[1] 因为系统的LIST语法和DBI的占位符,显式转义常常是不需要的。
[2] 在Perl中是由PerlIO层来处理的。

  • Re^2: Is Perl a good career move? by Juerd, 2005
    • 依然没有命名空间
    • 没有闭包,甚至没有匿名函数
    • 没有良好的HTML分析器
    • 没有简单的MIME构建工具
    • 没有良好的WWW库
    • 没有 CPAN
    • 没有数组
    • 没什么用的逻辑操作符
  • Yaywoo! by Dave Brown, 2004
    • 使用system()无法避免(不安全的)shell
    • XY-problem
    • 大量的不同程序,但是,很多只是做了差不多的事情的变体
    • 第二参数和返回值毫无意义
    • 函数名的差劲拼写方式
  • Why PHP sucks by Edwin Martin, 2004,中文翻译 为什么PHP令人不爽(对于大型系统)
    • 不良的递归支持
    • PHP 不是线程安全的
    • PHP 由于商业原因而不健全
    • 没有命名空间
    • 非标准的日期格式化字符
    • 混乱的许可证
    • 不一致的函数命名规则
    • 魔法引用地狱
  • Perl vs. PHP – octo’s subjektiver Vergleich by Florian Forster, 2003 (German)
    • Perl 比 PHP 快很多
    • Perl 比 PHP 更丰富
    • Perl 比 PHP 有更好的文档
    • PHP 缺乏模块化支持
    • PHP的here-docs对Windows用户毫无用途
    • PHP 缺少一致的数据库API
    • PHP 缓存数据库查询结果很危险
    • 图形上,PHP实际上被限制在了 GD 中
  • I hate PHP by Keith Devens, 2003
    • 白痴似的——调用时不推荐引用传递
  • Experiences of Using PHP in Large Websites by Aaron Crane, 2002
    • PHP 是推荐把表现和业务逻辑结合起来的
    • 没有命名空间造成很多问题
    • php.ini 的全局配置
    • 过分简单化导致了额外的复杂度
  • PHP Annoyances by Neil de Carteret, 2002
    • 没有真正的引用或者指针
    • 毫无命名空间的概念
    • 毫不组件化
    • 想变成Perl,但事实上也没想变成Perl
    • 没有标准的DB接口
    • 所有的PHP社区都是针对非程序员的
    • 不支持链式方法调用 (现在已经不是了 –tnx.nl)
    • 没有全局变量除非通过导入
    • register_globals 和 $_REQUEST 都让人痛心
    • 数组都是哈西表
    • PEAR 并不是 CPAN
    • Arrays 不能内插值替换成字符串(如$a=array();$b="$a";是错误的)
    • 没有类似 “use strict” 用来检验变量名的功能
  • PHP: A love and hate relationship by Ivan Ristic, 2002
    • 社区令我不安
    • 知识渊博的人少之又少
    • Zend 发布的文章还建议不安全的实践方式
  • My list of PHP shortcomings by Nathan Torkington, 2001
    • 没有命名空间
    • 所有的函数都是全局的
    • 没有真正的引用
    • 没有真实的数据结构 (现在已经不是了 –tnx.nl)
    • 没有匿名函数

参考

EFnet #php:
19:45 <+Dragnslcr> Comparing PHP to Perl is like comparing pears to newspapers

Perl Monks: PHP – it’s “training wheels without the bike” — Randal L. Schwartz

为什么PHP令人不爽(对于大型系统)

译者:本文其实非常老,大约是2002年的文章,主要针对的是PHP4的版本,其实文中很多问题都随着PHP的不断发展而解决了。有批评才有进步嘛。

Edwin Martin <edwin@bitstorm.org>.

翻译:ShiningRay @ Nirvana Studio

我在过去的四年里一直致力于PHP应用的开发。PHP确实十分容易编写。但是PHP也有一些十分严重的缺陷。

下面我会给出我的理由,为什么PHP不适合于比小型业余网站更大的网站。

1. 对递归的不良支持

递归是一种函数调用自身的机制。这是一种强大的特性可以把某些复杂的东西变得很简单。有一个使用递归的例子是快速排序(quicksort)。不幸的是,PHP并不擅长递归。Zeev,一个PHP开发人员,说道:“PHP
4.0(Zend)对密集数据使用了栈方式,而不是使用堆方式。也就是说它能容忍的递归函数的数量限制和其他语言比起来明显少。”见bug
1901
。这是一个很不好的借口。每一个编程语言都应该提供良好的递归支持。

2. 许多PHP模块都不是线程安全的

在几年前,Apache发布了Web服务器的2.0版。这个版本支持多线程模式,在这个模式下,软件一个一部分可以同时运行多个。PHP的发明者说PHP的核心是线程安全的,但是非核心模块不一定是。但是十次有九次,你想要在PHP脚本中使用这种模块,但这又使你的脚本不能合适Apache的多线程模式。这也是为什么PHP小组不推荐在Apache
2 的多线程模式下运行PHP
。不良的多线程模式支持使PHP常被认为是Apache 2依然不流行的原因之一。

请阅读这篇讨论: Slashdot: Sites Rejecting Apache 2?.

3. PHP 由于商业原因而不健全

通过使用缓存,PHP的性能可以陡增500%[见基准测试]。那么为什么缓存没有被构建在PHP中呢?因为Zend——PHP的制造者,它在销售自己的Zend
Accelerator
,所以当然,他们不想抛弃自己的商业产品这块肥肉。

但是有另一个可选择的: APC.
(Zend后来推出Zend Optimizer,免费的加速器——译者)

4. 没有命名空间

设想某个人制作了一个PHP模块用来阅读文件。模块中一个函数叫做read。然后另一个人的模块可以读取网页的,同样包含一个函数read。然后我们就无法同时使用这两个模块了,因为PHP不知道你要用哪个函数。

但是有一个很简单的解决方法,那就是命名空间。曾经有人建议PHP5加入这个特性,但不幸得是他没有这么做。现在,没有命名空间,每个函数都必须加上模块名作为前缀,来避免名称冲突。这导致了函数名恐怖得长,例如xsl_xsltprocessor_transform_to_xml让代码难于书写和理解。

5. 不标准的日期格式字符

很多程序员对 日期格式字符
都很熟悉,它是从UNIX和C语言中来的。其他一些编程语言采用了这个标准,但是很奇怪的,PHP有它自己的一套完全不兼容的日期格式字符。在C中,“%j”表示一年中的当天,在PHP中他表示一个月中的当天。然而使事情更混乱的是:Smarty
(一个很流行的PHP模版引擎)的 strftime
函数和 date_format
函数,却使用了C/UNIX的格式化字符。

6. 混乱的许可证

你也许认为PHP是免费的,所有的在手册中提到的PHP模块也是免费的。错了!例如,如果你想在PHP中生成PDF文件,你会在手册中发现两个模块:PDF
ClibPDF。但是这两个都是有商业许可证的。所以,你所使用的每个模块,你都要确保你同意他的许可证。

7. 不一致的函数命名规则

有些函数名称是有多个单词组成的。一般有三种单词组合的习惯:

  1. 直接拼接:getnumberoffiles
  2. 用下划线分开:get_number_of_files
  3. 骆驼法则:getNumberOfFiles

大部分语言选择其中一中。但是PHP都用到了。

例如,你想要把一些特殊字符转换成HTML实体,你会使用函数htmlentities
(直接拼接单词)。如果你要使用相反的功能,你要用到它的小弟弟html_entity_decode。由于某些特殊的原因,这个函数名是由下划线分隔单词。怎么能这样呢?你知道有一个函数叫strpad。或者他是str_pad?每次你都要查看一下到底这个符号是什么或者直接等他出现一个错误。函数是不分大小写的,所以对于PHP来说rawurldecode
RawUrlDecode之间没有什么区别。这也很糟糕,因为两个都使用到了同时他们看上去还不一样,混淆了阅读者。

8. 魔法引用的地狱

魔法引用(Magic quote)可以保护PHP脚本免受SQL注入攻击。这很好。但是出于某些原因,你可以在php.ini中关闭这个配置。所以你如果要写出一个有弹性的脚本,你总要检查魔法引用是开启还是关闭。这样一个“特性”应该让编程更简单,而事实上变得更复杂了。

9. 缺少标准框架

一个成长中的网站没有一个整体框架,最终会变成维护的噩梦。一个框架可以让很多工作变得简单。现在最流行的框架模型时MVC-模型,在其中表现层、业务逻辑和数据库访问都分离开了。

很多PHP网站不使用MVC-模型。他们甚至没有一个框架。甚至现在有一些PHP框架同时你都可以自己写一个,关于PHP的文章和手册没有提高框架的一个字。同时JSP-开发人员使用像Struts的框架、ASP开发人员使用.Net,看起来好像这些概念都广泛被PHP开发人员所了解。这就说明了PHP实际上到底是多专业。

总结

超载的汽车什么问题?

对于非常小的项目,它可以是一个十分符合人意的编程语言。但是对于较大的和更为复杂的项目,PHP就显出他的薄弱了。当你不断地摸索之后,你会发现我提到的某些问题的解决方案。所以,当解决方案已知之后,为什么不能修正他呢?另外为什么这些修补不在手册中提到呢?

一个开源的语言十分流行是一件好事。但不幸得是,它不是一个伟大的语言。我希望所有的问题能有一天得到解决(也许在PHP6?),然后我们就将拥有一个开源语言,他既开源,又好用。

到现在,当你要启动一个多于5个脚本页面的项目的时候,你最好考虑C#/ASP.Net 或者 Java/JSP或者也许Python同样是一个更好的选择。

在我写了这篇文章之后,一些人告诉我一些其他的类似文章:

Cake 指南:建立一个简单的Blog

注意!
本指南针对cake_0.9.1_dev 发布版不是SVN的版本。请不要把指南的代码改成于SVN一样。

Cake 指南:建立一个简单的Blog

这是一个还在不断变化的一个应用框架的指南。文章中的东西可能可以运行,但是如果有些东西无法运行,请您先仔细阅读 API 文档 。错误报告对我们很有价值,所以,请报告任何影响您使用的地方。

注意:命名方法的约定已经从下划线_风格变成了骆驼回归法则。所以,在我们目前的发布版中link_to()已经变成linkTo()

默认Cake目录结构一览

在本篇指南中,我们将一起使用Cake来创建一个简单的Blog应用。我假设你对PHP十分了解,可以在你的系统中游刃有余(包括从命令行中运行程序),同时已经有一个配置好的开发服务器环境,例如运行PHP的 XAMPP

下载

下载最新的Cake 包并解压缩到你的Web服务器的DOCUMENT_ROOT下(因为本文是指南的缘故,我假设他可以从http://localhost/cake/下访问)。你可以看到基本的目录结构。

创建数据库

创建一个用来存放Blog帖子的表格,并且初始化一些数据。以下是SQL语句:

注意表名称是复数形式, 这是Cake使用的所有表格的默认形式。还有,idcreatedupdated字段是有特殊含义的,等一会儿你就会发现。也就是说现在你不应该更改他们的名字

配置Cake的数据库设置

若要配置数据库访问,我们编辑 config/database.php.default (他应该是自己包含说明的), 并将它保存为config/database.php. 如果 config/database.php 不存在,Cake仍会运行,并且不使用数据库访问。

创建一个模型类 (MVC中的Model)

现在给帖子表格posts创建一个模型类,新建 app/models/post.php包含以下内容 :

app/models/post.php

这已经足以使Cake来关注这个模型了,载入、并把它和数据库表格连接在一起。注意:模型类的类名是单数形式。坚持命名约定十分重要,因为如果不这样,Cake就不会自动运行。默认情况下,模型类和数据库表使用同一个单词而前者以单数形式,后者是复数形式。

建立一个控制器助手类

新建一个控制器助手类。把下面的内容放在 app/helpers/posts_helper.php中:

app/helpers/posts_helper.php

创建一个控制器类 (C ontroller)

新建一个控制器类. 把下面的内容放在 app/controllers/posts_controller.php中:

app/controllers/posts_controller.php

控制器已经准备好了,这是我们需要在他里面添上行为(Action)来对存储的数据进行操作。添加一个方法到 PostsController 类中 :

app/controllers/posts_controller.php

PostsController::index() 不需要其他内容了,除了需要一个模版(在Cake中称之为视图“View”)。

建立一个视图 (View)

把下面的代码放入 app/views/posts/index.thtml:

这应该可以正确运行,我们来测试一下。我假设你可以通过浏览 http://localhost/cake/ 来得到Cake目录,这样测试一下我们的新控制器,它指向 http://localhost/cake/posts/index 。你会(希望如此)看到类似于下面的一些东西:

为什么我没看到呢?

如果你遇到了一个页面,上面说“not Found: The requested URL /posts/index was not found on this server,”你可能要使用 http://localhost/cake/index.php?url=posts来访问. 很明显,这很不美观。如果你遇到了一个页面上说“Fatal error: Call to a member function on a non-object …”那么你可能要检查一下你的配置内容,有没有把config/database.php.default 改名为 config/database.php. 参见 Blog指南中的故障及解决方法

我们现在做了些什么?

让我们回顾一下。我们创建了数据库表posts,一个模型类Post,一个控制器PostsController 和他的 index()方法,还有一个视图文件app/views/posts/index.thtml。我觉得他一点也不困难。让我们继续吧。

帖子标题是连接到/cake/posts/view/[post_id]的. 让我们点击它。

噢~,对了,我们忘记添加PostsController::view()行为了。让我们现在完成它:

app/controllers/posts_controller.php

还有视图文件:

app/views/posts/view.thtml

回到浏览器,刷新,看到:

成功了!

添加功能

在指南的第一部分之后,我们有了一个帖子的列表,同时我们可以查看帖子了。当我们完成了第二部分,我们可以:

  • 添加一个新的帖子。
  • 删除不要的帖子。
  • 编辑存在的帖子。

添加一个新的帖子

添加一个新的帖子:

app/controllers/posts_controller.php

同时行为的模版文件是:

app/views/posts/add.thtml

现在你可以通过地址”/cake/posts/add”来访问 add 页面 page at the address “/cake/posts/add”, 或者,我们可以在索引页面的底部放上一个快捷的 “Add new post”连接:

app/views/posts/index.thtml

现在让我们试试添加一些帖子。

像我这样没规矩的,添加了一个没有标题的帖子。当然,我们可以通过数据有效性检验(Data Validation)来避免这种不正确的行为。

数据有效性

数据有效性规则是放在数据模型中的。

app/models/post.php

了解API文档中更多关于有效检验器的内容。

删除一个帖子

app/controllers/posts_controller.php

delete 行为没有模版。在成功删除之后,我们只要显示一个快捷消息(所谓的“flash”),然后转回索引页面。

现在我们添加一个删除行为的连接到每一个blog帖子的视图中:

app/views/posts/index.thtml

在完成它之后,我们就可以删除那些空白标题的帖子了。

编辑帖子

app/controllers/posts_controller.php

app/views/posts/edit.thtml

你也可以在表单标签中用

来代替直接使用html的<input>标签。

同时, 在 index.thtml 中, 我们添加一个编辑连接:

从视图中分离逻辑

让我们回头看一下 index.thtml 视图:

app/views/posts/index.thtml

我们应该从视图中删除 findAll() 的调用,然后把它放在控制器重。这可以建立一种更好的逻辑和视图的分离。然后我们从控制器重为视图获取数据。现在就让我们完成它吧。

在帖子的控制器中,我们从posts模型中获取所有的记录,并且储存在变量 data 中。

app/controllers/posts_controller.php

同时在视图中,我们通过对每行数据的迭代,来全部显示他的内容。

app/views/posts/index.thtml

这太简单了,不是么?

jMemorize 介绍

jMemorize是一个 Java 应用程序,可以使用著名的Leither系统来管理你的抽认卡,让记忆事情不仅有效而且有趣。它可以管理你的整个学习过程和特点分类、统计和一个优雅的外观。

Leitner 原理

Wikipedia的文章这样定义Flashcard (抽认卡):

一个抽认卡是一小块纸片,在学校里用来作辅助教学(主要是英语国家)。抽认卡可以用来记录词汇、历史事件时间、公式等等。使用抽认卡的目的主要是帮助[[记忆]]。你在每个卡片上写下一个问题(同时在背面记下答案),用它们来测试自己,并根据你的测试、学习结果把它们进行排序、分组。

这种策略使得学习更有选择性,也是就:在一组的卡片越难,你就越要花时间复习这一组。于是,你就能节省很多学习时间。

这个方法是由德国心理学家 Sebastian Leitner 在 19世纪70年代提出的。 Pimsleur 语言课也是基于类似的想法。

这个基本思路是根据它们对你的难易程度把卡片划分成不同的级别。这是通过你尝试回答一些重复问题来完成的。每次你了解的一个卡片的正确回答,它就会放到下一个较高的卡片级别中。如果某个卡片你没有回答出来,它就会被放回到开始的级别。

这个系统是和时间表结合在一起的。已经学习过的卡片,会在一个特定的失效期过后,重新被确认为要学习。级别越高,失效时间就越长。例如一个卡片第一
次成功检测过之后,会被安排在一天之后再次学习,当这个卡片连续三次都回答正确了之后,系统会在一周内都认为学过了。只要这个卡片被认为是学过了,它就不
会在当前学习阶段出现。

总之,这个系统将管理你的个人学习过程并让你关注于学习本身,同时它会自动决定哪个事物现在要再学习,尽可能让这些事情从你的时间中抽出来。

特性

  • 新建、编辑、删除和重置卡片。
  • 通过建立卡片分类来安排卡片。
  • 你当前的学习情况的可视化演示。
  • 可方便地自定义你的学习会话设置。这包括以下功能:
    • 选择要学习的分类。
    • 选择只学习新的卡片、过期卡片和所有卡片。
    • 选择卡片/时间的限制。
    • 选择你是否想以普通模式(顺序)学习还是随机模式。
    • 选择预设的时间表或自己创建自定义时间表。
  • 可以搜索你的卡片
  • 还有很多很多

许可证

本软件由 Riad Djemili ( riad.de) 开发以 GPL license.发布。这表示 jMemorize 是自由软件,不仅可以自由分发二进制代码,同时还提供完整的源代码。

JavaScript的类型转换

我阅读了birdshome的在JavaScript中也玩变量类型强行转之后,在文章后面留言。也许我是表达不清楚,所以我想澄清一些情况,首先我反对使用“强制类型转换”这个词。

首先我们可以看C中间的强制类型转换,只能存在于基本类型之间,像整数可表示类型和浮点数类型之间,比如(int)2.45,这是可以的,但是他们和字符串之间都没有强制转换的可能,因为字符串实际是一个指针。因此不可能出现(char *)2.45就可以获得"2.45"这个字符串的情况,反之也不能。
而在C++中,我们可以创建一个类,并且重载强制转换操作,来完成这种情况,那么就必须要求有相应的对象,但是对于基本类型,这也是不可以的。

然后,因为JavaScript在对象模型上主要参考了Java,我们可以再参考一下Java的类型转换:在Java中,基本类型之间的强制转换也不是这样的,比如,整数要转换成字符串,必须使用Integer.toString()静态方法或者String.valueOf()静态方法,把字符串转换为整数,必须使用Integer.valueOf()

可见,不能把JavaScript中的类型转换看作为“强制类型转换”。

在JavaScript中,Double类型和Int类型都是看作为Number对象,因此无论是typeof 1还是typeof 1.0,都是返回number。这样我们可以不用去管是Int还是Double类型,让JavaScript解释引擎内部去处理。

  • 如果要把Number转换成String,可以使用NumbertoString()方法,(象(1).toString()括号必须或者 1 .toString()空格必须,否则会编译出错,如果是变量则无需),或者调用String()函数,以及直接进行字符串连接(字符串变量或常量后直接进行“+”操作)这几种方法都会自动调用解释引擎内部的 NumberToString()函数,或者根据进制调用其它函数,基本类似。
  • 如果要把String转换成Number,可以使用Number()函数,他会自动判断String中是整数还是浮点数,然后内部使用相应的数据类型,另外可以使用全局函数parseInt()和parseFloat(),他们根据你的要求进行转换。同样的,他们在解释引擎内部机制上是使用了 StringToNumber,StringToInt等等内部的函数。
  • 而如果是Double转换成Int,必须使用Math.floor()函数(截尾取整)或者Math.round()(四舍五入)。使用parseInt只能增加运算量,先从Double到String再到Int
  • Int转换成Double,无须考虑任何问题,直接把Int当成Double进行运算

注:NumberString函数是特殊的函数,在JS引擎中,他会自动判断是作为构造函数调用还是普通调用,所以既可以使用new关键字,也可以作为函数直接调用。

关于JS的参考手册,微软有一个CHM十分不错,指南、API参考都有,是Windows脚本技术,中文版,我在MSDN上下载的。而关于JS解释引擎的,我参考的是Netscape的Spidermonkey,现在由Mozilla组织维护。