2008年1月5日
方法调用的绑定:
将方法的调用连到方法本身被称为“绑定”。当绑定发生在程序运行之前时,被称作“前绑定”。
后绑定也称为“动态绑定”或“运行时绑定”,指程序运行的时候,根据对象的类型来决定该绑定哪个方法。如果语言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其合适方法的机制。也就是说编译器还是不知道对象的类型,但是方法的调用机制会找出,并且调用正确的方法。
除了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 仰望者 阅读(207) |
评论 (0) |
编辑 收藏
2007年12月23日
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 仰望者 阅读(154) |
评论 (0) |
编辑 收藏
2007年12月20日
合成与继承
继承:
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 仰望者 阅读(228) |
评论 (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 仰望者 阅读(233) |
评论 (0) |
编辑 收藏
2007年12月18日
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 仰望者 阅读(171) |
评论 (0) |
编辑 收藏
摘要: 菜单
功能
热键
说明
Edit
Add Block Comment
Ctrl+Shift+/
Editing in Structured Text Editors
...
阅读全文
posted @
2007-12-18 15:36 仰望者 阅读(310) |
评论 (0) |
编辑 收藏
2007年12月14日
JDK SRC中注解:
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
此实现假定哈希函数将元素正确分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代集合视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)的和成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
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 仰望者 阅读(426) |
评论 (0) |
编辑 收藏
2007年12月13日
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请求并转发给控制器类。控制器类是系统中处理所有请求的最初入口点。控制器完成一些必要的处理后把请求委托给分发者类;分发者类分发者负责视图的管理和导航,它管理将选择哪个视图提供给用户,并提供给分发资源控制。在这一部分分别采用了分发者、策略、工厂方法、适配器等设计模式。
为了使请求捕获者类自动捕获用户请求并进行处理,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模式编写的,具有极其良好的可扩展性。它可以轻松实现以下功能:
①实现一个模型的多个视图;
②采用多个控制器;
③当模型改变时,所有视图将自动刷新;
④所有的控制器将相互独立工作。
这就是MVC模式的好处,只需在以前的程序上稍作修改或增加新的类,即可轻松增加许多程序功能。以前开发的许多类可以重用,而程序结构根本不再需要改变,各类之间相互独立,便于团体开发,提高开发效率。下面讨论如何实现一个模型、两个视图和一个控制器的程序。其中模型类及视图类根本不需要改变,与前面的完全一样,这就是面向对象编程的好处。对于控制器中的类,只需要增加另一个视图,并与模型发生关联即可。该模式下视图、控制器、模型三者之间的示意图如图2所示。
同样也可以实现其它形式的MVC例如:一个模型、两个视图和两个控制器。从上面可以看出,通过MVC模式实现的应用程序具有极其良好的可扩展性,是ASP.NET面向对象编程的未来方向。
四、MVC的优点
大部分用过程语言比如ASP、PHP开发出来的Web应用,初始的开发模板就是混合层的数据编程。例如,直接向数据库发送请求并用HTML显示,开发速度往往比较快,但由于数据页面的分离不是很直接,因而很难体现出业务模型的样子或者模型的重用性。产品设计弹性力度很小,很难满足用户的变化性需求。MVC要求对应用分层,虽然要花费额外的工作,但产品的结构清晰,产品的应用通过模型可以得到更好地体现。
首先,最重要的是应该有多个视图对应一个模型的能力。在目前用户需求的快速变化下,可能有多种方式访问应用的要求。例如,订单模型可能有本系统的订单,也有网上订单,或者其他系统的订单,但对于订单的处理都是一样,也就是说订单的处理是一致的。按MVC设计模式,一个订单模型以及多个视图即可解决问题。这样减少了代码的复制,即减少了代码的维护量,一旦模型发生改变,也易于维护。 其次,由于模型返回的数据不带任何显示格式,因而这些模型也可直接应用于接口的使用。
再次,由于一个应用被分离为三层,因此有时改变其中的一层就能满足应用的改变。一个应用的业务流程或者业务规则的改变只需改动MVC的模型层。
控制层的概念也很有效,由于它把不同的模型和不同的视图组合在一起完成不同的请求,因此,控制层可以说是包含了用户请求权限的概念。
最后,它还有利于软件工程化管理。由于不同的层各司其职,每一层不同的应用具有某些相同的特征,有利于通过工程化、工具化产生管理程序代码。
五、MVC的不足
MVC的不足体现在以下几个方面:
(1)增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
(2)视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
(3)视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
(4) 目前,一般高级的界面工具或构造器不支持MVC模式。改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。
posted @
2007-12-13 15:22 仰望者 阅读(143) |
评论 (0) |
编辑 收藏
2007年12月12日
构造函数重载:重载三要素(参数数量、参数类型、参数的排列顺序)。
基本数据类型的重载:如果实参比形参的类型小,数据会先提升,如果实参比形参大,那么要先进行强制类型转换。
返回值类型不是重载的要素:理解之一是,构造函数要实现重载,但构造函数无返回值。另外调用函数的时候可以没有返回值类型。
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 仰望者 阅读(240) |
评论 (0) |
编辑 收藏
正则表达式:是一种用通用术语描述字符串的方法。要表示一个可能有也可能没有的负号,你可以负号后面加一个问号,如:-?,表示整数,就是表示一个或多个阿拉伯数字。在正则表达式中,阿拉伯数字用‘\d’表示的,但在JAVA的String里,前面应加\,如下表示“前面可能有个负号后面跟一串阿拉伯数字,写成:-?\\d+,在正则表达式里表示“一个或多个前述的表达式”,使用“+”。
posted @
2007-12-12 14:43 仰望者 阅读(158) |
评论 (0) |
编辑 收藏