
2007年12月12日
方法調用的綁定:
將方法的調用連到方法本身被稱為“綁定”。當綁定發生在程序運行之前時,被稱作“前綁定”。
后綁定也稱為“動態綁定”或“運行時綁定”,指程序運行的時候,根據對象的類型來決定該綁定哪個方法。如果語言實現了后綁定,那它就必須要有能在運行時判斷對象類型,并且調用其合適方法的機制。也就是說編譯器還是不知道對象的類型,但是方法的調用機制會找出,并且調用正確的方法。
除了static和final方法(private方法隱含有final的意思),java的所有的方法都采用后綁定。也就是說,通常情況下你不必考慮是不是應該采用后綁定--它是自動的。 為什么要聲明final方法,上一章指出,這樣可以禁止別人覆寫這個方法,不過更重要的可能還是要“關閉”它的動態綁定,或者理確切的說,告訴編譯器這里不需要使用后綁定。
shape為circle的基類,下面這句就是在“上傳”:
Shape s = new Circle();
這里先創建了一個Circle對象,接著馬上把它的引用賦給了Shape,看上去這像是一個錯誤(一種類型怎么能賦給另一種);但是由于Circle是由Shape派生出來的,Circle就是一種Shape,因此這種做法是非常正確的。假設你調用了一個基類的方法:s.draw();這里派生類里已經覆寫了此方法,那么可能你會認為,這次應該總調用Shape的draw()了吧,因為畢竟這是Shape的引用,但是由于實現了后綁定(多態性),實際上它會調用Circle.draw().
posted @
2008-01-05 19:18 仰望者 閱讀(220) |
評論 (0) |
編輯 收藏
1、 float f=1.3;
是不對的,編譯時會出錯,java認為1.3是double型的,所以定義時應寫成:float f=1.3f,或float f= (float)1.3;
2、 byte b = 3; b=b*3;
是不對的,原因是在*運算過程中,java會做類型的提升,將b提升為int型,所以應改為:b=(byte)(b*3);
3、 while(1),if(1)
是不對的,原因是java中布爾型只有true 和false兩個值,這里與C語言不同,只能用while(true)..
4、 數組聲明:int num[3];
這是不對的,java中聲明數組時不應對空間限定,正確的語法應是:
int[] num = new int[3];
或
int[] num;
num = new int[3];
5、數組初始化:int[] num;
num {1,3,4,4};
是不對的,應在定義的時候初始化。如:int[] num={1,3,4,4};
6、int[] num3 =new int[]{1,2,3};
int[] num5 =new int[3]{1,2,3};
int[] num3 =new int[]{1,2,3};是對的。
int[] num5 =new int[3]{1,2,3};是錯的。已經初始化的數組,不應再列明:[3]
posted @
2007-12-23 23:01 仰望者 閱讀(163) |
評論 (0) |
編輯 收藏
合成與繼承
繼承:
super關鍵字的使用:super使用在派生類中,如果派生類中重寫了基類的方法,但在這個被重寫的方法中仍然要調用基類的同名的方法,這就要用到super關鍵字,特別是在創建對象時,在帶參數構造函數中調用基類構造函數的情況。
如:
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Testing base class:");
Cleanser.main(args);
}
} ///:~
可以看到基類Cleanser 中定義了scrub方法,但派生類Detergent 中對scrub方法進行了修改,并用在派生類Detergent 的scrub方法中,要調用基本的scrub方法,那么用super.scrub();
基類的初始化:
當你創建一個派生類的對象的時候,這個對象里面還有一個基類的子對象,這個子對象同基類自己創建的對象沒什么兩樣,只是從外面看來,這個子對象被包裹在派生類的對象里面。
基類子對象的正確初始化是非常重要的,而且只有一個辦法能保證這一點:調用基類的構造函數來進行初始化,因為只有它才能掌握怎么樣才能正確地進行初始化的信息和權限。java會讓派生類的構造函數自動地調用基類的構造函數。
示例:
class Art {
Art() {
System.out.println("Art constructor");
}
}
class Drawing extends Art {
Drawing() {
System.out.println("Drawing constructor");
}
}
public class Cartoon extends Drawing {
Cartoon() {
System.out.println("Cartoon constructor");
}
public static void main(String[] args) {
Cartoon x = new Cartoon();
}
} ///:~
輸出結果為:
Art constructor
Drawing constructor
Cartoon constructor
一看結果便一目了然了。
上面的示例是不帶任何參數的情況,
如果構造函數中帶有參數的話,那這里又要用到super的特性了。與上面super的使用涵意一樣,super在這里用作:派生的帶參數構造函數中調用基類的帶參構造函數,只是這里不象上面那樣super.scrub();這里只使用super(i);即可。
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
} ///:~
輸出結果是:
Game constructor
BoardGame constructor
Chess constructor
合成和繼承一起使用,實現類的復用:
class Plate {
Plate(int i) {
System.out.println("Plate constructor");
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
System.out.println(
"DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
System.out.println("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
System.out.println("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
System.out.println("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
System.out.println("Knife constructor");
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
System.out.println("Custom constructor");
}
}
public class PlaceSetting extends Custom {
Spoon sp;
Fork frk;
Knife kn;
DinnerPlate pl;
PlaceSetting(int i) {//把初始化工作都放在構造函數中
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
System.out.println(
"PlaceSetting constructor");
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
} ///:~
盡管編譯器會強迫我們對基礎類進行初始化,并要求我們在構建器最開頭做這一工作,但它并不會監視我們是否正確初始化了成員對象。所以對此必須特別加以留意。
FINAL關鍵字:
FINAL關鍵字指“那樣東西是不允許改動”,你可能會出于兩點考慮不想讓別人作改動:設計和效率。由于這兩個原因差別很大,所以很可能會誤用final關鍵字。
final的三種用途:數據(Data)、方法(method)、類(class)。
很多語言通知編譯器:“這段常量(constant)數據”的手段。常量能用下列兩種情況出現:
1、可以是“編譯時的常量”,這樣以后就不能改了;
2、也可以是運行時初始化的值,這個值以后就不想再改了。
如果是編譯時的常量,編譯器會把常量放到算式里面;這樣編譯的時候就能進行計算,因此也就降低了運行時的開銷。在Java 中這種常量必須是primitive 型的,而且要用final 關鍵詞表示。這種常量的賦值必須在定義的時候進行。
一個既是static 又是final 的數據成員會只占據一段內存,并且不可修改。
當final 不是指primitive,而是用于對象的reference 的時候,意思就有點不一樣了。對primitive 來說,final 會將這個值定義成常量,但是對于對象的reference 而言,final 的意思則是這個reference 是常量。初始化的時候,一旦將reference 連到了某個對象,那么它就再也不能指別的對象了。但是這個對象本身是可以修改的;Java 沒有提供將某個對象作成常量的方法。
(但是你可以自己寫一個類,這樣就能把類當做常量了)
這種局限性也體現在數組上,因為它也是一個對象。
注意,通常約定,被初始化為常量值的final static 的primitive 的名字全都用大寫,詞與詞之間用下
劃線分開,如VAL_ONE
Final 方法
使用final 方法的目的有二:
第一,為方法上“鎖”,禁止派生類進行修改。這是出于設計考慮。當你希望某個方法的功能,能在繼承過程中被保留下來,并且不被覆寫,就可以使用這個方法。
第二個原因就是效率。如果方法是final 的,那么編譯器就會把調用轉換成“內聯的(inline)”。它會用方法本身的拷貝來代替方法的調用
final 和private
private 方法都隱含有final 的意思。由于你不能訪問private 的方法,因此你也不能覆寫它。你可以給private 方法加一個final 修飾符,但是這樣做什么意義也沒有。
這個問題有可能會造成混亂,因為即使你覆寫了一個private 方法(它隱含有final 的意思),看上去它還是可以運行的,而且編譯器也不會報錯:
class WithFinals {
// Identical to "private" alone:
private final void f() {
System.out.println("WithFinals.f()");
}
/ / Also automatically "final":
private void g() {
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
System.out.println("OverridingPrivate.f()");
}
private void g() {
System.out.println("OverridingPrivate.g()");
}
}
只有是基類接口里的東西才能被“覆寫”,如果方法是private 的,那它就不屬于基類的接口。它只能算是被類隱藏起來的,正好有著相同的名字的代碼。如果你在派生類里創建了同名的public 或protected,或package 權限的方法,那么它們同基類中可能同名的方法,沒有任何聯系。你并沒有覆寫那個方法,你只是創建了一個新的方法。由于private 方法是無法訪問的,實際上是看不見的,因此這么作除了會影響類的代碼結構,其它什么意義都沒有。
Final 類
把整個類都定義成final 的(把final 關鍵詞放到類的定義部分的前面)就等于在宣布,你不會去繼承這個類,你也不允許別人去繼承這個類。換言之,出于類的設計考慮,它再也不需要作修改了,或者從安全角度出發,你不希望它再生出子類。
final class Dinosaur{}
注意,final 類的數據可以是final 的,也可以不是final 的,這要由你來決定。無論類是不是final 的,這一條都適用于“將final 用于數據的”場合。但是,由于final 類禁止了繼承,覆寫方法已經不可能了,因
此所有的方法都隱含地變成final 了。你可以為final 類的方法加一個final 修飾符,但是這一樣沒什么意義。
posted @
2007-12-20 17:33 仰望者 閱讀(238) |
評論 (0) |
編輯 收藏
訪問控制符:public 、private、protected、friendly
public包內包外均可訪問。
private只有本類可訪問。
protected針對繼承而使用的:1、包內繼承,因為在包內,聲明為protected不影響它本來的friendly權限。
2、包外繼承,必須聲明為protected。
派生類可以訪問基類的protected成員。
注意不可將類設成private(那樣會使除類之外的其他東西都不能訪問它),也不能設成protected。因此,我們現在對于類的訪問只有兩個選擇:“友好的”或者public。若不愿其他任何人訪問那個類,可將所有構建器設為private,這樣除你之外,沒有可以用類創建的了。而你可以使用static成員創建對象。
package com.access.external;
class Soup{
private Soup(){//構造函數聲明為private,其它類不能用此構造函數創建對象;
System.out.println("sffewe");
}
public static Soup makSoup(){//其它類可通過makSoup來創建對象;
return new Soup();
}
private static Soup ps1 = new Soup();//自己創建對象;
public static Soup access(){//返回對象的引用。
return ps1;
}
public void f(){}
}
class Sandwich{
void f(){
new Lunch();
}
}
public class Lunch {
void test(){
//Soup priv1 = new Soup();
Soup priv2 = Soup.makSoup();
Sandwich f1 = new Sandwich();
Soup.access().f();//不創建對象,但通過Soup中返回的對象引用調用其方法。
}
}
該方法返回一個句柄,它指向類Soup的一個對象。
Soup類向我們展示出如何通過將所有構建器都設為private,從而防止直接創建一個類。請記住,假若不明確地至少創建一個構建器,就會自動創建默認構建器(沒有自變量)。若自己編寫默認構建器,它就不會自動創建。把它變成private后,就沒人能為那個類創建一個對象。但別人怎樣使用這個類呢?上面的例子為我們揭示出了兩個選擇。第一個選擇,我們可創建一個static方法,再通過它創建一個新的Soup,然后返回指向它的一個句柄。如果想在返回之前對Soup進行一些額外的操作,或者想了解準備創建多少個Soup對象(可能是為了限制它們的個數),這種方案無疑是特別有用的。
第二個選擇是采用“設計方案”(Design Pattern)技術,本書后面會對此進行詳細介紹。通常方案叫作“獨子”,因為它僅允許創建一個對象。類Soup的對象被創建成Soup的一個static private成員,所以有一個而且只能有一個。除非通過public方法access(),否則根本無法訪問它。
posted @
2007-12-20 11:09 仰望者 閱讀(248) |
評論 (0) |
編輯 收藏
Eclipse提供了很好的工具:
1、實時運算薄頁面(java scrapbook page)具體的說就是一小段代碼,比如一個for循環,就可以在里面執行,無須寫出main函數等。
操作如下:new->other->java->java run/debug->scrapbook page
創建頁面后,輸入代碼:
for (int i = 0; i < 10; i++) {
System.out.println(Integer.toString(i));
}
選擇代碼,右鍵excute即可看到結果。。很方便。。。。。
2、程序代碼產生模板
window->prefrences->java->editor->Templates
添加:name:Sys
context:java
Description:shortcut for System.out.println
pattern:System.out.println(${cursor});
確定后,在程序中輸入s或Sys時再按alt+/會提示語句。。。接著按enter鍵吧。。。
3、產生 getter 與 setter Java 編輯器可以為編譯單元內的類型字段,產生存取元(accessors,也就是getter和setter的method)。 I. 「Source」→「Generate Getter and Setter...」 (或是在Java編輯器按右鍵,「Source」→「Generate Getter and Setter...」)
挑選哪些需要建立getter和setter的method ;
選擇method要建立的地方 ;
排序的方式;
選擇Access modifier ;
選擇是否需要建立批注;
按OK;
4、建立新的 JAR 檔案 如果要在工作臺中建立新 JAR 檔,請執行下列動作: I. 在「Package Explorer」中,可以選擇性地預選一或多個要匯出的 Java 元素。(在步驟IV中,這些會在JAR Package Specification精靈頁面中自動選出。) II. 從快速菜單或從菜單列的File菜單,選取Export。 III. 選取JAR file,然后按一下Next。
IV. 在JAR Package Specification頁面的Select the resources to export字段中,選取要匯出的資源。 V. 選取適當的勾選框,以指出想Export generated class files and resourcess或Export java source files and resources。附注:這兩種情況皆會匯出所選的資源。 VI. 在Select the export destination字段中,輸入或按一下Browse以選取 JAR 文件的位置。 VII. 選取或清除Compress the contents of the JAR file勾選框。 VIII. 選取或清除Overwrite existing files without warning勾選框。如果清除這個勾選框,則會提示確認是否要更換每一個將被改寫的檔案。 IX. 附注:在撰寫 JAR 檔、JAR 說明與 Manifest 檔時,會套用改寫選項。 X. 有兩項選擇: ?? 按一下Finish來立即建立 JAR 檔。 ?? 按一下Next,使用「JAR 套裝選項」頁面,以設定進階選項,建立 JAR 說明,或變更預設 manifest。
posted @
2007-12-18 15:53 仰望者 閱讀(180) |
評論 (0) |
編輯 收藏
摘要: 菜單
功能
熱鍵
說明
Edit
Add Block Comment
Ctrl+Shift+/
Editing in Structured Text Editors
...
閱讀全文
posted @
2007-12-18 15:36 仰望者 閱讀(318) |
評論 (0) |
編輯 收藏
JDK SRC中注解:
基于哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,并允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證映射的順序,特別是它不保證該順序恒久不變。
此實現假定哈希函數將元素正確分布在各桶之間,可為基本操作(get 和 put)提供穩定的性能。迭代集合視圖所需的時間與 HashMap 實例的“容量”(桶的數量)及其大?。ㄦI-值映射關系數)的和成比例。所以,如果迭代性能很重要,則不要將初始容量設置得太高(或將加載因子設置得太低)。
HashMap 的實例有兩個參數影響其性能:初始容量 和加載因子。容量 是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。加載因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,通過調用 rehash 方法將容量翻倍。
通常,默認加載因子 (.75) 在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地降低 rehash 操作次數。如果初始容量大于最大條目數除以加載因子,則不會發生 rehash 操作。
如果很多映射關系要存儲在 HashMap 實例中,則相對于按需執行自動的 rehash 操作以增大表的容量來說,使用足夠大的初始容量創建它將使得映射關系能更有效地存儲。
注意,此實現不是同步的。如果多個線程同時訪問此映射,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。(結構上的修改是指添加或刪除一個或多個映射關系的操作;僅改變與實例已經包含的鍵關聯的值不是結構上的修改。)這一般通過對自然封裝該映射的對象進行同步操作來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap 方法來“包裝”該映射。最好在創建時完成這一操作,以防止對映射進行意外的不同步訪問,如下所示:
Map m = Collections.synchronizedMap(new HashMap(...));
由所有此類的“集合視圖方法”所返回的迭代器都是快速失敗 的:在迭代器創建之后,如果從結構上對映射進行修改,除非通過迭代器自身的 remove 或 add 方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。因此,面對并發的修改,迭代器很快就會完全失敗,而不冒在將來不確定的時間任意發生不確定行為的風險。
注意,迭代器的快速失敗行為不能得到保證,一般來說,存在不同步的并發修改時,不可能作出任何堅決的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴于此異常程序的方式是錯誤的,正確做法是:迭代器的快速失敗行為應該僅用于檢測程序錯誤。
Hashtable和HashMap的區別:
1.Hashtable是Dictionary的子類,HashMap是Map接口的一個實現類;
2.Hashtable
中的方法是同步的,而HashMap中的方法在缺省情況下是非同步的。即是說,在多線程應用程序中,不用專門的操作就安全地可以使用Hashtable
了;而對于HashMap,則需要額外的同步機制。但HashMap的同步問題可通過Collections的一個靜態方法得到解決:
Map Collections.synchronizedMap(Map m)
這個方法返回一個同步的Map,這個Map封裝了底層的HashMap的所有方法,使得底層的HashMap即使是在多線程的環境中也是安全的。
3.
在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。當get()方法返回null值時,即可以表示
HashMap中沒有該鍵,也可以表示該鍵所對應的值為null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,
而應該用containsKey()方法來判斷。
posted @
2007-12-14 12:06 仰望者 閱讀(439) |
評論 (0) |
編輯 收藏
MVC模式是"Model-View-Controller"的縮寫,中文翻譯為"模式-視圖-控制器"。MVC應用程序總是由這三個部分組成。Event(事件)導致Controller改變Model或View,或者同時改變兩者。只要Controller改變了Models的數據或者屬性,所有依賴的View都會自動更新。類似的,只要Controller改變了View,View會從潛在的Model中獲取數據來刷新自己。MVC模式最早是smalltalk語言研究團提出的,應用于用戶交互應用程序中。smalltalk語言和java語言有很多相似性,都是面向對象語言,很自然的SUN在petstore(寵物店)事例應用程序中就推薦MVC模式作為開發Web應用的架構模式。MVC模式是一種架構模式,其實需要其他模式協作完成。在J2EE模式目錄中,通常采用service to worker模式實現,而service to worker模式可由集中控制器模式,派遣器模式和Page Helper模式組成。而Struts只實現了MVC的View和Controller兩個部分,Model部分需要開發者自己來實現,Struts提供了抽象類Action使開發者能將Model應用于Struts框架中。
MVC模式是一個復雜的架構模式,其實現也顯得非常復雜。但是,我們已經終結出了很多可靠的設計模式,多種設計模式結合在一起,使MVC模式的實現變得相對簡單易行。Views可以看作一棵樹,顯然可以用Composite Pattern來實現。Views和Models之間的關系可以用Observer Pattern體現。Controller控制Views的顯示,可以用Strategy Pattern實現。Model通常是一個調停者,可采用Mediator Pattern來實現。
現在讓我們來了解一下MVC三個部分在J2EE架構中處于什么位置,這樣有助于我們理解MVC模式的實現。MVC與J2EE架構的對應關系是:View處于Web Tier或者說是Client Tier,通常是JSP/Servlet,即頁面顯示部分。Controller也處于Web Tier,通常用Servlet來實現,即頁面顯示的邏輯部分實現。Model處于Middle Tier,通常用服務端的javaBean或者EJB實現,即業務邏輯部分的實現。(Enterprise Bean 與 JavaBean 不同。JavaBean 是使用 java.beans 包開發的,它是 Java 2 標準版的一部分。JavaBean 是一臺機器上同一個地址空間中運行的組件。JavaBean 是進程內組件。Enterprise Bean 是使用 javax.ejb 包開發的,它是標準 JDK 的擴展,是 Java 2 Enterprise Edition 的一部分。Enterprise Bean 是在多臺機器上跨幾個地址空間運行的組件。因此 Enterprise Bean 是進程間組件。JavaBean 通常用作 GUI 窗口小部件,而 Enterprise Bean 則用作分布式商業對象. )
一、MVC設計思想
MVC英文即Model-View-Controller,即把一個應用的輸入、處理、輸出流程按照Model、View、Controller的方式進行分離,這樣一個應用被分成三個層——模型層、視圖層、控制層。
視圖(View)代表用戶交互界面,對于Web應用來說,可以概括為HTML界面,但有可能為XHTML、XML和Applet。隨著應用的復雜性和規模性,界面的處理也變得具有挑戰性。一個應用可能有很多不同的視圖,MVC設計模式對于視圖的處理僅限于視圖上數據的采集和處理,以及用戶的請求,而不包括在視圖上的業務流程的處理。業務流程的處理交予模型(Model)處理。比如一個訂單的視圖只接受來自模型的數據并顯示給用戶,以及將用戶界面的輸入數據和請求傳遞給控制和模型。
模型(Model):就是業務流程/狀態的處理以及業務規則的制定。業務流程的處理過程對其它層來說是黑箱操作,模型接受視圖請求的數據,并返回最終的處理結果。業務模型的設計可以說是MVC最主要的核心。目前流行的EJB模型就是一個典型的應用例子,它從應用技術實現的角度對模型做了進一步的劃分,以便充分利用現有的組件,但它不能作為應用設計模型的框架。它僅僅告訴你按這種模型設計就可以利用某些技術組件,從而減少了技術上的困難。對一個開發者來說,就可以專注于業務模型的設計。MVC設計模式告訴我們,把應用的模型按一定的規則抽取出來,抽取的層次很重要,這也是判斷開發人員是否優秀的設計依據。抽象與具體不能隔得太遠,也不能太近。MVC并沒有提供模型的設計方法,而只告訴你應該組織管理這些模型,以便于模型的重構和提高重用性。我們可以用對象編程來做比喻,MVC定義了一個頂級類,告訴它的子類你只能做這些,但沒法限制你能做這些。這點對編程的開發人員非常重要。
業務模型還有一個很重要的模型那就是數據模型。數據模型主要指實體對象的數據 保存(持續化)。比如將一張訂單保存到數據庫,從數據庫獲取訂單。我們可以將這個模型單獨列出,所有有關數據庫的操作只限制在該模型中。
控制(Controller)可以理解為從用戶接收請求, 將模型與視圖匹配在一起,共同完成用戶的請求。劃分控制層的作用也很明顯,它清楚地告訴你,它就是一個分發器,選擇什么樣的模型,選擇什么樣的視圖,可以完成什么樣的用戶請求。控制層并不做任何的數據處理。例如,用戶點擊一個連接,控制層接受請求后, 并不處理業務信息,它只把用戶的信息傳遞給模型,告訴模型做什么,選擇符合要求的視圖返回給用戶。因此,一個模型可能對應多個視圖,一個視圖可能對應多個模型。模型、視圖與控制器的分離,使得一個模型可以具有多個顯示視圖。如果用戶通過某個視圖的控制器改變了模型的數據,所有其它依賴于這些數據的視圖都應反映到這些變化。因此,無論何時發生了何種數據變化,控制器都會將變化通知所有的視圖,導致顯示的更新。這實際上是一種模型的變化-傳播機制。模型、視圖、控制器三者之間的關系和各自的主要功能,如圖1所示。
二、MVC設計模式的實現
ASP.NET提供了一個很好的實現這種經典設計模式的類似環境。開發者通過在ASPX頁面中開發用戶接口來實現視圖;控制器的功能在邏輯功能代碼(.cs)中實現;模型通常對應應用系統的業務部分。在ASP.NET中實現這種設計而提供的一個多層系統,較經典的ASP結構實現的系統來說有明顯的優點。將用戶顯示(視圖)從動作(控制器)中分離出來,提高了代碼的重用性。將數據(模型)從對其操作的動作(控制器)分離出來可以讓你設計一個與后臺存儲數據無關的系統。就MVC結構的本質而言,它是一種解決耦合系統問題的方法。
2.1 視圖
視圖是模型的表示,它提供用戶交互界面。使用多個包含單顯示頁面的用戶部件,復雜的Web頁面可以展示來自多個數據源的內容,并且網頁人員,美工能獨自參與這些Web頁面的開發和維護。
在ASP.NET下,視圖的實現很簡單。可以像開發WINDOWS界面一樣直接在集成開發環境下通過拖動控件來完成頁面開發本。本文中介紹每一個頁面都采用復合視圖的形式即:一個頁面由多個子視圖(用戶部件)組成;子視圖可以是最簡單HTML 控件、服務器控件或多個控件嵌套構而成的Web自定義控件。頁面都由模板定義,模板定義了頁面的布局,用戶部件的標簽和數目,用戶指定一個模板,平臺根據這些信息自動創建頁面。針對靜態的模板內容,如頁面上的站點導航,菜單,友好鏈接,這些使用缺省的模板內容配置;針對動態的模板內容(主要是業務內容),由于用戶的請求不同,只能使用后期綁定,并且針對用戶的不同,用戶部件的顯示內容進行過濾。使用由用戶部件根據模板配置組成的組合頁面,它增強了可重用性,并原型化了站點的布局。
視圖部分大致處理流程如下:首先,頁面模板定義了頁面的布局;頁面配置文件定義視圖標簽的具體內容(用戶部件);然后,由頁面布局策略類初始化并加載頁面;每個用戶部件根據它自己的配置進行初始化,加載校驗器并設置參數,以及事件的委托等;用戶提交后,通過了表示層的校驗,用戶部件把數據自動提交給業務實體即模型。
這一部分主要定義了WEB頁面基類PageBase;頁面布局策略類PageLayout,完成頁面布局,用于加載用戶部件到頁面;用戶部件基類UserControlBase即用戶部件框架,用于動態加載檢驗部件,以及實現用戶部件的個性化。為了實現WEB應用的靈活性,視圖部分也用到了許多配置文件例如:置文件有模板配置、頁面配置、路徑配置、驗證配置等。
2.2 控制器
為了能夠控制和協調每個用戶跨越多個請求的處理,控制機制應該以集中的方式進行管理。因此,為了達到集中管理的目的引入了控制器。應用程序的控制器集中從客戶端接收請求(典型情況下是一個運行瀏覽器的用戶),決定執行什么商業邏輯功能,然后將產生下一步用戶界面的責任委派給一個適當的視圖組件。
用控制器提供一個控制和處理請求的集中入口點,它負責接收、截取并處理用戶請求;并將請求委托給分發者類,根據當前狀態和業務操作的結果決定向客戶呈現的視圖。在這一部分主要定義了HttpReqDispatcher(分發者類)、HttpCapture(請求捕獲者類)、Controller(控制器類)等,它們相互配合來完成控制器的功能。請求捕獲者類捕獲HTTP請求并轉發給控制器類??刂破黝愂窍到y中處理所有請求的最初入口點。控制器完成一些必要的處理后把請求委托給分發者類;分發者類分發者負責視圖的管理和導航,它管理將選擇哪個視圖提供給用戶,并提供給分發資源控制。在這一部分分別采用了分發者、策略、工廠方法、適配器等設計模式。
為了使請求捕獲者類自動捕獲用戶請求并進行處理,ASP.NET 提供低級別的請求/響應 API,使開發人員能夠使用 .NET 框架類為傳入的 HTTP 請求提供服務。為此,必須創作支持 System.Web.IHTTPHandler 接口和實現 ProcessRequest() 方法的類即:請求捕獲者類,并在web.config 的 <httphandlers> 節中添加類。ASP.NET 收到的每個傳入 HTTP 請求最終由實現 IHTTPHandler 的類的特定實例來處理。IHttpHandlerFactory 提供了處理 IHttpHandler 實例 URL 請求的實際解析的結構。HTTP 處理程序和工廠在 ASP.NET 配置中聲明為 web.config 文件的一部分。ASP.NET 定義了一個 <httphandlers> 配置節,在其中可以添加和移除處理程序和工廠。子目錄繼承 HttpHandlerFactory 和 HttpHandler 的設置。 HTTP 處理程序和工廠是 ASP.NET 頁框架的主體。工廠將每個請求分配給一個處理程序,后者處理該請求。 例如,在全局 machine.config 文件中,ASP.NET 將所有對 ASPx 文件的請求映射到 HttpCapture類:
<httphandlers>
...
...
</httphandlers>
2.3 模型
MVC系統中的模型從概念上可以分為兩類――系統的內部狀態和改變系統狀態的動作。模型是你所有的商業邏輯代碼片段所在。本文為模型提供了業務實體對象和業務處理對象:所有的業務處理對象都是從ProcessBase類派生的子類。業務處理對象封裝了具體的處理邏輯,調用業務邏輯模型,并且把響應提交到合適的視圖組件以產生響應。業務實體對象可以通過定義屬性描述客戶端表單數據。所有業務實體對象都EntityBase派生子類對象,業務處理對象可以直接對它進行讀寫,而不再需要和request、response對象進行數據交互。通過業務實體對象實現了對視圖和模型之間交互的支持。實現時把"做什么"(業務處理)和"如何做"(業務實體)分離。這樣可以實現業務邏輯的重用。由于各個應用的具體業務是不同的,這里不再列舉其具體代碼實例。
三、MVC設計模式的擴展
通過在ASP.NET中的MVC模式編寫的,具有極其良好的可擴展性。它可以輕松實現以下功能:
?、賹崿F一個模型的多個視圖;
?、诓捎枚鄠€控制器;
?、郛斈P透淖儠r,所有視圖將自動刷新;
?、芩械目刂破鲗⑾嗷オ毩⒐ぷ鳌?/span>
這就是MVC模式的好處,只需在以前的程序上稍作修改或增加新的類,即可輕松增加許多程序功能。以前開發的許多類可以重用,而程序結構根本不再需要改變,各類之間相互獨立,便于團體開發,提高開發效率。下面討論如何實現一個模型、兩個視圖和一個控制器的程序。其中模型類及視圖類根本不需要改變,與前面的完全一樣,這就是面向對象編程的好處。對于控制器中的類,只需要增加另一個視圖,并與模型發生關聯即可。該模式下視圖、控制器、模型三者之間的示意圖如圖2所示。
同樣也可以實現其它形式的MVC例如:一個模型、兩個視圖和兩個控制器。從上面可以看出,通過MVC模式實現的應用程序具有極其良好的可擴展性,是ASP.NET面向對象編程的未來方向。
四、MVC的優點
大部分用過程語言比如ASP、PHP開發出來的Web應用,初始的開發模板就是混合層的數據編程。例如,直接向數據庫發送請求并用HTML顯示,開發速度往往比較快,但由于數據頁面的分離不是很直接,因而很難體現出業務模型的樣子或者模型的重用性。產品設計彈性力度很小,很難滿足用戶的變化性需求。MVC要求對應用分層,雖然要花費額外的工作,但產品的結構清晰,產品的應用通過模型可以得到更好地體現。
首先,最重要的是應該有多個視圖對應一個模型的能力。在目前用戶需求的快速變化下,可能有多種方式訪問應用的要求。例如,訂單模型可能有本系統的訂單,也有網上訂單,或者其他系統的訂單,但對于訂單的處理都是一樣,也就是說訂單的處理是一致的。按MVC設計模式,一個訂單模型以及多個視圖即可解決問題。這樣減少了代碼的復制,即減少了代碼的維護量,一旦模型發生改變,也易于維護。 其次,由于模型返回的數據不帶任何顯示格式,因而這些模型也可直接應用于接口的使用。
再次,由于一個應用被分離為三層,因此有時改變其中的一層就能滿足應用的改變。一個應用的業務流程或者業務規則的改變只需改動MVC的模型層。
控制層的概念也很有效,由于它把不同的模型和不同的視圖組合在一起完成不同的請求,因此,控制層可以說是包含了用戶請求權限的概念。
最后,它還有利于軟件工程化管理。由于不同的層各司其職,每一層不同的應用具有某些相同的特征,有利于通過工程化、工具化產生管理程序代碼。
五、MVC的不足
MVC的不足體現在以下幾個方面:
(1)增加了系統結構和實現的復雜性。對于簡單的界面,嚴格遵循MVC,使模型、視圖與控制器分離,會增加結構的復雜性,并可能產生過多的更新操作,降低運行效率。
(2)視圖與控制器間的過于緊密的連接。視圖與控制器是相互分離,但確實聯系緊密的部件,視圖沒有控制器的存在,其應用是很有限的,反之亦然,這樣就妨礙了他們的獨立重用。
(3)視圖對模型數據的低效率訪問。依據模型操作接口的不同,視圖可能需要多次調用才能獲得足夠的顯示數據。對未變化數據的不必要的頻繁訪問,也將損害操作性能。
?。?) 目前,一般高級的界面工具或構造器不支持MVC模式。改造這些工具以適應MVC需要和建立分離的部件的代價是很高的,從而造成使用MVC的困難。
posted @
2007-12-13 15:22 仰望者 閱讀(152) |
評論 (0) |
編輯 收藏
構造函數重載:重載三要素(參數數量、參數類型、參數的排列順序)。
基本數據類型的重載:如果實參比形參的類型小,數據會先提升,如果實參比形參大,那么要先進行強制類型轉換。
返回值類型不是重載的要素:理解之一是,構造函數要實現重載,但構造函數無返回值。另外調用函數的時候可以沒有返回值類型。
this關鍵詞的使用:
1、this只能用于方法內部,它負責返回調用這個方法的對象的引用。你可以把this對象的引用當成任何對象的引用。
2、this用于在構造函數中調用其他構造函數,但只能調用一個,且調用的代碼應放在程序最前面,否則編譯器會報錯。
3、this.s=s當類的數據成員與類的方法的參數同名時,使用this.s=s消除歧義。
static的含義:
它的意思是,這個方法沒有this,你不能在static方法里調用非static的方法,但你卻可以不通過對象,直接調用static方法。類的static方法只能訪問其它static方法static成員。
垃圾回收:
java提供finalize()方法用于垃圾回收器回收前執行方法中的代碼進行非new所創建內存的清理。你可以自己定義類的finalize()方法。不過要注意java對象并不總是會被回收的。它不象C++一樣提供析構函數,可做到總是回收。java垃圾回收特點:
1、對象不一定會被垃圾回收器回收;
2、垃圾回收不是析構;
3、你可以自己寫方法時行垃圾回收;
4、垃圾回收只與內存有關;
5、垃圾回收和finalize()都是靠不住的,只要jvm還沒到快要耗盡內存的地步,它是不會浪費時間來回收垃圾以恢復內存的。
初始化的順序:初始化的順序是由變量在類的定義里面的順序所決定的。變量的定義可能會分散在類定義的各個地方,并且與方法的定義相互交錯,但是變量的初始化會先于任何方法,甚至是構造函數的調用。以下為例:
class Tag{
Tag(int marker){
System.out.println("Tag("+marker+")");
}
}
class Card{
Tag t1 = new Tag(1);
Card(){
System.out.println("Card()");
t3 = new Tag(22);
}
Tag t2 = new Tag(2);
void f(){
System.out.println("f()");
}
Tag t3 = new Tag(3);
}
public class Clean {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Card t = new Card();
t.f();
}
}
輸出結果為:
Tag(1)
Tag(2)
Tag(3)
Card()
Tag(22)
f()
Static數據的初始化(注意結合代碼調試理解):
package com.initialization.order;
class Bowl {
Bowl(){
System.out.println("Bowl(9)");
}
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
static Bowl b6 = new Bowl(6);
static Bowl b9 = new Bowl();
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
Bowl b10 = new Bowl();
static Bowl b4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
b4.f(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = new Bowl(5);
}
public class StaticInitialization {
//static Bowl b7 = new Bowl(7); //----------(1)
public static void main(String[] args) {
System.out.println(
"Creating new Cupboard() in main");
new Cupboard();
System.out.println(
"Creating new Cupboard() in main");
new Cupboard();
//t2.f2(1); //--------------(2)
//t3.f3(1); //---------------(3)
}
//static Bowl b8 = new Bowl(8); //----------------(4)
//static Table t2 = new Table(); //----------------(5)
//static Cupboard t3 = new Cupboard(); //---------(6)
} ///:~
調試以上代碼,總結出以下結論:
一、初始化的過程:總體來說順序為:static初始化->非static初始化->執行構造函數;
二、代碼分析一:對現有代碼執行結果如下:
Creating new Cupboard() in main
Bowl(6)
Bowl(9)
Bowl(4)
Bowl(5)
Bowl(3)
Bowl(9)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Bowl(9)
Cupboard()
f(2)
執行過程:
1、java解釋器尋找public class類,加載StaticInitialization 類;
2、尋找StaticInitialization 類中的static定義代碼段;這里因為(1)、(4)、(5)、(6)均加了注釋,所以StaticInitialization 中沒有static需要初始化;
3、進入main函數中執行代碼輸出Creating new Cupboard() in main;
4、繼續執行new Cupboard();,注意初始化的順序是static初始化->非static初始化->執行構造函數;所以加載類Cupboard后,首先尋找Cupboard類中的static代碼段;
發現static段是: static Bowl b4 = new Bowl(4); static Bowl b5 = new Bowl(5);
同時發現有非static段是: Bowl b3 = new Bowl(3); Bowl b10 = new Bowl();
按順序先執行:static Bowl b4 = new Bowl(4); 初始化,因為定義的是Bowl類的實例,所以先加載Bowl類,進入Bowl類發現又有static代碼段static Bowl b6 = new Bowl(6);
static Bowl b9 = new Bowl();然而我們知道初始化static Bowl b4 = new Bowl(4); 需要調用Bowl 的構造函數,但調用構造函數之前必須將該類需要初始化的部分先進行初始化,所以執行到這里就要先進行Bowl類中的static代碼段的初始化,之后才能調用構造函數Bowl(int marker) 為static Bowl b4 = new Bowl(4); 進行初始化。于是b6,b9分別調用構造函數Bowl(int marker),Bowl(),輸出Bowl(6),Bowl(9),完了之后,b4調用構造函數Bowl(int marker)輸出Bowl(4),b4初始化結束,返回Cupboard類繼續執行,初始化b5,輸出Bowl(5),此時Cupboard類中static部分初始化完,接下來對非static部分初始化,即對b3和b10初始化,一樣的方法,加載Bowl類,發現static字段在上面已經初始化,所以這里直接調用Bowl類的構造函數,輸出Bowl(3),Bowl(9)。至此Cupboard類中需要初始化的部分已經初始化完了,所以放心大膽的調用Cupboard類的構造函數,為main函數中代碼完成new Cupboard();的實現,輸出Cupboard(),f(2)。程序執行返回到main函數,輸出:Creating new Cupboard() in main,代碼new Cupboard();又一次出現,這里實際上是想演示static只會初始化一次,而非static只要創建了對象或調用了成員、成員函數,會進行第二次初始化,于是可以看到輸出結果并沒有再輸出Bowl(6)、Bowl(9)、Bowl(4)、Bowl(5),而是輸出:Bowl(3)、Bowl(9)、Cupboard()、f(2)。
5、取消注釋(1)、(4),發現結果如下:
Bowl(6)
Bowl(9)
Bowl(7)
Bowl(8)
Creating new Cupboard() in main
Bowl(4)
Bowl(5)
Bowl(3)
Bowl(9)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Bowl(9)
Cupboard()
f(2)
可以看出輸出了Bowl(7)、Bowl(8),這說明在main()函數執行之前,程序要先對StaticInitialization進行檢查,如果有static部分,則先初始化。
6、再取消注釋(2)、(5)輸出結果為:
Bowl(6)
Bowl(9)
Bowl(7)
Bowl(8)
Bowl(1)
Bowl(2)
Table()
f(1)
Creating new Cupboard() in main
Bowl(4)
Bowl(5)
Bowl(3)
Bowl(9)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Bowl(9)
Cupboard()
f(2)
f2(1)
在前面的基礎上又輸出了Bowl(1)、Bowl(2)、Table()、f(1)。然而我們看到當沒有代碼
static Table t2 = new Table();的時候Table類中的static部分沒有被初始化,這說明什么?
static初始化只有在必要的時候才會進行。只有在創建了第一個Table對象之后才會進行初始化。
總結如下:初始化順序為:加載public StaticInitialization類->初始化static->加載類Cupboard->初始化static->加載類Bowl->初始化static->執行Bowl類構造函數->初始化Cupboard類非static->調用Bowl類構造函數執行->調用Cupboard類構造函數執行->返回StaticInitialization類.
posted @
2007-12-12 17:16 仰望者 閱讀(249) |
評論 (0) |
編輯 收藏
正則表達式:是一種用通用術語描述字符串的方法。要表示一個可能有也可能沒有的負號,你可以負號后面加一個問號,如:-?,表示整數,就是表示一個或多個阿拉伯數字。在正則表達式中,阿拉伯數字用‘\d’表示的,但在JAVA的String里,前面應加\,如下表示“前面可能有個負號后面跟一串阿拉伯數字,寫成:-?\\d+,在正則表達式里表示“一個或多個前述的表達式”,使用“+”。
posted @
2007-12-12 14:43 仰望者 閱讀(167) |
評論 (0) |
編輯 收藏