[转]敏捷软件开发不是黑客行为

(本文发表于程序员杂志2006年第4期)

在很多人的印象中,敏捷软件开发是种类似黑客行为的过程,是程序员最爱的勾当。不写文档,不作需求分析,没有项目经理,做什么东西完全是程序员自己的行为。所以他们认为这样的过程无法满足真正大型项目和复杂项目的需要,因此在经过考虑后,放弃了敏捷方法。

真的是这样吗?敏捷过程到底是如何做需求分析?用户故事和用例有什么区别?敏捷过程如何去管理需求的?这些是一些想要实践敏捷的人一直在困惑的事情。

我们常常看到书中讲,程序员拿到一个用户故事后,怎么计划,怎么分解,怎么写单元测试,怎么小步前进,怎么持续集成。这是典型的程序员视角。事实上,敏捷方法分为三部分,敏捷项目管理,敏捷需求分析,敏捷软件开发。上述书中提到的完全是敏捷开发中的实践,很多人了解到的敏捷,只是敏捷的三分之一。

在敏捷的团队中,作一个敏捷程序员确实是非常舒服的事情。从程序员的角度来看,只需要选择一张他感兴趣的故事卡片,了解清楚该卡片的需求,开始从功 能测试写代码,等通过了所有测试就完工。基本上不需要考虑太多的事情,非常轻松愉快。但程序员向谁去问清楚需求?故事卡片是怎样写出来的呢?让我们来关注 开发前发生的事情。

了解敏捷过程的人都知道,Kent Beck在XP过程中提到了现场客户,如果一个敏捷团队能够有现场客户,这当然是最棒的事情。但多数情况下,客户都是很忙碌的,很难全力投入到软件开发过程中。这时候,我们就需要商务分析师这个角色,来充当客户的角色。

我在ThoughtWorks的团队中担任的就是商务分析师这个角色。商务分析师最重要的职责就是与客户交谈,了解和分析需求,将其制作成用户故事并将需求转述给程序员。同时,商务分析师也要代替客户负责功能验收测试。

敏捷思想的核心是人与交流。需求问题实际上是一个交流问题。商务分析师要和客户交流,搞清楚客户到底需要什么,到底为什么需要这些东西。商业价值是 商务分析师关注的最终目标。有了目标的指向,就可以不迷失方向。和客户进行交流,最终目的就是挖掘出客户的商业目标。可能大家会经常有这样的经验,客户 说,我要这个功能,我想要怎么怎么样。这时候要特别注意,他说的这些东西并不是真正的需求。商务分析师需要详细的问客户为什么,挖掘出他真正的目标。

在这个目标下,商务分析师开始进行需求的分析:我们到底是否真的需要这个需求?有没有更好的解决方案?有没有简单并且低廉的方式?换一种形式是不是也能达到这样的需求?这个需求有多少地方涉及到以前的软件变更?

搞清楚这些事情后,就可以写出用户故事。用户故事的书写遵循一定的原则,一般包括三部分:"作为(系统的一个涉众),我想要(做一件事),从而(达到一个商业价值)"。在书写的时候格式比较随意,可以在故事卡背面写上注释或疑问,甚至画上界面原形图。

举一个最常见的用户故事例子,"作为一个普通用户,我希望能够用用户名和密码登录,以便我能享受到个性化的服务"。其中,用户是系统涉众,登录是他想要做的事情,而他的目标是获得个性化的服务。

从这个例子我们可以想象到,这个页面可能存在两个文本框,用于输入用户名和密码,有一个按钮来登录,并且不登录就不能看到个人资料,另外,如果用户 输入错误需要提示"登录失败请重试"。这就是可见性,也可以称为可测试性。我们可以根据这样的可见性写出功能测试,从而驱动这个用户故事的开发,这被称为 Acceptance Driven Development。

用户故事的作用有两个,一个是作为进度跟踪的依据,一个是作为与人交谈的备忘录。用户故事卡片并不是很精确的需求,因此不需要把事情描述的非常清 楚。将需求的详细分析推迟到实现前夕来完成,这是敏捷需求分析的精华所在。任何提前做好的东西都会导致浪费,敏捷过程提倡足够就好,避免浪费。

不少人对用户故事和用例的区别感到疑惑。用户故事的作用是备忘功能,而不是文档。而用例需要详细的描述其操作步骤,以及每个异常路径,因而起到了文 档的作用。用户故事是可见的商业价值,而不是功能描述。每个用户故事的粒度和工作量都相差不多,这和用例有很大的区别。用户故事是小粒度的,可测试的,可 见的,并且是有价值的。[注:此处存有争议,请读者辨证阅读,勿断章取义]

ThoughtWorks有个项目组作的是一个网游物品交易平台。该平台是典型的互联网项目,在开工的时候客户对功能需求还不明确,但需要快速推出抢占市场,正是最适合敏捷过程的项目。

在项目伊始,商务分析师和客户做了深入的谈话,了解他的商业构想,他的盈利模式,搞清楚宏观的结构,然后思考并整理获得的结果,花1-2天时间将客 户需求大略整理为几十个用户故事。这些用户故事并不完善,不足以做好整个系统。但对于我们开始项目的前一阵,已经足够了。我们可以从这里开始项目。

敏捷方法希望快速交付可用的软件。实现软件的快速交付是通过迭代来完成。在迭代开始前,由一组有经验的开发人员大致评估一下用户故事,标记出不同的 难度和风险,并提出问题供商务分析师来获得更详细的信息,商务分析师会和相关涉众去讨论。然后商务分析师将推荐优先级最高的一组用户故事给客户来挑选,客 户可以选择这些用户故事,或者指出从他的视角看到的优先级更高的用户故事。这些将成为下一个迭代的内容。

客户看到每个迭代交付的可运行的软件后或者得到用户反馈后,常常会有新的想法冒出来。有些想法是好的,有些想法就属于看到别家网站有这个功能,不假思索的提出的功能。这些不同的需求都需要经过认真的分析,找出哪些是值得我们立即考虑的,哪些是不用急迫的去实现的。

有一次和客户谈话时,他说到希望增加拍卖功能。那么,我们为什么需要拍卖呢?客户说希望让用户拍卖物品以获得最高价格。经过考虑,我们发现,网游物品的实时性和唯一性决定了系统不适合使用拍卖机制。拍卖的时效性无法满足实时交易的要求,因此,用户最终放弃了这个特性。

另一次,客服人员提出增加一个查询用户交易的功能,而此时我们有其他更加重要的功能需要先去考虑,查询用户交易功能可以由技术人员临时通过数据库直 接代为查询,因为项目运营初期交易不是很多,暂时还不需要专门的后台功能来支持客服的工作。所以把这个需求卡片一直贴在墙壁上,始终没有排到最高的优先 级。

客户一开始也不是很能够接受敏捷需求中强调商业价值和优先级的做法。但经过几个月的磨合,客户也逐渐适应了许多敏捷思想,甚至我在和客户讨论时,偶然提起了后期的某种可能的情况,他们还能够帮我纠正应当考虑目前的情况,为近期的情况作计划。

用户故事的跟踪和管理是由项目经理来进行。每个迭代跟踪卡片的进展,是否已经开始实现?是否已经完成代码开发?是否已经开始功能测试?不同的卡片在 迭代前都会评估为不同的大小。我们一般分为大中小三级。等实践过几个迭代后,团队的开发速度基本保持恒定,我们就可以很容易的知道每个迭代能做多少个用户 故事,这样就可以安排下一迭代的开发。

每个迭代内分析好恰好足够下一个迭代开发的需求,就是商务分析师每个迭代的主要工作内容。商务分析师的需求分析工作在上一个迭代完成,包括需求的了解,分析,评估和排列优先级。

在每个迭代开始的时候,由商务分析师主持召开迭代计划会议,在会议上向所有的程序员解释这个迭代要完成的用户故事,然后由程序员自由提问,知道他们能够获得足够开始实现该功能的信息。

在程序员完成一个用户故事后,商务分析师还要来代表客户做功能验收测试,查看是否完成了预计的功能,是否有程序员还没有想到的异常情况。如果存在问题需要退回给程序员继续完成。这在一定程度上保证了系统完成的需求不偏离客户的要求。当然,更多的测试还需要QA来完成。

我们的实践充分表明了,敏捷过程并不是没有需求分析,而是把需求分析过程分散到整个开发的过程中,让开发和需求分析并行进行。这就是ThoughtWorks敏捷方法实施成功的秘诀之一。而商务分析师在这个过程中,起到了纽带和桥梁的作用,是一个团队不可缺少的角色。

Read: 570

好歌推荐——Bonnie Winds ——Bohinta

好歌推荐——Bonnie Winds ——Bohinta

这首Bonnie Winds,取名自1998年8月26日正面吹袭美国北卡罗来纳州的一场热带风暴。这首歌曲中基本上运用的都是原声乐器,只是将一些凯尔特人最传统的乐器比如风笛,口琴,小提琴统统进行了协调处理,使之在音乐以外能滋长出更多质朴于纯净的味道。

Bohinta由来自都柏林的兄妹Martin Furey和Aime Furey组成。

选自专辑:Sessions

about the bond

Bohinta [ theband ]

"Nobody’s done music quite like this before..hints of Moving Hearts and an ethereal quality reminiscent of Enya, but comparisons will never do them justice, they need to be heard"

ROCK N’REEL

Bohinta were formed in Dublin and are fronted by Martin Furey of the famous Irish musical family. On-stage, they play an eclectic mix of original and contermporary songs and tunes drawn from Martin’s inherent song-writing skills and the wealth of Irish music and Celtic-lore.

Hauntingly beautiful, this is music for the heart and soul that effortlessly blends the ancient with the new. Together Bohinta play uillean pipes ,guitar,bodhran,whistles, djembe, percussion,keyboards,bass, flute and of course vocals. The band have already graced The Fleadh-London, Glastonbury Festival ,the Phoenix and Dranouter Festival, Belgium in addition to many folk festivals and extensive tours of the UK and Ireland. In 1999 they embarked upon an eight-week tour of Australia wooing over festival and theatre audiences with ease and producing an immediate invitation for a speedy return. Musical accomplishments include reaching number 7 in the French charts, a number 5 in the Australian Irish-Traditional charts and a number 7 in the U.S traditional charts. Martin’s playing on uillean-pipes and whistles, featured recently in the BBC TV production (Rennoch The Red Deer’ and also on a production of Macbeth which won the silver-screen award for the best sound-track.

Bohinta’s long-awaited second album ‘Belladonna’ is released and out now (02). The album was played extensively pre-all Finbar Furey shows on the February/March 02 tour earlier this year, when Martin and Greig ( of Bohinta ) supported and played alongside the great man himself. The album is entitled "Belladonna" and was recorded at Realworld Studios, home of Peter Gabriel. Martin’s first solo offereing is "Howl", which features and is produced by Martin Swan of Mouthmusic. Martin also sings and plays on the current Mouthmusic album "Seafaring Man" on Skitteesh Records. "Howl" will be released on Skitteesh Records later this year,preceding the outhmusic tour on which Martin will playing in Feb/March 03. In addition to further Bohinta dates, Martin and Greig on percussion will also be joining forces once again with Finbar friends in the October 02 dates and an extensive March/April 03 tour!

7,8年后都难得这首歌还能有印象。。。。呵呵。。。
总算找到了 ,,,曾经找过很多次

顺别列一下其他几首我都很喜欢的歌 都是非流行音乐类型的

Gregorain(皇家教皇合唱团)    ———- the Gift    专辑 Master of chant VI (上帝之音4)
Michelle Tumes(米雪尔 托梅斯) ——-   Feel        专辑 未知….有时间查了再补上
Secret Garden(神秘园)   —————-   Prayer    专辑 同上 神秘园很多歌我都很喜欢 就是名字记不住..

先列这几个…有时间继续写…脑子不好用啊……….

Read: 739

计算代码行数的小程序

/******************************************
*   使用方法….在命令行下运行
*   lineamount.exe > 要输出行数的文件名
*   输入含有要统计的文件路径的文件
*
*******************************************/

#include "stdafx.h"
#include "stdio.h"
#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int countline( string filepath );

int _tmain(int argc, _TCHAR* argv[])
{
   string filepath;

   cin>>filepath;

   fstream filespath(filepath.c_str(),ios_base::in);
   if(!filespath){
       cout<<"读取文件失败!"<<endl;
   } else {
       int linecount = 0, temp = 0, line = 0;
       string sep;
       while( filespath >> filepath ){
           temp = countline(filepath);
           if (temp > 0){

               cout<<temp<<"ttt"<<filepath<<endl;
               line++;
               linecount += temp;
           } else {
               cout<<"读取文件:"<<filepath<<"出错"<<endl;
           }
       }
       cout<<"总行数:"<<linecount<<endl;
   }
//   cin>>filepath;
   return 0;
}

int countline( string filepath )
{
   fstream filespath(filepath.c_str(),ios_base::in);
   if(!filespath){
       return -1;
   }
   int linecount = 0;
   string temp = "";
   while( filespath >> temp ){
       linecount++;
   }
   return linecount;
}

Read: 1133

INTRODUCTION TO JavaScript Functions

作者:F. Permadi
译者:Sheneyan(子乌)
英文原文: INTRODUCTION TO JavaScript Functions
中文译文(包括示例):javascript的函数
子乌注:一篇相当不错的function入门文章,个人感觉相当经典。

引用地址:http://www.ny2050.cn/article.asp?id=82

词语翻译列表:

function:函数(Function未翻译)
declare:定义
assign:指派,分配
functionbody:函数体(就是函数的内容)
object:对象
property:属性
unnamed:匿名(在这里没翻译成未命名)
object oriented programming:面相对相编程
class:类(比如后面的class data type我翻译成类数据类型)
pointer:指针
reassign:重新分配
nest:嵌套
feature:功能,特性
local/global:局部/全局
blueprint:蓝图(?)
user defined:用户自定义
instance:实例
prototype:原型(除了标题都不翻译)
internal:内部
constructor:构造器
duplication:

函数:定义

有以下这些方法可以定义一个函数。所有这些都是有效的,但是它们在后台如何实现的则有一些差别。

常用的写法

一般大家都用这个写法来定义一个函数:

functionName([parameters]){functionBody};
Example D1:

CODE:
function add(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3

当我们这么定义函数的时候,函数内容会被编译(但不会立即执行,除非我们去调用它)。而且,也许你不知道,当这个函数创建的时候有一个同名的对象也被创建。就我们的例子来说,我们现在有一个对象叫做“add”(要更深入了解,看底下函数:对象节。)

匿名函数

我们也可以通过指派一个变量名给匿名函数的方式来定义它。

Example D2

var add=function(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3

这个代码和前一个例子做了同样的事情。也许语法看起来比较奇怪,但它应该更能让你感觉到函数是一个对象,而且我们只是为这个对指派了一个名称。可以把它看做和 var myVar=[1,2,3]一样的语句。以这种方式声明的函数内容也一样会被编译。

当我们指派一个这样的函数的时候,我们并不一定要求必须是匿名函数。在这里,我作了和ExampleD2一样的事情,但我加了函数名“theAdd”,而且我可以通过调用函数名或者是那个变量来引用函数。

Example D2A

var add=function theAdd(a, b)
{
return a+b;
}
alert(add(1,2)); // 结果 3
alert(theAdd(1,2)); // 结果也是 3

使用这种方式来定义函数在面向对象编程中是很有用的,因为我们能像底下这样使一个函数成为一个对象的属性。

var myObject=new Object();
myObject.add=function(a,b){return a+b};
// myObject 现在有一个叫做“add”的属性(或方法)
// 而且我能够象下面这样使用它
myObject.add(1, 2);

我们也能够通过使用运算符new来定义一个函数。这是一个最少见的定义函数的方式并且并不推荐使用这种方式除非有特殊的理由(可能的理由见下)。语法如下:

varName=new Function([param1Name, param2Name,…paramNName], functionBody);
Example D3:

var add=new Function("a", "b", "return a+b;");
alert(add(3,4)); // 结果 7

我在这里有两个参数叫做a和b,而函数体返回a和b的和。请注意new Function(…)使用了大写F,而不是小写f。 这就告诉javascript,我们将要创建一个类型是Function的对象。 还要注意到,参数名和函数体都是作为字符串而被传递。我们可以随心所欲的增加参数,javascript知道函数体会是右括号前的最后一个字符串(如果没 有参数,你能够只写函数体)。你没必要将所有东西都写在一行里(使用或者使用字符串连接符+来分隔长代码)。标记告诉JavaScript在下一行查 找字符串的其余部分。例子如下:

Example D4

var add=new Function("a", "b",
"alert" + // 注意 "+"
"(‘adding ‘+a+’ and ‘ +b); // 和 ""的不同用法
return a+b;");
alert(add(3,4)); // 结果 7

采用这种方式定义函数会导致函数并没被编译,而且它有可能会比用其它方式定义的函数要慢。至于为什么,看一下这个代码:

Example D5

function createMyFunction(myOperator)
{
return new Function("a", "b", "return a" + myOperator + "b;");
}

var add=createMyFunction("+"); // 创建函数 "add"
var subtract=createMyFunction("-"); // 创建函数 "subtract"
var multiply=createMyFunction("*"); // 创建函数 "multiply"
// test the functions
alert("加的结果="+add(10,2)); // 结果是 12
alert("减的结果="+subtract(10,2)); // 结果是 8
alert("乘的结果="+multiply(10,2)); // 结果是 20
alert(add);

这个有趣的例子创建了三个不同的function,通过实时传递不同的参数来创建一个新Function。因为编译器没法知道最终代码会是什么样 子的,所以new Function(…)的内容不会被编译。那这有什么好处呢?嗯,举个例子,如果你需要用户能够创建他们自己的函数的时候这个功能也许很有用,比如在 游戏里。我们也许需要允许用户添加“行为”给一个“player”。但是,再说一次,一般情况下,我们应该避免使用这种形式,除非有一个特殊的目的。

函数:对象

函数是javascript中的一种特殊形式的对象。它是第一个[b〕类数据类型(class data type)。这意味着我们能够给它增加属性。这里有一些需要注意的有趣观点:

对象的创建

就像刚才提及的,当我们定义一个函数时,javascript实际上在后台为你创建了一个对象。这个对象的名称就是函数名本身。这个对象的类型是function。在下面的例子,我们也许不会意识到这一点,但我们实际上已经创建了一个对象:它叫做Ball。

Example 1

function Ball() // 也许看起来有点奇怪,但是这个声明
{ // 创建了一个叫做Ball的对象
i=1;
}
alert(typeof Ball); // 结果 "function"

我们甚至能将这个对象的内容打印出来而且它会输出这个函数的实际代码,Example2: 点击 alert(Ball);来看看Ball的内容。

属性的添加

我们能够添加给Object添加属性,包括对象function。因为定义一个函数的实质是创建一个对象。我们能够“暗地里”给函数添加属性。比如,我们这里定义了函数Ball,并添加属性callsign。

function Ball() // 也许看起来有点奇怪,但是这个声明
{ // 创建了一个叫做Ball的对象,而且你能够
} // 引用它或者象下面那样给它增加属性
Ball.callsign="The Ball"; // 给Ball增加属性
alert(Ball.callsign); // 输出 "The Ball"

指针

因为function是一个对象,我们能够为一个function分配一个指针。如下例,变量ptr指向了对象myFunction。

function myFunction(message)
{
alert(message);
}
var ptr=myFunction; // ptr指向了myFunction
ptr("hello"); // 这句会执行myFunction:输出"hello"

我们能够运行这个函数,就好像这个函数名已经被指针名代替了一样。所以在上面,这行ptr("hello"); 和myFunction("hello");的意义是一样的。

指向函数的指针在面向对象编程中相当有用。例如:当我们有多个对象指向同一个函数的时候(如下):

Example 4A

function sayName(name)
{
alert(name);
}
var object1=new Object(); // 创建三个对象
var object2=new Object();
var object3=new Object();
object1.sayMyName=sayName; // 将这个函数指派给所有对象
object2.sayMyName=sayName;
object3.sayMyName=sayName;

object1.sayMyName("object1"); // 输出 "object1"
object2.sayMyName("object2"); // 输出 "object2"
object3.sayMyName("object3"); // 输出 "object3"

http://www.blueidea.com/articleimg/2006/07/3823/01.gif

因为只有指针被保存(而不是函数本身),当我们改变函数对象自身的时候,所有指向那个函数的指针都会发生变化。我们能够在底下看到:

Example 5:

function myFunction()
{
alert(myFunction.message);
}
myFunction.message="old";
var ptr1=myFunction; // ptr1 指向 myFunction
var ptr2=myFunction; // ptr2 也指向 myFunction

ptr1(); // 输出 "old"
ptr2(); // 输出 "old"

myFunction.message="new";

ptr1(); // 输出 "new"
ptr2(); // 输出 "new"

指针的指向

我们能够在一个函数创建之后重新分配它,但是我们需要指向函数对象本身,而不是指向它的指针。在下例中,我将改变myfunction()的内容。

Example 6:

function myFunction()
{
alert("Old");
}
myFunction(); // 输出 "Old"
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"

旧函数哪里去了??被抛弃了。

如果我们需要保留它,我们可以在改变它之前给它分配一个指针。

http://www.blueidea.com/articleimg/2006/07/3823/02.gif

Example 6A:

function myFunction()
{
alert("Old");
}
var savedFuncion=myFunction;
myFunction=function()
{
alert("New");
};
myFunction(); // 输出 "New"
savedFuncion(); // 输出 "Old"

http://www.blueidea.com/articleimg/2006/07/3823/03.gif

不过要小心,象下面这样的例子并不会有作用,因为是创建了另一个叫做myFunctionPtr的函数而不是修改它。

Example 6B:

function myFunction()
{
alert("Old");
}
var savedFunc=myFunction;
savedFunc=function()
{
alert("New");
};
myFunction(); // 输出 "Old"
savedFunc(); // 输出 "New"

内嵌函数

我们还能够在一个函数中嵌套一个函数。下例,我有一个叫做getHalfOf的函数,而在它里面,我有另一个叫做calculate的函数。

Example 7

function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}

var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"

你只能在内部调用嵌套的函数。就是说,你不能这么调用:getHalfOf.calculate(10),因为calculate只有当外部函数 (getHalfOf())在运行的时候才会存在。这和我们前面的讨论一致(函数会被编译,但只有当你去调用它的时候才会执行)。

调用哪个函数?

你也许正在想命名冲突的问题。比如,下面哪一个叫做calculate的函数会被调用?

Example 8

function calculate(number)
{
return number/3;
}

function getHalfOf(num1, num2, num3)
{
function calculate(number)
{
return number/2;
}

var result="";
result+=calculate(num1)+" ";
result+=calculate(num2)+" ";
result+=calculate(num3);
}
var resultString=getHalfOf(10,20,30);
alert(resultString); // 输出 "5 10 15"

在这个例子中,编译器会首先搜索局部内存地址,所以它会使用内嵌的calculate函数。如果我们删除了这个内嵌(局部)的calculate函数,这个代码会使用全局的calculate函数。

函数:数据类型及构造函数

让 我们来看看函数的另一个特殊功能--这让它和其它对象类型截然不同。一个函数能够用来作为一个数据类型的蓝图。这个特性通常被用在面向对象编程中来模拟用 户自定义数据类型(user defined data type)。使用用户自定义数据类型创建的对象通常被成为用户自定义对象(user defined object)。

数据类型

在定义了一个函数之后,我们也同时创建了一个新的数据类型。这个数据类型能够用来创建一个新对象。下例,我创建了一个叫做Ball的新数据类型。

Example DT1

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向一个新对象

alert(ball0); // 输出 "Object",因为 ball0 现在是一个对象

这样看来,ball0=new Ball()作了什么?new关键字创建了一个类型是Object的新对象(叫做ball0)。然后它会执行Ball(),并将这个引用传给ball0 (用于调用对象)。下面,你会看到这条消息:“creating new Ball”,如果Ball()实际上被运行的话。

Example DT2

function Ball(message)
{
alert(message);
}
var ball0=new Ball("creating new Ball"); // 创建对象并输出消息
ball0.name="ball-0"; // ball0现在有一个属性:name
alert(ball0.name); // 输出 "ball-0"

我们可以把上面这段代码的第6行看做是底下的代码6-8行的一个简写:

function Ball(message)
{
alert(message);
}
var ball0=new Object();
ball0.construct=Ball;
ball0.construct("creating new ball"); // 执行 ball0.Ball("creating..");
ball0.name="ball-0";
alert(ball0.name);

这行代码ball0.construct=Ball和Example 4中的ptr=myFunction语法一致。

如果你还是不明白这行的含义那就回过头再复习一下Example 4。注意:你也许考虑直接运行ball0.Ball("…"),但是它不会起作用的,因为ball0并没有一个叫做Ball("…")的属性,并且它也不知道你究竟想作些什么。

添加属性

当 我们象上面那样使用关键字new创建一个对象的时候,一个新的Object被创建了。我们可以在创建之后给这个对象添加属性(就好像我在上面那样添加属性 name。而接下来的问题就是如果我们创建了这个对象的另外一个实例,我们得象下面那样再次给这个新对象添加这个属性。)

Example DT3 (creates 3 ball objects)

function Ball()
{
}
var ball0=new Ball(); // ball0 现在指向了类型Ball的一个新实例
ball0.name="ball-0"; // ball0 现在有一个属性"name"

var ball1=new Ball();
ball1.name="ball-1";

var ball2=new Ball();

alert(ball0.name); // 输出 "ball-0"
alert(ball1.name); // 输出 "ball-1"
alert(ball2.name); // 哦,我忘记给ball2添加“name”了!

我忘记给ball2添加属性name了,如果在正式的程序中这也许会引发问题。有什么好办法可以自动增加属性呢?嗯,有一个:使用this关键 字。this这个词在function中有特别的意义。它指向了调用函数的那个对象。让我们看看下面的另一个示例,这时候我们在构造函数中添加上这些属 性:

Example DT4

function Ball(message, specifiedName)
{
alert(message);
this.name=specifiedName;
}
var ball0=new Ball("creating new Ball", "Soccer Ball");
alert(ball0.name); // prints "Soccer Ball"

请记住:是new关键字最终使得构造函数被执行。在这个例子中,它将会运行Ball("creating new Ball", "Soccer Ball");而关键字this将指向ball0。
因此,这行:this.name=specifiedName变成了ball0.name="Soccer Ball"。
它主要是说:给ball0添加属性name,属性值是Soccer Ball。
我 们现在只是添加了一个name属性给ball0,看起来和上一个例子中所做的很象,但却是一个更好更具扩展性的方法。现在,我们可以随心所欲的创建许多带 有属性的ball而无需我们手动添加它们。而且,人们也希望创建的Ball对象能够清晰的看懂它的构造函数并且能够轻松找出Ball的所有属性。让我们添 加更多属性到Ball里。

Example DT5

function Ball(color, specifiedName, owner, weight)
{
this.name=specifiedName;
this.color=color;
this.owner=owner;
this.weight=weigth;
}
var ball0=new Ball("black/white", "Soccer Ball", "John", 20);
var ball1=new Ball("gray", "Bowling Ball", "John", 30);
var ball2=new Ball("yellow", "Golf Ball", "John", 55);
var balloon=new Ball("red", "Balloon", "Pete", 10);

alert(ball0.name); // 输出 "Soccer Ball"
alert(balloon.name); // 输出 "Balloon"
alert(ball2.weight); // 输出 "55"

嘿!使用面向对象术语,你能够说Ball是一个拥有如下属性的对象类型:name, color, owner, weight。

将对象赋给属性

我们并没被限制只能添加形如字符串或者数字之类的简单数据类型作为属性。我们也能够将对象赋给属性。下面,supervisor是Employee的一个属性.

Example DT6

function Employee(name, salary, mySupervisor)
{
this.name=name;
this.salary=salary;
this.supervisor=mySupervisor;
}
var boss=new Employee("John", 200);

var manager=new Employee("Joan", 50, boss);
var teamLeader=new Employee("Rose", 50, boss);

alert(manager.supervisor.name+" is the supervisor of "+manager.name);
alert(manager.name+"’s supervisor is "+manager.supervisor.name);

会输出什么呢?

就像你在上面这个例子中看到的那样,manager和teamLeader都有一个supervisor属性,而这个属性是类型Employee的一个对象。

将函数作为属性

任何类型的对象都可以作为一个属性,回忆一下前面的Example 4(不是Example DT4),函数也是一个对象。所以你可以让一个函数作为一个对象的一个属性。下面,我将添加两个函数getSalary和addSalary。

Example DT7

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;

this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

var boss=new Employee("John", 200000);
boss.addSalary(10000); // boss 长了 10K 工资……为什么老板工资可以长这么多:'(
alert(boss.getSalary()); // 输出 210K……为什么默认工资也那么高……:'(

addSalary和getSalary演示了几种将函数赋给属性的不同方法。如果你记得我们最开始的讨论;我讨论了三种声明函数的不同方式。所有那些在这里都是适用的,但是上面展示的两个最常用。

让我们看看有什么不同。下面,注意一下9-12行的代码。当这部分代码执行的时候,函数getSalary被声明。如前面数次提到的,一个函数声明的结果是一个对象被创建。所以这时候boss被创建(接下来的第19行),而boss里有一个getSalary属性。

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;

this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

var boss=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);
var boss3=new Employee("Kim", 200000);

当你创建这个对象的更多实例时(boss2和boss3),每一个实例都有一份getSalary代码的单独拷贝;而与此相反,addSalary则指向了同一个地方(即addSalaryFunction)。

http://www.blueidea.com/articleimg/2006/07/3823/04.gif

看看下面的代码来理解一下上面所描述的内容。

Example DT8

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;
this.getSalary=function()
{
return this.salary;
};
}
function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

var boss1=new Employee("John", 200000);
var boss2=new Employee("Joan", 200000);

// 给getSalary函数对象添加属性
boss1.getSalary.owner="boss1";
boss2.getSalary.owner="boss2";
alert(boss1.getSalary.owner); // 输出 "boss1"
alert(boss2.getSalary.owner); // 输出 "boss2"
// 如果两个对象指向同一个函数对象,那么
// 上面两个输出都应该是“boss2”。

// 给addSalary函数对象添加属性
boss1.addSalary.owner="boss1";
boss1.addSalary.owner="boss2";
alert(boss1.addSalary.owner); // 输出 "boss2"
alert(boss2.addSalary.owner); // 输出 "boss2"
// 因为两个对象都指向同一个函数,(子乌注:原文写are not pointing to the same function,疑为笔误)
// 当修改其中一个的时候,会影响所有的实例(所以两个都输出“boss2”).

也许不是重要的事情,但这里有一些关于运行类似上面的getSalary的内嵌函数的结论: 1) 需要更多的存储空间来存储对象(因为每一个对象实例都会有它自己的getSalary代码拷贝);2) javascript需要更多时间来构造这个对象。

让我们重新写这个示例来让它更有效率些。

Example DT9

function Employee(name, salary)
{
this.name=name;
this.salary=salary;

this.addSalary=addSalaryFunction;
this.getSalary=getSalaryFunction;
}
function getSalaryFunction()
{
return this.salary;
}

function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

看这儿,两个函数都指向同一个地方,这将会节约空间和缩短构造时间(特别是当你有一大堆内嵌函数在一个构造函数的时候)。这里有另外一个函数的功能能够来提升这个设计,它叫做prototype,而我们将在下一节讨论它。

函数:原型

每一个构造函数都有一个属性叫做原型(prototype,下面都不再翻译,使用其原文)。这个属性非常有用:为一个特定类声明通用的变量或者函数。

prototype的定义

你不需要显式地声明一个prototype属性,因为在每一个构造函数中都有它的存在。你可以看看下面的例子:

Example PT1

function Test()
{
}
alert(Test.prototype); // 输出 "Object"

给prototype添加属性

就如你在上面所看到的,prototype是一个对象,因此,你能够给它添加属性。你添加给prototype的属性将会成为使用这个构造函数创建的对象的通用属性。

例如,我下面有一个数据类型Fish,我想让所有的鱼都有这些属性:livesIn="water"和price=20;为了实现这个,我可以给构造函数Fish的prototype添加那些属性。

Example PT2

function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20;

接下来让我们作几条鱼:

var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white");

再来看看鱼都有哪些属性:

for (int i=1; i<=3; i++)
{
var fish=eval("fish"+i); // 我只是取得指向这条鱼的指针
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
}

输出应该是:

"mackarel, gray, water, 20"
"goldfish, orange, water, 20"
"salmon, white water, 20"

你看到所有的鱼都有属性livesIn和price,我们甚至都没有为每一条不同的鱼特别声明这些属性。这时因为当一个对象被创建时,这个构造函 数将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。

你也可以通过prototype来给所有对象添加共用的函数。这有一个好处:你不需要每次在构造一个对象的时候创建并初始化这个函数。为了解释这一点,让我们重新来看Example DT9并使用prototype来重写它:

用prototype给对象添加函数

Example PT3

function Employee(name, salary)
{
this.name=name;
this.salary=salary;
}
Employee.prototype.getSalary=function getSalaryFunction()
{
return this.salary;
}

Employee.prototype.addSalary=function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

我们可以象通常那样创建对象:

var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000);

并验证它:

alert(boss1.getSalary()); // 输出 200000
alert(boss2.getSalary()); // 输出 100000
alert(boss3.getSalary()); // 输出 150000

这里有一个图示来说明prototype是如何工作的。这个对象的每一个实例(boss1, boss2, boss3)都有一个内部属性叫做__proto__,这个属性指向了它的构造器(Employee)的属性prototype。当你执行 getSalary或者addSalary的时候,这个对象会在它的__proto__找到并执行这个代码。注意这点:这里并没有代码的复制(和 Example DT8的图表作一下对比)。

http://www.blueidea.com/articleimg/2006/07/3823/05.gif

Read: 941

【转】为什么要 SET NAMES

大家都知道
SET NAMES x
相当于
SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;

以下从MySQL5.0官方文档上摘录了相关内容,并翻译,说明了相关系统变量的用处:

What character set is the statement in when it leaves the client?
statement离开客户端的时候是什么字符集?

The server takes the character_set_client system variable to be the character set in which statements are sent by the client.
客户端送过来的statement,服务器认为它的字符集是系统变量character_set_client的值。

What character set should the server translate a statement to after receiving it?
服务器收到一个statement后,会把它转换成什么字符集?

For this, the server uses the character_set_connection and collation_connection system variables. It converts statements sent by the client from character_set_client to character_set_connection (except for string literals that have an introducer such as _latin1 or _utf8). collation_connection is important for comparisons of literal strings. For comparisons of strings with column values, collation_connection does not matter because columns have their own collation, which has a higher collation precedence.
为了这一用途,服务器使用系统变量character_set_connection和collation_connection。它把客户端传来的 statement,从character_set_client字符集转换成character_set_connection字符集(除非字符串中有 类似_latin1或者_utf8的字符集声明)。collation_connection对于字符串的比较是非常重要的。对于字符类型的字段值的比 较,collation_connection是不起作用的。因为字段有自己的collation,有更高的优先级。

What character set should the server translate to before shipping result sets or error messages back to the client?
在结果集由服务器传递给客户端之前,需要转换成什么字符集?

The character_set_results system variable indicates the character set in which the server returns query results to the client. This includes result data such as column values, and result metadata such as column names.
character_set_results系统变量表明了服务器返回查询结果时使用的字符集。返回的数据,有比如字段的值和元数据(例如字段名)。

If you are using the mysql client with auto-reconnect enabled (which is not recommended), it is preferable to use the charset command rather than SET NAMES. For example:
如果你使用mysql客户端的自动重连(不推荐使用),最好用charset命令,而不是SET NAMES。例如:

mysql> charset utf8
Charset changed

The charset command issues a SET NAMES statement, and also changes the default character set that is used if mysql reconnects after the connection has dropped.
charset命令发出了一个SET NAMES语句,并且连接断开后自动重连时使用的缺省字符集也被修改了。

The database character set and collation are used as default values if the table character set and collation are not specified in CREATE TABLE statements. They have no other purpose.
如果CREATE TABLE中没有明确指出字符集和collation,那么database字符集和collation将做为缺省值,它们没有其他的用处。

The character set and collation for the default database can be determined from the values of the character_set_database and collation_database system variables. The server sets these variables whenever the default database changes. If there is no default database, the variables have the same value as the corresponding server-level system variables, character_set_server and collation_server.
缺省database的字符集和collation可以通过系统变量character_set_database和 collation_database查看。服务器当缺省database改变时设置这些变量的值。如果没有缺省的database,这些变量的将与对应 的服务器级的系统变量-character_set_server和collation_server的值相同。

The table character set and collation are used as default values if the column character set and collation are not specified in individual column definitions. The table character set and collation are MySQL extensions; there are no such things in standard SQL.
表的字符集和collation会作为缺省值,如果列的定义中没有指明字符集和collation的话。

Every “character” column (that is, a column of type CHAR, VARCHAR, or TEXT) has a column character set and a column collation.
每个“字符”字段(即CHAR、VARCHAR或者TEXT类型的字段)都有一个字段字符集和字段校验(collation)。

Every character string literal has a character set and a collation.
每个字符串有一个字符集和一个较验。

A character string literal may have an optional character set introducer and COLLATE clause:
每个字符串有一个可选的字符集introducer和COLLATE子句:

[_charset_name]’string’ [COLLATE collation_name]

Examples:

SELECT ‘string’;
SELECT _latin1’string’;
SELECT _latin1’string’ COLLATE latin1_danish_ci;

Read: 39