image.jsp-------------------生成隨即驗證碼圖片的jsp頁面
代碼如下:
<%@ page contentType="image/jpeg" import="java.awt.*,
java.awt.image.*,java.util.*,javax.imageio.*" %>
<%!
Color getRandColor(int fc,int bc)
{
?Random random = new Random();
?if(fc>255) fc=255;
?if(bc>255) bc=255;
?int r=fc+random.nextInt(bc-fc);
?int g=fc+random.nextInt(bc-fc);
?int b=fc+random.nextInt(bc-fc);
?return new Color(r,g,b);
}
%>
<%
out.clear();//這句針對resin服務器,如果是tomacat可以不要這句
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
int width=60, height=20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200,250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman",Font.PLAIN,18));
?
g.setColor(getRandColor(160,200));
for (int i=0;i<155;i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x,y,x+xl,y+yl);
}
String sRand="";
for (int i=0;i<4;i++){
String rand=String.valueOf(random.nextInt(10));
sRand+=rand;
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
g.drawString(rand,13*i+6,16);
}
// 將認證碼存入SESSION
session.setAttribute("rand",sRand);
g.dispose();
ImageIO.write(image, "JPEG", response.getOutputStream());
%>
login.jsp--------------------登錄頁面,在這里我是提供輸入驗證碼然后提交進行驗證
代碼如下:
<%@ page language="java" import="java.sql.*" errorPage="" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>用戶登錄</title>
<script language="javascript">
? function loadimage(){
??? document.getElementById("randImage").src = "image.jsp?"+Math.random();
? }
? </script>
</head>
<body>
<table width="256" border="0" cellpadding="0" cellspacing="0">
? <!--DWLayoutTable-->
? <form action="validate.jsp" method="post" name="loginForm">
? <tr>
??? <td width="118" height="22" valign="middle" align="center"><input type="text" name="rand" size="15"></td>
??? <td width="138" valign="middle" align="center"><img alt="code..." name="randImage" id="randImage" src="image.jsp" width="60" height="20" border="1" align="absmiddle"></td>
? </tr>
? <tr>
??? <td height="36" colspan="2" align="center" valign="middle"><a href="javascript:loadimage();"><font class=pt95>看不清點我</font></a></td>
? </tr>
? <tr>
??? <td height="36" colspan="2" align="center" valign="middle"><input type="submit" name="login" value="提交"></td>
? </tr>
? </form>
</table>
</body>
</html>
validate.jsp-------------用來驗證輸入的驗證碼是否正確
代碼如下:
<%@ page contentType="text/html; charset=gb2312" language="java" import="java.sql.*" errorPage="" %>
<%
String rand = (String)session.getAttribute("rand");
String input = request.getParameter("rand");
if(rand.equals(input)){
?out.print("<script>alert('驗證通過!');</script>");
} else{
?out.print("<script>alert('請輸入正確的驗證碼!');location.href='login.jsp';</script>");
}
%>
上面生成的是純數字的驗證碼,我們對其進行一下修改就可以生成數字字母混合的驗證碼了。
修改部分的代碼如下:
<%
......
......
......
for (int i=0;i<155;i++)
{
? ......
? ......
}
//-----------------以下是要添加的代碼--------------
char c[] = new char[62];
for (int i = 97, j = 0; i < 123; i++, j++) {
c[j] = (char) i;
}
for (int o = 65, p = 26; o < 91; o++, p++) {
c[p] = (char) o;
}
for (int m = 48, n = 52; m < 58; m++, n++) {
c[n] = (char) m;
}???????
String sRand="";
for (int i=0;i<4;i++){
int x = random.nextInt(62);
String rand=String.valueOf(c[x]);
sRand+=rand;
//---------------------------------------------
%>
據有部分朋友反應,在測試過程中出現java.lang.IllegalStateException: getOutputStream() has already been called for this response的異常,經過測試發現,使用tomcat5一下的版本確實會出現該問題,解決的辦法就是把tomcat換成高點版本就可以了。
作者:Cloves Carneiro;
simmone
原文地址:
http://www.javaworld.com/javaworld/jw-06-2005/jw-0620-dwr.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43926_DWR_AJAX.html
關鍵詞: DWR AJAX
概述
這篇文章闡述了使用開源項目DWR(直接Web遠程控制)和AJAX(異步JavaScript和XML)的概念來提高Web應用的可用性。作者一步步來展示DWR如何使得AJAX的應用既簡單又快捷。(1600字;2005年6月20日)
AJAX,或者說是異步JavaScript和XML,描述了一種使用混合了HTML(或XHTML)和層疊樣式表作為表達信息,來創建交互式的Web應用的開發技術;文檔對象模型(DOM),JavaScript,動態地顯示和與表達信息進行交互;并且,XMLHttpRequest對象與Web服務器異步地交換和處理數據。
因特網上許多例子展示了在一個HTML文件內部使用XMLHttpRequest與服務器端進行交互的必要的步驟。當手工地編寫和維護XMLHttpRequest代碼時,開發者必須處理許多潛在的問題,特別是類似于跨瀏覽器的DOM實現的兼容性這樣的問題。這將會導致在編碼和調試Javascript代碼上面花費數不清的時間,這顯然對開發者來說很不友好。
DWR(直接Web遠程控制)項目是在Apache許可下的一個開源的解決方案,它供給那些想要以一種簡單的方式使用AJAX和XMLHttpRequest的開發者。它具有一套Javascript功能集,它們把從HTML頁面調用應用服務器上的Java對象的方法簡化了。它操控不同類型的參數,并同時保持了HTML代碼的可讀性。
DWR不是對一個設計的插入,也不強迫對象使用任何種類的繼承結構。它和servlet框架內的應用配合的很好。對缺少DHTML編程經驗的開發者來說,DWR也提供了一個JavaScript庫包含了經常使用的DHTML任務,如組裝表,用item填充select下拉框,改變HTML元素的內容,如<div>和<span>
DWR網站是詳盡的并且有大量的文檔,這也是這篇文章的基礎。一些例子用來展示DWR如何使用和用它的庫可以完成什么樣的工作。
這篇文章讓讀者看到了一個使用了DWR的Web應用是如何一步步建立的。我會展示創建這個簡單的示例應用的必要的細節,這個應用是可下載的并且可以在你的環境中布署來看看DWR如何工作。
注意:找到有關AJAX的信息并不困難;網頁上有幾篇文章和博客的條目涵蓋了這個主題,每一個都試圖指出和評論這個概念的不同的方面。在資源部分,你會找到一些有趣的指向示例和文章的鏈接,來學習AJAX的更多的內容。
示例應用
這篇文章使用的示例應用模擬了多倫多的一個公寓出租搜索引擎。用戶可以在搜索前選擇一組搜索標準。為了提高交互性,AJAX中以下兩種情況下使用:
·應用通告用戶配合他的選擇會返回多少搜索結果。這個數字是實時更新的-使用AJAX-當用戶選擇的臥室和浴室的數量,或者價格范圍變化時。當符合標準的搜索結果沒有或太多時,用戶就沒有必要點擊搜索按紐。
·數據庫查詢并取回結果是由AJAX完成的。當用戶按下顯示結果按鈕時,數據庫執行搜索。這樣,應用看起來更具響應了,而整個頁面不需要重載來顯示結果。
數據庫
我們使用的數據庫是HSQL,它是一種占用資源很小的Java SQL數據庫引擎,可以不需要安裝和配置的與Web應用捆綁在一起。一個SQL文件被用來在Web應用的上下文啟動時創建一個內存中的表并添加一些記錄。
Java類
應用包含了兩個主要的類叫Apartment和ApartmentDAO。Apartment.java類是一個有著屬性和getter/setter方法的簡單的Java類。ApartmentDAO.java是數據訪問類,用來查詢數據庫并基于用戶的搜索標準來返回信息。ApartmentDAO類的實現的直接了當的;它直接使用了Java數據庫聯接調用來得到公寓的總數和符合用戶請求的可用公寓的列表。
DWR配置和使用
設置DWR的使用是簡單的:將DWR的jar文件拷入Web應用的WEB-INF/lib目錄中,在web.xml中增加一個servlet聲明,并創建DWR的配置文件。DWR的分發中需要使用一個單獨的jar文件。你必須將DWR servlet加到應用的WEB-INF/web.xml中布署描述段中去。
????<servlet>
????????<servlet-name>dwr-invoker</servlet-name>
????????<display-name>DWR Servlet</display-name>
????????<description>Direct Web Remoter Servlet</description>
????????<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
????????<init-param>
????????????<param-name>debug</param-name>
????????????<param-value>true</param-value>
????????</init-param>
????</servlet>
????<servlet-mapping>
????????<servlet-name>dwr-invoker</servlet-name>
????????<url-pattern>/dwr/*</url-pattern>
????</servlet-mapping>
一個可選的步驟是設置DWR為調試模式—象上面的例子那樣—在servlet描述段中將debug參數設為true。當DWR在調試模式時,你可以從HTMl網頁中看到所有的可訪問的Java對象。包含了可用對象列表的網頁會出現在/WEBAPP/dwr這個url上,它顯示了對象的公共方法。所列方法可以從頁面中調用,允許你,第一次,運行服務器上的對象的方法。下圖顯示了調試頁的樣子:

調試頁
現在你必須讓DWR知道通過XMLHttpRequest對象,什么對象將會接收請求。這個任務由叫做dwr.xml的配置文件來完成。在配置文件中,定義了DWR允許你從網頁中調用的對象。從設計上講,DWR允許訪問所有公布類的公共方法,但在我們的例子中,我們只允許訪問幾個方法。下面是我們示例的配置文件:
<dwr>
????<allow>
????????<convert converter="bean" match="dwr.sample.Apartment"/>
????????<create creator="new" javascript="ApartmentDAO" class="dwr.sample.ApartmentDAO">
????????????<include method="findApartments"/>
????????????<include method="countApartments"/>
????????</create>
????</allow>
</dwr>
上面的文件實現了我們例子中的兩個目標。首先,<convert>標記告訴DWR將dwr.sample.Apartment對象的類型轉換為聯合數組,因為,出于安全的原因,DWR默認的不會轉換普通bean。第二,<create>標記讓DWR暴露出dwr.sample.ApartmentDAO類給JavaScript調用;我們在頁面中使用JavaScript文件被javascript屬性定義。我們必須注意<include>標記,它指明了dwr.sample.ApartmentDAO類的哪些方法可用。
HTML/JSP代碼
配置完成后,你就可以啟動你的Web應用了,這時DWR會為從你的HTML或Java服務器端頁面(JSP)上調用所需方法作好準備,并不需要你創建JavaScript文件。在search.jsp文件中, 我們必須增加由DWR提供的JavaScript接口,還有DWR引擎,加入以下三行到我們的代碼中:
??<script src='dwr/interface/ApartmentDAO.js'></script>
??<script src='dwr/engine.js'></script>
??<script src='dwr/util.js'></script>
我們注意到當用戶改變搜索標準時,這是AJAX在示例程序中的首次應用;正如他所看到的,當標準改變時,可用的公寓數量被更新了。我創建了兩個JavaScript函數:當某一個選擇下拉框中的值變化時被調用。ApartmentDAO.countApartments()函數是最重要的部分。最有趣的是第一個參數, loadTotal()函數,它指明了當接收到服務端的返回時DWR將會調用的JavaScript方法。loadTotal于是被調用來在HTML頁面的<div>中顯示結果。下面是在這個交互場景中所使用到的JavaScript函數:
function updateTotal() {
????$("resultTable").style.display = 'none';
????var bedrooms = document.getElementById("bedrooms").value;
????var bathrooms = document.getElementById("bathrooms").value;
????var price = document.getElementById("price").value;
????ApartmentDAO.countApartments(loadTotal, bedrooms, bathrooms, price);
}
function loadTotal(data) {
????document.getElementById("totalRecords").innerHTML = data;
}
很明顯,用戶想看到符合他的搜索條件的公寓列表。那么,當用戶對他的搜索標準感到滿意,并且總數也是有效的話,他會按下顯示結果的按紐,這將會調用updateResults() JavaScript方法:
function updateResults() {
????
????DWRUtil.removeAllRows("apartmentsbody");
????var bedrooms = document.getElementById("bedrooms").value;
????var bathrooms = document.getElementById("bathrooms").value;
????var price = document.getElementById("price").value;
????ApartmentDAO.findApartments(fillTable, bedrooms, bathrooms, price);
????$("resultTable").style.display = '';
}
function fillTable(apartment) {
????DWRUtil.addRows("apartmentsbody", apartment, [ getId, getAddress, getBedrooms, getBathrooms, getPrice ]);
}
updateResults()方法清空了存放搜索返回結果的表域,從用戶界面上獲取所需參數,并且將這些參數傳給DWR創建的ApartmentDAO對象。然后數據庫查詢將被執行,fillTable()將會被調用,它解析了DWR返回的對象(apartment),然后將其顯示到頁面中(apartmentsbody)。
安全因素
為了保持示例的簡要,ApartmentDAO類盡可能的保持簡單,但這樣的一個類通常有一組設置方法來操作數據,如insert(), update()和delete()。DWR暴露了所有公共方法給所有的HTML頁面調用。出于安全的原因,像這樣暴露你的數據訪問層是不明智的。開發者可以創建一個門面來集中所有JavaScript函數與底層業務組件之間的通信,這樣就限制了過多暴露的功能。
結論
這篇文章僅僅讓你在你的項目中使用由DWR支持的AJAX開了個頭。DWR讓你集中注意力在如何提高你的應用的交互模型上面,消除了編寫和調試JavaScript代碼的負擔。使用AJAX最有趣的挑戰是定義在哪里和如何提高可用性。DWR負責了操作Web頁面與你的Java對象之間的通信,這樣就幫助你完全集中注意力在如何讓你的應用的用戶界面更加友好,
我想感謝Mircea Oancea和Marcos Pereira,他們閱讀了這篇文章并給予了非常有價值的返匱。
資源
·javaworld.com:
javaworld.com
·Matrix-Java開發者社區:
http://www.matrix.org.cn/
·onjava.com:
onjava.com
·下載示例程序的全部源碼:
http://www.javaworld.com/javaworld/jw-06-2005/dwr/jw-0620-dwr.war
·DWR: http://www.getahead.ltd.uk/dwr/index.html
·HSQL:http://hsqldb.sourceforge.net/
·AJAX的定義:http://en.wikipedia.org/wiki/AJAX
· “AJAX:通向Web應用的新途徑": Jesse James Garrett (Adaptive Path, 2005.2): http://www.adaptivepath.com/publications/essays/archives/000385.php
· “非常動態的Web界面” Drew McLellan (xml.com, 2005.2): http://www.xml.com/pub/a/2005/02/09/xml-http-request.html
·XMLHttpRequest & AJAX 工作范例: http://www.fiftyfoureleven.com/resources/programming/xmlhttprequest/examples
· “可用的XMLHttpRequest實踐” Thomas Baekdal (Baekdal.com, 2005.3): http://www.baekdal.com/articles/Usability/usable-XMLHttpRequest/
·"XMLHttpRequest使用導引" Thomas Baekdal (Baekdal.com,??2005.2):http://www.baekdal.com/articles/Usability/XMLHttpRequest-guidelines/
·AJAX實質:http://www.ajaxmatters.com/
(看完后個人感覺:有了DWR就JAVA開發而言,完全可以與AJAX匹敵啦,省了在JS上對XMLHTTP以及對DOM的處理,不可以避免地在后臺對應的IO處理;然后就DWR來說,它增加了對XML中對應的配置--在開源框架中似乎一直不曾停止過。還有對一些DWR自有用法如DWRUtil.addRows得參考其相關文檔---當然這樣的功能我們自己也可以用JS來解決,并且它顯然很實用。add by jkallen)
摘要: 制作環境:
?
Windows 2003 + IIS6、jre1.5.0_06、apache-tomcat-5.5.17
?
首先需要做以下準備工作
...
閱讀全文
文件上傳在web應用中非常普遍,要在servlet/jsp環境中實現文件上傳功能非常容易,因為網上已經有許多用java開發的組件用于文件上傳,本文以commons-fileupload組件為例,為servlet/jsp應用添加文件上傳功能。
common-fileupload組件是apache的一個開源項目之一,可以從
http://jakarta.apache.org/commons/fileupload/
下載。該組件簡單易用,可實現一次上傳一個或多個文件,并可限制文件大小。
下載后解壓zip包,將commons-fileupload-1.0.jar復制到tomcat的webapps\你的webapp\WEB-INF\lib\下,如果目錄不存在請自建目錄。
新建一個servlet: Upload.java用于文件上傳:
?1?import?java.io.*
;
?2?import?java.util.*
;
?3?import?javax.servlet.*
;
?4?import?javax.servlet.http.*
;
?5?import?org.apache.commons.fileupload.*
;
?6?
?7?public?class?Upload?extends
?HttpServlet?{
?8?
?9?????private?String?uploadPath?=?"C:\\upload\\";?//?用于存放上傳文件的目錄
10?????private?String?tempPath?=?"C:\\upload\\tmp\\";?//?用于存放臨時文件的目錄
11?
12?????public?void
?doPost(HttpServletRequest?request,?HttpServletResponse?response)
13?????????throws
?IOException,?ServletException
14?
????{
15?
????}
16?
}
17?
18?//當servlet收到瀏覽器發出的Post請求后,在doPost()方法中實現文件上傳。以下是示例代碼:
19?
20?public?void
?doPost(HttpServletRequest?request,?HttpServletResponse?response)
21?????throws
?IOException,?ServletException
22?
{
23?????try
?{
24?????????DiskFileUpload?fu?=?new
?DiskFileUpload();
25?????????//?設置最大文件尺寸,這里是4MB
26?????????fu.setSizeMax(4194304
);
27?????????//?設置緩沖區大小,這里是4kb
28?????????fu.setSizeThreshold(4096
);
29?????????//?設置臨時目錄:
30?
????????fu.setRepositoryPath(tempPath);
31?
32?????????//?得到所有的文件:
33?????????List?fileItems?=
?fu.parseRequest(request);
34?????????Iterator?i?=
?fileItems.iterator();
35?????????//?依次處理每一個文件:
36?????????while
(i.hasNext())?{
37?????????????FileItem?fi?=
?(FileItem)i.next();
38?????????????//?獲得文件名,這個文件名包括路徑:
39?????????????String?fileName?=
?fi.getName();
40?????????????if(fileName!=null
)?{
41?????????????????//
?在這里可以記錄用戶和文件信息
42?????????????????//
?
43?????????????????//?寫入文件a.txt,你也可以從fileName中提取文件名:
44?????????????????fi.write(new?File(uploadPath?+?"a.txt"
));
45?
????????????}
46?
????????}
47?????????//?跳轉到上傳成功提示頁面
48?
????}
49?????catch
(Exception?e)?{
50?????????//?可以跳轉出錯頁面
51?
????}
52?
}
53?
54?//如果要在配置文件中讀取指定的上傳文件夾,可以在init()方法中執行:
55?
56?public?void?init()?throws
?ServletException?{
57?????uploadPath?=
?
.
58?????tempPath?=
?
.
59?????//?文件夾不存在就自動創建:
60?????if(!new
?File(uploadPath).isDirectory())
61?????????new
?File(uploadPath).mkdirs();
62?????if(!new
?File(tempPath).isDirectory())
63?????????new
?File(tempPath).mkdirs();
64?
}
65?
編譯該servlet,注意要指定classpath,確保包含commons-upload-1.0.jar和tomcat\common\lib\servlet-api.jar。
配置servlet,用記事本打開tomcat\webapps\你的webapp\WEB-INF\web.xml,沒有的話新建一個。典型配置如下:
?1?<?xml?version="1.0"?encoding="ISO-8859-1"?>
?2?<!
DOCTYPE?web-app
?3?
????PUBLIC?"-//Sun?Microsystems,?Inc.//DTD?Web?Application?2.3//EN"
?4?????"http://java.sun.com/dtd/web-app_2_3.dtd">
?5?
?6?<web-app>
?7?????<servlet>
?8?????????<servlet-name>Upload</servlet-name>
?9?????????<servlet-class>Upload</servlet-class>
10?????</servlet>
11?
12?????<servlet-mapping>
13?????????<servlet-name>Upload</servlet-name>
14?????????<url-pattern>/fileupload</url-pattern>
15?????</servlet-mapping>
16?</web-app>
17?
配置好servlet后,啟動tomcat,寫一個簡單的html測試:
1?<form?action="fileupload"?method="post"
2?enctype="multipart/form-data"?name="form1">
3???<input?type="file"?name="file">
4???<input?type="submit"?name="Submit"?value="upload">
5?</form>
注意action="fileupload"其中fileupload是配置servlet時指定的url-pattern。
摘自:
http://www.j2medev.com/Article/Class10/j2eeopensource/200409/62.html
輸入輸出
在所有的本系列文章中,在編寫Ruby代碼時都使用了大量的Ruby標準輸出方法。其中,最為常用的是print和puts方法,有關其使用細節不再贅述。
所有這些和其它處理輸入和輸出的方法都定義于Kernel模塊中。這個Kernel模塊又被包含在Object類中。因此,Kernel的方法出現在每一個對象中。在輸出方面,Kernel定義了print,printf,putc和IO類和兩個子類(File和BasicSocket)-它們允許讀寫文件和套接字。BasicSocket是套接字庫的一部分并且將在以后討論它。包含了FileTest模塊的File類,提供了許多方法來操作系統文件和目錄。從Kernel中使用的用于讀寫到標準輸入/輸出機制的方法被進一步重用于File實例中的讀寫操作。下面是一個代碼示例-它把一些名字寫入一個新建的文件中,然后再把這些名字讀回到一個數組中。
customers=%w[Jim?Kevin?Davin?Andrew] outFile?=?File.new("c:\\examples\\test\\customers.txt",?"w") customers.each{|customer|?outFile.puts(customer)} outFile.close inFile=?File.new("c:\\examples\\customers.txt",?"r") readCustomers=inFile.readlines readCustomers.each{|customer|?puts?customer} inFile.close |
標準庫
Ruby除了提供大量內置的類和模塊外,它還提供了一定數目的標準庫。這些庫不是自動地成為你可以利用的Ruby類,模塊,方法的一部分。你必須先在你的文件的頂部使用require(或load)關鍵字來使用庫中的類或模塊。在前一節中,我曾提到一個庫-套接字庫,它包含了大量的Ruby類(包括BasicSocket),以便于訪問網絡服務。但是在Ruby下載中一同提供了一整套其它的庫。你可以查看一下你的Ruby下載中的lib目錄,這個目錄下應該存在大量的你的Ruby程序需要使用的庫。
有關這些庫的不好的一點是,沒有大量的有關于這些類的參考文檔。你可以在網站http://www.ruby-doc.org/stdlib/上找到一系列標準庫及其包含類和模塊的文件。即使是這些文檔也指出:
"你需要明白,在表格中粗體的庫具有良好的文檔,而斜體的庫沒有文檔。"
這就是Ruby的現狀。你可能想說,Ruby是一個不可思議地豐富而有力的語言并且內置了許多構建我們的應用程序需要的功能,但是文檔仍然有點不足。還好,現在已經有不少的人在努力改進Ruby的文檔和支持。現在有不少的Ruby論壇已經倔起,并且隨著每一個新版本的發行,文檔都將有一定的改進-當然,這也是最近它備受關注的結果。然而,幫助文檔仍然會成為這種語言挫敗人心的一個因素。
?在Ruby中,一切都是對象。更精確地說,Ruby中的一切都是一個具有完整功能的對象。因此,在Ruby中,數字4,定點數3.14和字符串"Hi"都是對象。顯然,它們是有點"特殊"的,因為你不必使用new方法來創建它們。代之的是,你使用例如"literal 4"這樣的形式來創建一個代表數字4的對象的實例。
然而,對于絕大多數人來說,學習一種新的編程語言時,首先理解該語言提供的"標準"類型是非常有用的。所以,在這一節,我們先探討數字類型,字符串類型,布爾類型和另外一些基本的Ruby數據類型。
數字類型
實質上,Ruby中的數字被分為整數和浮點數兩大類。其中,整數又被進一步細分為"常規大小"的整數和大型整數。因為在Ruby中一切都是對象,所以整數和浮點數都是按類來定義的(見圖1)。從圖1看出,Numeric是所有數字類型的基類,Float和Integer類是Numeric的子類。Fixnum和Bignum都是Integer的子類型-它們分別定義了"常規大小"的整數和大型整數。
圖1.Ruby的數字類型類繼承圖。
Literal用來描述這些類的實例。下面的在交互式Ruby外殼(irb)中的代碼顯示了Float,Fixnum和Bignum的literal實例。注意,可以在literal上進行方法調用(在此,是指類方法)。
irb(main):001:0>?3.class =>?Fixnum irb(main):002:0>?3.4.class =>?Float irb(main):003:0>?10000000000000000000.class =>?Bignum? |
還有另外一些語法用來創建數字類型,顯示于下面的代碼列表中。字母E可以用來描述以指數標志的數字。數字的前面加上0代表這是一個八進制數,加上0x代表這是一個十六進制數,而0b代表是一個二進制數。為清晰起見,下劃線可以用作數字中的分隔符號。注意,當寫literal時,不要用逗號作為分隔符號。在一些情況中,這實際上能生成一個數組,我們將在后面討論。最后,在一個字符(或Ctrl或元字符的組合)前面的一個問號將會創建一個Fixnum的實例,相應于字符的ASCII字符/逃逸序列值。
< irb(main):001:0>?3.14E5?#指數標志 =>?314000.0 irb(main):002:0>?054?#八進制 =>?44 irb(main):003:0>?0x5A?#十六進制 =>?90 irb(main):004:0>?0b1011?#二進制 =>?11 irb(main):005:0>?10_000?#10,000,用下劃線隔開 =>?10000 irb(main):006:0>?i=10,000?#創建一個數組而不是10000?Fixnum =>?[10,?0] irb(main):007:0>?i.class =>?Array irb(main):008:0>??Z?#Fixnum?ASCII值 =>?90 irb(main):009:0>??Z.class =>?Fixnum irb(main):010:0>??\C-s?#Control-s的值ASCII =>?19? |
Fixnum和Bignum實例間的真實差別是什么?Fixnum整數可以被存儲在機器中的一個字(通常16,32或64位)中,但減去1個位;而Bignum實例是超出固定存儲空間的整數。當然,作為開發者,你不必擔心整數的大小(見下面的例子),由Ruby負責為你實現Fixnum和Bignum之間的自動轉換!
irb(main):001:0>?i=4 =>?4 irb(main):002:0>?i.class =>?Fixnum irb(main):003:0>?i=i+100000000000000 =>?100000000000004 irb(main):004:0>?i.class =>?Bignum irb(main):005:0>?i=i-100000000000000 =>?4 irb(main):006:0>?i.class =>?Fixnum |
字符串 在Ruby中,字符串是任意順序的字節。通常,它們是一個字符序列。在Ruby中,可以使用一個literal或new方法來創建String類的實例。
irb(main):001:0>?s1="Hello?World" =>?"Hello?World" irb(main):002:0>?s2=String.new("Hello?World") =>?"Hello?World"? |
當然,String中定義了許多方法(和操作符)。另外,可以使用單引號或雙引號來指定一個字符串。雙引號情況下允許串中加入逃逸字符并能夠嵌入待計算的表達式。在單引號串情況下,你看到的就是串中的實際內容。為了更好的理解,請看下列例子。
irb(main):001:0>?str1='a?\n?string' =>?"a?\\n?string" irb(main):002:0>?str2="a?\n?string" =>?"a?\n?string" irb(main):003:0>?puts?str1 a?\n?string =>?nil irb(main):004:0>?puts?str2 a? string =>?nil irb(main):005:0>?'try?to?add?#{2+2}' =>?"try?to?add?\#{2+2}" irb(main):006:0>?"try?to?add?#{2+2}" =>?"try?to?add?4" irb(main):007:0>?this="that" =>?"that" irb(main):008:0>?'when?single?quote?rights?#{this}' =>?"when?single?quote?rights?\#{this}" irb(main):009:0>?"double?quote?rights?#{this}" =>?"double?quote?rights?that"? |
請注意,在顯示之前,雙引號中的文本是如何被計算的,其中包括了逃逸符號(\n)和表達式(#{2+2})。
除了使用單引號和雙引號來定義一個字符串literal外,在Ruby中,還有另外的方法可以表達literal。一個百分號和小寫或大寫字母Q可以用來表達一個字符串,分別相應于單引號或雙引號風格。
irb(main):001:0>?%q@this?is?a?single?quote?string?#{2+2}?here@ =>?"this?is?a?single?quote?string?\#{2+2}?here" irb(main):002:0>?%Q@this?is?a?double?quote?string?#{2+2}?here@ =>?"this?is?a?double?quote?string?4?here"? |
注意,跟隨在q%或Q%后面的字符分別定義了字符串literal的開始和結束。在本例中,@符號用作字符串開始與結束的限界符號。
還應該注意,Ruby并沒有區分一個字符串和一個字符。也就是說,沒有適用于單個字符的特定的類-它們僅是一些小的字符串。
????? 布爾類型
最后,讓我們再看一下布爾類型。在Ruby中,有兩個類用于表達布爾類型:TrueClass和FalseClass。每個這些類僅有一個實例(一個singleton):也就是true和false。這些是可在Ruby的任何地方存取的全局值。還有一個類NilClass。NilClass也僅有一個實例nil-表示什么也沒有。然而,在布爾邏輯中,nil是false的同義詞。
irb(main):001:0>?true|false =>?true irb(main):002:0>?true&false =>?false irb(main):003:0>?true|nil =>?true irb(main):004:0>?true&nil =>?false? |
正規表達式
大多數程序語言中都使用正規表達式。基于許多腳本語言的Ruby也廣泛地使用正規表達式。我的一個同事曾經說"正規表達式太復雜了。"換句話說,你需要花費一些時間來學習正規表達式。在本文中,你僅能一瞥Ruby正規表達式的威力。在程序開發中,你不必一定使用正規表達式,但是如果使用這種工具,你的編碼將更為緊湊而容易。而且,如果你想成為一名Ruby大師,你必須要花費其它時間來研究它。
在下面的例子中,Ruby中的正規表達式是在Tiger或菲Phil之間定義的。
現在你可以在一個條件或循環語句中使用帶有一個匹配操作符("=~")的正規表達式來匹配或查找其它的字符串。
irb(main):001:0>? golfer="Davis" if?golfer?=~?/Tiger|Phil/ puts?"This?is?going?to?be?a?long?drive." else puts?"And?now?a?drive?by?"?+?golfer end =>?"Davis"? |
下面是另一個稍微復雜些的正規表達式:
/[\w._%-]+@[\w.-]+.[a-zA-Z]{2,4}/ |
你能夠猜出這個表達式代表什么意思嗎?它相應于一個電子郵件地址。這個正規表達式可以用來校驗電子郵件地址。
irb(main):001:0>? emailRE=?/[\w._%-]+@[\w.-]+.[a-zA-Z]{2,4}/ email?=?"jwhite@interechtraining.com" if?email?=~?emailRE puts?"This?is?a?valid?email?address." else puts?"this?is?not?a?valid?email?address." end 這是一個有效的電子郵件地址。 irb(main):002:0> email?=?"###@spammer&&&.333" if?email?=~?emailRE puts?"This?is?a?valid?email?address." else puts?"this?is?not?a?valid?email?address." end |
?
這不是一個有效的電子郵件地址
圖2把電子郵件正規表達式分解開來。你已看到,正規表達式語言是相當豐富的,但在此不多詳述。有關正規表達式的更多信息請參考http://www.regular-expressions.info。

圖2.電子郵件正規表達式
注意,在Ruby中正規表達式也是一種對象。在下面的代碼示例中,一個正規表達式實例(派生自類Regexp)作為String方法的一個參數(gsub)以達到使用"glad"來替換和"happy"與"joy"之目的。
irb(main):001:0>? quote?=?"I?am?so?happy.?Happy,?happy,?joy,?joy!" regx?=?/(h|H)appy|joy/ quote.gsub(regx,?"glad") =>?"I?am?so?happy.?Happy,?happy,?joy,?joy!" =>?/(h|H)appy|joy/ =>?"I?am?so?glad.?glad,?glad,?glad,?glad!"? |
當你在正規表達式對象上使用=~操作符時,你能夠得到例如匹配模式串的索引等信息。
irb(main):001:0>?/Tiger|Phil/=~"EyeOfTheTiger" =>?8? |
如果你曾編寫過大量有關字符串的程序,你就會知道Ruby中的正規表達式是非常有力量的。因此,我建議在較深入地用Ruby開發你的第一個程序前,你應該全面地探討一下Ruby中的正規表達式。當然,你可以參考本文相應的源碼文件,其中包含了大量的正規表達式。
范圍
在Ruby中,一個很不平常但是非常有用的概念就是范圍(range)。一個范圍是一個值序列。例如,字符a到z就可以定義在英語字母表中的所有的小寫字母。另外一個范圍的例子是整數1到10。一個范圍能從任何類型的對象中創建,假定對象的類型允許使用Ruby的操作符(<=>)和succ方法進行比較。根據<=>操作符左邊的操作數是否小于,等于或大于<=>操作符右邊的操作數,<=>操作符將分別返回-1,0或+1。例如,"A"<=>"B"將返回-1。運行于整數4上的succ方法(4.succ)將返回5。
可以使用Range類的new方法或特殊的標志來創建一個范圍。下面是在irb中分別使用括號和點速記標志創建的兩個相同的范圍(表示所有的大寫字母)。
irb(main):001:0>?r1=Range.new("A","Z") =>?"A".."Z" irb(main):002:0>?r2=("A".."Z") =>?"A".."Z"? |
當創建一個范圍時,必須指定開始值和結束值。在上面的情況中,A作為開始值,Z作為結束值。當創建一個范圍時,你還可以指示是否這個范圍應該包括或不包括末尾元素。默認情況下,如上例所示,范圍包括末尾元素。為了排除結束元素,你可以使用new方法的排除參數(置為true)或如下所示的3個點的速記標志。
irb(main):001:0>?r1=Range.new("A","Z",true) =>?"A"..."Z" irb(main):002:0>?r2=("A"..."Z") =>?"A"..."Z" irb(main):003:0>?r1.include?"Z" =>?false irb(main):004:0>?r2.include?"Z" =>?false? |
上面的示例中在范圍上調用的include?方法顯示是否其參數是范圍的一個成員。上例中,"Z"不是范圍的一個元素。這個方法還有一個與之等價的操作符"==="-它實現相同的功能。
irb(main):005:0>?r2==="Z" =≫? false? |
范圍被應用在Ruby編程的許多方面。它們有兩種特定的使用:作為生成器(generator)和謂詞(predicate)。作為一個生成器,在范圍上的每個方法允許你遍歷該范圍中的每個元素;例如,你想確定在一個K字節范圍中的實際字節數。下面在irb中運行的代碼使用了一個范圍作為K字節到字節的生成器。
irb(main):008:0>? kilobytes=(1..10) kilobytes.each{|x|?puts?x*1024} =>?1..10 1024 2048 3072 4096 5120 6144 7168 8192 9216 10240 |
?
在Ruby中,條件邏輯范圍可以被用作謂語(predicate),通常借助于操作符===的幫助。例如,你可以使用一個范圍謂詞來測試一個相對于有效的端口號(0~65535)和保留的端口號(0~1024,不包括1024)的整數參考。
irb(main):001:0>? proposedPort?=?8080 validPorts=(0..65535) reservedPorts=(0...1024) if?(validPorts?===?proposedPort)?&?!(reservedPorts?===?proposedPort) puts?"Proposed?port?is?ok?to?use." else puts?"Proposed?port?is?not?allowed?to?be?used." end =>?8080 =>?0..65535 =>?0...1024? |
上例的結果是,建議的端口號可以使用。
另外,范圍也可以用于存取數據結構(如數組和哈希表)中的元素。
?一些程序語言(如C++和CLOS)提供了多重繼承機制:一個類可以繼承自多個超類。例如,一個House可能繼承自一個Building類(連同Office和Hospital類一起)和Residence類(連同Apartment類一起)。盡管多重繼承可能成為一種語言強有力的特征,但是由于它會增加該語言的復雜性和歧義,因此許多面向對象語言都沒有加入它。
Ruby支持單繼承。然而,它還提供了mixin-它提供了多繼承的許多特征。一個mixin是一種"模塊"類型。為此,你必須首先理解在Ruby中模塊的含義。
在Ruby中,模塊是一種把方法和常數分組的方式。它與類相似,但是一個模塊沒有實例并且也沒有子類。也許解釋模塊的最好方法是舉一個例子。假定你正在開發一個制造業應用程序。該程序需要存取大量的科學公式和常數,那么你可以或者是創建一個通用類來把這些放在其中或者是創建一個模塊。模塊的優點在于,在存取其中的公式時,根本沒有任何實例擾亂所帶來的煩惱。
module?Formulas ACCELERATION?=?9.8 LIGHTSPEED?=?299792458 def?energy?(mass) mass*(LIGHTSPEED**2) end def?force?(mass) mass*ACCELERATION end end |
?
現在,這些公式方法和常數可以被任何數目的其它類或其自身所使用:
irb(main):046:0>?Formulas.force(10) =>?98.0 irb(main):047:0≫?Formulas::ACCELERATION =≫?9.8? |
注意,為了調用一個模塊方法或使用一個模塊常數,你必須使用類似于調用一個類方法的標志。為了調用一個模塊方法,你需要使用模塊類名,后面跟著一個點,再跟著模塊方法名。為了引用模塊常數,你可以使用模塊名,后面跟著兩個冒號,再跟著常數名。
除了作為方法和常數的"公共"應用以外,模塊還可以幫助定義多重繼承。一個mixin是一個簡單的"包括"有一個類定義的模塊。當一個類包括一個模塊時,所有的模塊中的方法和常數都成為類的實例方法和常數。例如,假定上面定義的Formula模塊作為一個mixin被添加到Rectangle類。為此,你要使用"include"關鍵字:
class?Rectangle include?Formulas end |
? 現在,Rectangle的實例具有了它們可以使用的force和energy方法,并且Rectangle類能夠訪問常數ACCELERATION和LIGHTSPEED:
irb(main):044:0>?class?Rectangle irb(main):045:1>?include?Formulas irb(main):046:1>?end =>?Rectangle irb(main):047:0>?Rectangle.new(4,5).force(10) =>?98.0 irb(main):048:0>?Rectangle::LIGHTSPEED =>?299792458? |
這意味著,mixin給Ruby中的類帶來了許多多重繼承的優點,卻避開了多重繼承中存在的問題。
十一、 控制流
象所有的程序語言一樣,Ruby提供了一組控制流命令,這包括條件語句(if/else結構),case語句和循環語句(do,while和for),還提供了象Ada和Java等語言中的異常處理能力。下面是Ruby中的一些控制流語句的示例:
ifarea?>?100 "big" else "small" end case?height |?when?1 |?print?"stubby\n" |?when?2..10?#高度范圍為2~10 |?print?"short\n" |?when?10..20?#高度范圍為2~10 |?print?"tall\n" |?end aRect?=?Rectangle.new(4,6) while?aRect.area?<?100?and?aRect.height?<?10 aRect.doubleSize() end for?element?in?[2,?9.8,?"some?string",?Math::PI]?#遍歷對象集合 print?"The?type?is:?"?+?element.type.to_s?+?"\n&" end? |
控制語句通常非常直接,但是如前面的case語句和for循環語句所顯示的,Ruby借鑒了其它一些語言特征和普通的面向對象的特性。
異常處理類似于Java中的"try...catch...finally"語句。在Ruby中,它們更改為"begin...rescue...ensure"語句:
begin #實現一些事情 rescue ##處理錯誤 ensure #做一些清理工作,如關閉一個打開的文件等 end? |
為了在你的代碼中引發一個異常,你只需要簡單地調用raise方法:
if?area?<?0 raise else?if?area?>?0?and?area?<?10 raise?"Rectangle?too?small" else?if?area?>?100 raise?TooBigException?"Rectangle?too?big" end? |
第一個raise調用創建一個RuntimeError。第二個raise創建一個顯示一條消息的RuntimeError。最后一個raise調用一個TooBigException的新實例(由它創建一個粗略定義的錯誤),并設置它的適當消息。
一個Ruby小程序
為了幫助你更好地掌握Ruby的基礎知識,我在本文中提供了一個小程序供你學習之用。為了使這個程序工作,你可以下載并把該文件解壓到你的文件系統。之后,它將創建一個examples文件夾,在這個目錄下共有9個Ruby代碼文件(.rb文件)。本文中的代碼就包含在這些文件中。另外,你會找到一個testShapes.rb文件,它是測試Ruby的Rectangle,Square和Circle對象的主文件。只需簡單地打開一個命令提示符并運行testShapes.rb文件即可。
你將注意到,在testShapes.rb和另外一些代碼中,文件以"require"再加上一個文件名開頭(象rectangle.rb)。這是在你的Ruby程序中加入或使用來自于其它文件代碼的Ruby標志。
總結
Ruby能否接管Java或C#而成為業界領先的現代軟件開發語言?盡管Ruby可能變得十分流行,但我對此仍抱有懷疑態度。作為一名在業界摸爬滾打多年的專業人員,我對其偶然性并不感到驚訝,但我還是比較注重實效的。例如,我發現Smalltalk是一種比Java優越的高級語言,然而優越并不會總會贏。現代語言背后總存在大量的技術和市場方面的支持。庫,開發工具包,框架,架構,連接器,適配器,支持平臺,服務,知識庫,能干的開發團隊,等等,都會被配置到位以支持象Java這樣的程序語言。并且,無論你喜歡與否,Sun和Microsoft主宰下的市場在未來一段時間內肯定還是開發環境的勝者。
那么,為什么還要探討Ruby呢?作為一種Perl或Python腳本語言的代替(這正是它的最初目的)或快速原型開發工具,Ruby可能特別有用。一些人也已經看到了Ruby的威力,并且開始把Ruby作為一種偉大的方法進行編程教學。根據我的本地Ruby用戶組的成員提供的信息,有一些人正在把它應用于測試生產系統。不僅如此,我將邀請你,就象Bruce Tate和Dave Thomas邀請我一樣來探討一下這種語言的力量和美麗之處。即使Ruby不會得到廣泛使用,但是隨著人們對它的逐漸認識和試用,它一定會找到適應自己的編程環境。
?
-
· Private:只能為該對象所存取的方法。
-
· Protected:可以為該對象和類實例和直接繼承的子類所存取的方法。
-
· Public:可以為任何對象所存取的方法(Public是所有方法的默認設置)。
這些關鍵字被插入在兩個方法之間的代碼中。所有從private關鍵字開始定義的方法都是私有的,直到代碼中出現另一個存取控制關鍵字為止。例如,在下面的代碼中,accessor和area方法默認情況下都是公共的,而grow方法是私有的。注意,在此doubleSize方法被顯式指定為公共的。一個類的initialize方法自動為私有的。
class?Rectangle attr_accessor?:height,?:width def?initialize?(hgt,?wdth) @height?=?hgt @width?=?wdth end def?area?() @height*@width end private?#開始定義私有方法 def?grow?(heightMultiple,?widthMultiple) @height?=?@height?*?heightMultiple @width?=?@width?*?widthMultiple return?"New?area:"?+?area().to_s end public?#再次定義公共方法 def?doubleSize?() grow(2,2) end end |
?
如下所示,doubleSize可以在對象上執行,但是任何對grow的直接調用都被拒絕并且返回一個錯誤。
irb(main):075:0>?rect2=Rectangle.new(3,4) =>?#<Rectangle:0x59a3088?@width=4,?@height=3> irb(main):076:0>?rect2.doubleSize() =>?"New?area:?48" irb(main):077:0>?rect2.grow() NoMethodError:?private?method?'grow'?called?for?#<Rectangle:0x59a3088?@width=8,?@height=6> from?(irb):77 from?:0? |
默認情況下,在Ruby中,實例和類變量都是私有的,除非提供了屬性accessor和mutator。
象大多數面向對象語言一樣,Ruby類也允許定義類變量和方法。一個類變量允許在一個類的所有實例間共享單個變量。在Ruby中,兩個@@號用于指示類變量。例如,如果你想要使一個BankAccount類的所有實例共享相同的利息率,那么該類可能被如下定義:
class BankAccount @@interestRate = 6.5 def BankAccount.getInterestRate() @@interestRate end attr_accessor :balance def initialize (bal) @balance = bal end end |
如你所見,類變量必須在使用前初始化,并且就象實例變量一樣,如果你想存取類變量的話,你需要編寫存取器方法。在此,我定義了一個類方法來返回利息率。注意,類名和在getInterestRate前面的句號表示一個類方法。一個類方法,不管對于任何實例,其工作方式都是相同的-在此,是把相同的利息率返回到所有的BankAccount實例。為了調用類方法,你需要使用類名,就象它使用于類方法定義中一樣:
irb(main):045:0> BankAccount.getInterestRate => 6.5 |
事實上,用于創建類實例的"new"方法就是一個類方法。因此,當你在程序中輸入"Rectangle.new"時,你實際在調用new類方法-這是Ruby默認情況下所提供的。
繼承
面向對象編程的原則之一是支持類層次結構。就象自然界中的事物分類一樣,類允許從更為通用的類進行繼承。面向對象編程的特征主要體現在方法和變量的使用上。例如,一個Square類繼承Rectangle類的一些特征,如方法和變量。一個Square是一種更具體類型的Rectangle(高度和寬度相等的Rectangle實例),但是它仍然有一個高度和寬度,也有一個面積(而且與矩形的計算方法相同)。在Ruby中,Square類可以使用下列定義創建:
class?Square?<?Rectangle end? |
"<Rectangle"意味著,Square是Rectangle的一個子類,或反過來說,Rectangle是Square的一個超類。默認情況下,一個Square實例自動地擁有所有一個Rectangle所擁有的相同的屬性和方法,包括height,width和area方法。為了確保Square實例的邊長相等,你可以重載現有的Square的initialize方法:
class?Square?<?Rectangle def?initialize?(size) @height?=?size @width?=?size end end? |
因為在Ruby中一切都是對象,所以Ruby中的一切幾乎都派生自Object類。盡管這在所有類定義中都不是顯式的(你不會看到<Object出現在定義中),但是的確所有的類都派生自Ruby的基類Object。知道這個事實后,你就會更容易地理解接下來要討論的內容。
當編寫你的應用程序時,你可以在一個類定義外定義方法。在本文開始,你已看到了一個并不是任何一個類的一部分的攝氏到華氏轉換器方法。作為另外一個示例,下面是一個位于任何類之外的方法:
def?feel? return?"I?feel?fine." end? |
為了執行這個方法,只要輸入該方法名,而不需要類或實例:
irb(main):042:0>?feel? =>?"I?feel?fine."? |
此方法看似另外一種語言(如C)中的函數或過程。事實上,盡管這些方法看上去好象不屬于任何類,但是這些方法卻都是你已經添加到Object類上的方法,它(因為Object是所有類的超類)反過來也把這一方法添加到你的繼承類上。因此,現在你可以在任何對象(如Square和Rectangle的實例)甚至一個類(如Rectangle類)上調用這個方法。
irb(main):043:0>?sq1=Square.new(4) =>?#<Square:0x5a18b50?@width=4,?@height=4> irb(main):044:0>?rect1=Rectangle.new(5,7) =>?#<Rectangle:0x5a139a8?@width=7,?@height=5> irb(main):045:0>?sq1.feel? =>?"I?feel?fine." irb(main):046:0>?rect1.feel? =>?"I?feel?fine." irb(main):047:0>?Rectangle.feel? =>?"I?feel?fine." |
變量與賦值
至此,你是否注意到前面所有的示例代碼中都缺少某種東西?難道你必須輸入常數,實例變量或類變量?絕對不是!這正是Ruby的真正面向對象的天性的一部分。為此,首先讓我們看一下Ruby中以前的普通變量。至此,你已經創建了很多Rectangle實例,但是你并沒有把它們保留多長時間。比方說,你想要把一個變量賦值給你創建的一個Rectangle實例:
myRectangle?=?Rectangle.new(4,5) |
在Ruby中這是完全有效的代碼,而且根本不需要另一行代碼來把myRectangle類型化或聲明為引用Rectangle的某種東西。在執行這一行代碼以后,變量myRectangle就引用一個Rectangle的實例(高度和寬度值分別為4,5)。但是,這僅是一種在任何時刻都可以更改的對象引用,而與對象的類型無關(在Ruby中一切都是對象)。因此,下面的命令提示符行中,你可以容易地把myRectangle賦值給一個字符串:
irb(main):049:0< myRectangle=Rectangle.new(4,5) => #<Rectangle:0x587c758 @width=5, @height=4> irb(main):050:0< myRectangle="Jim's Rectangle" => "Jim's Rectangle"
|
你可以在許多其它程序語言(甚至包括象Java這樣的面向對象的語言)中試驗一下,并觀察從你的IDE所產生的編譯錯誤。
變量,實例變量,類變量,甚至還有"常量"其實都只是對象引用。它們引用對象,但是它們并不是對象本身。因此,它們可以被動態地改變,甚至引用另一種不同類型的對象。
因為這一靈活性,所以必須在Ruby中進行一些約定以幫助每個人都知道某個變量正為代碼所使用。其實,你已經看到了其中之一(@符號,它意味著這是一個實例變量)。其它的變量,方法和類命名約定列于下表1中。
- · 局部變量和方法參數以一個小寫字母開頭。
- · 方法名字以一個小寫字母開頭。
- · 全局變量以一個$開頭。
- · 實例變量以一個@開頭。
- · 類變量以兩個@開頭。
- · 常數以一個大寫字母開頭(它們經常被指定全部大寫)。
- · 類和模塊名以一個大寫字母開頭。
?局部變量 |
?全局變量 |
?實例變量 |
?類變量 |
?常數 |
?類名 |
?方法名 |
?aVar |
?$Var |
?@var |
?@@var |
?VAR |
?MyClassmy |
?Method |
?name |
?$debug |
?@lastName |
?@@interest |
?PI |
?Rectangle |
?area |
表1.這個表包含了在Ruby編碼約定下的相關示例。
操作符方法
現在,假定你想實現合并Rectangle類的實例或把它們添加到另一個Rectangle實例。你當然可以定義另外一個稱為"add"的方法,這種選擇利用了Ruby真正的面向對象的特征之一。然而,你還可以重載"+"運算符來適當地把兩個Rectangle實例加起來。這個"+"方法(如4+5),對Ruby來說,只是另外一個方法而已。由于只是"另外一個方法",所以你可以給它增加一些功能來滿足Rectangle類的需要。例如,你還可以定義"+"運算符來實現一個矩形面積加上另一個矩形面積。
def + (anotherRectangle) totalArea = area() + anotherRectangle.area() Rectangle.new(@height,totalArea/@height) end
|
在把這個方法添加到Rectangle類以后,你可以使用+方法調用來把兩個Rectangle的實例相加:
irb(main):001:0> rect1=Rectangle.new(2,3) => #<Rectangle:0x58aa688 @width=3, @height=2> irb(main):002:0> rect2=Rectangle.new(3,4) => #<Rectangle:0x58a6ef0 @width=4, @height=3> irb(main):003:0> rect1+rect2 => #<Rectangle:0x58a4a60 @width=9, @height=2>
|
這正是操作符重載,對于那些使用過Agol,C++,Python和其它語言的用戶來說,可能已經非常熟悉這個特征。
方法參數
至此,我們一直假定,傳遞給一個方法的參數個數是已知的。也許在其它語言中不可思議,但是Ruby的確允許你傳遞可變個數的參數并且以單個參數來捕獲它們。為了創建一個可變長度的參數,只需要把一個星號(*)放在最后一個參數前面即可。這樣,你就可以在Ruby中編寫一個如下的多邊形定義。
class Polygon def initialize (s1,s2,s3,*others) @sideOne = s1 @sideTwo = s2 @sideThree = s3 @otherSides = others end end |
如下所示,你可以使用這個定義來創建一個三角形或一個六邊形。
irb(main):009:0> poly1=Polygon.new(2,4,5) => #<Polygon:0x594db10 @otherSides=[], @sideThree=5, @sideTwo=4, @sideOne=2> irb(main):010:0> poly2=Polygon.new(2,18,4,5,7,9) => #<Polygon:0x5948d58 @otherSides=[5, 7, 9], @sideThree=4, @sideTwo=18, @sideOne=2>
|
在支持可變長度參數的同時,Ruby還允許定義一個方法參數的默認值(在調用者沒有提供的情況下使用)。例如,下面是Rectangle類的一個更好的初始化表達。
def initialize (hgt = 1, wdth = 1) @height = hgt @width = wdth end
|
現在,在調用時如果省略了某參數,那么在定義中參數緊鄰的賦值運算符擔當一個缺省的賦值器。現在,當創建一新的矩形時,如果在調用時省略了寬度,那么一個適當的寬度也會被默認地提供:
irb(main):090:0> rect=Rectangle.new(2) => #<Rectangle:0x5873f68 @width=1, @height=2>
|