2005年07月25日

1.标准输入,标准输出,标准错误输出
以Unix首先倡导的“标准输入”、“标准输出”以及“标准错误输出”

概念为基础,Java提供了相应的System.in,System.out以及System.err

。贯这一整本书,大家都会接触到如何用System.out进行标准输出,它

已预封装成一个PrintStream对象。System.err同样是一个PrintStream

,但System.in是一个原始的InputStream,未进行任何封装处理。这意

味着尽管能直接使用System.out和System.err,但必须事先封装

System.in,否则不能从中读取数据。
典型情况下,我们希望用readLine()每次读取一行输入信息,所以需要

将System.in封装到一个DataInputStream中。这是Java 1.0进行行输入

时采取的“老”办法。在本章稍后,大家还会看到Java 1.1的解决方案

。下面是个简单的例子,作用是回应我们键入的每一行内容:

//: Echo.java
// How to read from standard input
import java.io.*;

public class Echo {
  public static void main(String[] args) {
    DataInputStream in =
      new DataInputStream(
        new BufferedInputStream(System.in));
    String s;
    try {
      while((s = in.readLine()).length() != 0)
        System.out.println(s);
      // An empty line terminates the program
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
} ///
2.为序列化一个对象,首先要创建某些OutputStream对象,然后将其封

装到ObjectOutputStream对象内。此时,只需调用writeObject()即可完

成对象的序列化,并将其发送给OutputStream。相反的过程是将一个

InputStream封装到ObjectInputStream内,然后调用readObject()。和

往常一样,我们最后获得的是指向一个上溯造型Object的句柄,所以必

须下溯造型,以便能够直接设置。
对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”

,而且能追踪对象内包含的所有句柄并保存那些对象;接着又能对每个

对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为“对

象网”,单个对象可与之建立连接。而且它还包含了对象的句柄数组以

及成员对象。若必须自行操纵一套对象序列化机制,那么在代码里追踪

所有这些链接时可能会显得非常麻烦。在另一方面,由于Java对象的序

列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算

法自动维护整个对象网。


3. transient(临时)关键字
控制序列化过程时,可能有一个特定的子对象不愿让Java的序列化机制自动保存与恢复。一般地,若那个子对象包含了不想序列化的敏感信息(如密码),就会面临这种情况。即使那种信息在对象中具有“private”(私有)属性,但一旦经序列化处理,人们就可以通过读取一个文件,或者拦截网络传输得到它。
为防止对象的敏感部分被序列化,一个办法是将自己的类实现为Externalizable,就象前面展示的那样。这样一来,没有任何东西可以自动序列化,只能在writeExternal()明确序列化那些需要的部分。
然而,若操作的是一个Serializable对象,所有序列化操作都会自动进行。为解决这个问题,可以用transient(临时)逐个字段地关闭序列化,它的意思是“不要麻烦你(指自动机制)保存或恢复它了——我会自己处理的”。

1.所有Java集合类都能自动改变自身的大小。所以,我们在编程时可使用数量众多的对象,同时不必担心会将集合弄得有多大。

2. 崩溃Java
Java标准集合里包含了toString()方法,所以它们能生成自己的String表达方式,包括它们容纳的对象。例如在Vector中,toString()会在Vector的各个元素中步进和遍历,并为每个元素调用toString()。假定我们现在想打印出自己类的地址。看起来似乎简单地引用this即可(特别是C++程序员有这样做的倾向):

//: CrashJava.java
// One way to crash Java
import java.util.*;

public class CrashJava {
  public String toString() {
    return "CrashJava address: " + this + "\n";
  }
  public static void main(String[] args) {
    Vector v = new Vector();
    for(int i = 0; i < 10; i++)
      v.addElement(new CrashJava());
    System.out.println(v);
  }
} ///:~


若只是简单地创建一个CrashJava对象,并将其打印出来,就会得到无穷无尽的一系列违例错误。然而,假如将CrashJava对象置入一个Vector,并象这里演示的那样打印Vector,就不会出现什么错误提示,甚至连一个违例都不会出现。此时Java只是简单地崩溃(但至少它没有崩溃我的操作系统)。这已在Java 1.1中测试通过。
此时发生的是字串的自动类型转换。当我们使用下述语句时:
"CrashJava address: " + this
编译器就在一个字串后面发现了一个“+”以及好象并非字串的其他东西,所以它会试图将this转换成一个字串。转换时调用的是toString(),后者会产生一个递归调用。若在一个Vector内出现这种事情,看起来堆栈就会溢出,同时违例控制机制根本没有机会作出响应。
若确实想在这种情况下打印出对象的地址,解决方案就是调用Object的toString方法。此时就不必加入this,只需使用super.toString()。当然,采取这种做法也有一个前提:我们必须从Object直接继承,或者没有一个父类覆盖了toString方法。

3.使用hashset时的一个小问题

1. 创建“关键”类
在前面的例子里,我们用一个标准库的类(Integer)作为Hashtable的一个键使用。作为一个键,它能很好地工作,因为它已经具备正确运行的所有条件。但在使用散列表的时候,一旦我们创建自己的类作为键使用,就会遇到一个很常见的问题。例如,假设一套天气预报系统将Groundhog(土拔鼠)对象匹配成Prediction(预报)。这看起来非常直观:我们创建两个类,然后将Groundhog作为键使用,而将Prediction作为值使用。如下所示:

//: SpringDetector.java
// Looks plausible, but doesn't work right.
import java.util.*;

class Groundhog {
  int ghNumber;
  Groundhog(int n) { ghNumber = n; }
}

class Prediction {
  boolean shadow = Math.random() > 0.5;
  public String toString() {
    if(shadow)
      return "Six more weeks of Winter!";
    else
      return "Early Spring!";
  }
}

public class SpringDetector {
  public static void main(String[] args) {
    Hashtable ht = new Hashtable();
    for(int i = 0; i < 10; i++)
      ht.put(new Groundhog(i), new Prediction());
    System.out.println("ht = " + ht + "\n");
    System.out.println(
      "Looking up prediction for groundhog #3:");
    Groundhog gh = new Groundhog(3);
    if(ht.containsKey(gh))
      System.out.println((Prediction)ht.get(gh));
  }
} ///:~


每个Groundhog都具有一个标识号码,所以赤了在散列表中查找一个Prediction,只需指示它“告诉我与Groundhog号码3相关的Prediction”。Prediction类包含了一个布尔值,用Math.random()进行初始化,以及一个toString()为我们解释结果。在main()中,用Groundhog以及与它们相关的Prediction填充一个散列表。散列表被打印出来,以便我们看到它们确实已被填充。随后,用标识号码为3的一个Groundhog查找与Groundhog #3对应的预报。
看起来似乎非常简单,但实际是不可行的。问题在于Groundhog是从通用的Object根类继承的(若当初未指定基础类,则所有类最终都是从Object继承的)。事实上是用Object的hashCode()方法生成每个对象的散列码,而且默认情况下只使用它的对象的地址。所以,Groundhog(3)的第一个实例并不会产生与Groundhog(3)第二个实例相等的散列码,而我们用第二个实例进行检索。
大家或许认为此时要做的全部事情就是正确地覆盖hashCode()。但这样做依然行不能,除非再做另一件事情:覆盖也属于Object一部分的equals()。当散列表试图判断我们的键是否等于表内的某个键时,就会用到这个方法。同样地,默认的Object.equals()只是简单地比较对象地址,所以一个Groundhog(3)并不等于另一个Groundhog(3)。
因此,为了在散列表中将自己的类作为键使用,必须同时覆盖hashCode()和equals(),就象下面展示的那样:

//: SpringDetector2.java
// If you create a class that's used as a key in
// a Hashtable, you must override hashCode()
// and equals().
import java.util.*;

class Groundhog2 {
  int ghNumber;
  Groundhog2(int n) { ghNumber = n; }
  public int hashCode() { return ghNumber; }
  public boolean equals(Object o) {
    return (o instanceof Groundhog2)
      && (ghNumber == ((Groundhog2)o).ghNumber);
  }
}

public class SpringDetector2 {
  public static void main(String[] args) {
    Hashtable ht = new Hashtable();
    for(int i = 0; i < 10; i++)
      ht.put(new Groundhog2(i),new Prediction());
    System.out.println("ht = " + ht + "\n");
    System.out.println(
      "Looking up prediction for groundhog #3:");
    Groundhog2 gh = new Groundhog2(3);
    if(ht.containsKey(gh))
      System.out.println((Prediction)ht.get(gh));
  }
} ///:~


注意这段代码使用了来自前一个例子的Prediction,所以SpringDetector.java必须首先编译,否则就会在试图编译SpringDetector2.java时得到一个编译期错误。
Groundhog2.hashCode()将土拔鼠号码作为一个标识符返回(在这个例子中,程序员需要保证没有两个土拔鼠用同样的ID号码并存)。为了返回一个独一无二的标识符,并不需要hashCode(),equals()方法必须能够严格判断两个对象是否相等。
equals()方法要进行两种检查:检查对象是否为null;若不为null,则继续检查是否为Groundhog2的一个实例(要用到instanceof关键字,第11章会详加论述)。即使为了继续执行equals(),它也应该是一个Groundhog2。正如大家看到的那样,这种比较建立在实际ghNumber的基础上。这一次一旦我们运行程序,就会看到它终于产生了正确的输出(许多Java库的类都覆盖了hashcode()和equals()方法,以便与自己提供的内容适应)。

4.Java集合类图

5.数组
Arrays类为所有基本数据类型的数组提供了一个过载的sort()和binarySearch(),它们亦可用于String和Object。

6.使用Enumeration,我们不必关心集合中的元素数量。所有工作均由hasMoreElements()和nextElement()自动照管了。
下面再看看另一个例子,让我们创建一个常规用途的打印方法:

//: HamsterMaze.java
// Using an Enumeration
import java.util.*;

class Hamster {
  private int hamsterNumber;
  Hamster(int i) {
    hamsterNumber = i;
  }
  public String toString() {
    return "This is Hamster #" + hamsterNumber;
  }
}

class Printer {
  static void printAll(Enumeration e) {
    while(e.hasMoreElements())
      System.out.println(
        e.nextElement().toString());
  }
}

public class HamsterMaze {
  public static void main(String[] args) {
    Vector v = new Vector();
    for(int i = 0; i < 3; i++)
      v.addElement(new Hamster(i));
    Printer.printAll(v.elements());
  }
} ///:~

1.Java中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。这意味着我们通常不必决定是否应进行后期绑定——它是自动发生的。

2.如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中的所有抽象方法提供方法定义。如果不这样做(完全可以选择不做),则衍生类也会是抽象的,而且编译器会强迫我们用abstract关键字标志那个类的“抽象”本质。
即使不包括任何abstract方法,亦可将一个类声明成“抽象类”。如果一个类没必要拥有任何抽象方法,而且我们想禁止那个类的所有实例,这种能力就会显得非常有用。
Instrument类可很轻松地转换成一个抽象类。只有其中一部分方法会变成抽象方法,因为使一个类抽象以后,并不会强迫我们将它的所有方法都同时变成抽象。

3.接口也包含了基本数据类型的数据成员,但它们都默认为static和final。

4.具体实现了一个接口以后,就获得了一个普通的类,可用标准方式对其进行扩展。
可决定将一个接口中的方法声明明确定义为“public”。但即便不明确定义,它们也会默认为public。所以在实现一个接口的时候,来自接口的方法必须定义成public。否则的话,它们会默认为“友好的”,而且会限制我们在继承过程中对一个方法的访问——Java编译器不允许我们那样做。

2005年07月24日

1. 含有自变量的构建器
上述例子有自己默认的构建器;也就是说,它们不含任何自变量。编译器可以很容易地调用它们,因为不存在具体传递什么自变量的问题。如果类没有默认的自变量,或者想调用含有一个自变量的某个基础类构建器,必须明确地编写对基础类的调用代码。这是用super关键字以及适当的自变量列表实现的,如下所示:


//: Chess.java
// Inheritance, constructors and arguments

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();
  }
} ///:~


如果不调用BoardGames()内的基础类构建器,编译器就会报告自己找不到Games()形式的一个构建器。除此以外,在衍生类构建器中,对基础类构建器的调用是必须做的第一件事情(如操作失当,编译器会向我们指出)。

2. 空白final
Java 1.1允许我们创建“空白final”,它们属于一些特殊的字段。尽管被声明成final,但却未得到一个初始值。无论在哪种情况下,空白final都必须在实际使用前得到正确的初始化。而且编译器会主动保证这一规定得以贯彻。然而,对于final关键字的各种应用,空白final具有最大的灵活性。举个例子来说,位于类内部的一个final字段现在对每个对象都可以有所不同,同时依然保持其“不变”的本质。下面列出一个例子:

//: BlankFinal.java
// "Blank" final data members

class Poppet { }

class BlankFinal {
  final int i = 0; // Initialized final
  final int j; // Blank final
  final Poppet p; // Blank final handle
  // Blank finals MUST be initialized
  // in the constructor:
  BlankFinal() {
    j = 1; // Initialize blank final
    p = new Poppet();
  }
  BlankFinal(int x) {
    j = x; // Initialize blank final
    p = new Poppet();
  }
  public static void main(String[] args) {
    BlankFinal bf = new BlankFinal();
  }
} ///:~


现在强行要求我们对final进行赋值处理——要么在定义字段时使用一个表达 式,要么在每个构建器中。这样就可以确保final字段在使用前获得正确的初始化。


3. final自变量
Java 1.1允许我们将自变量设成final属性,方法是在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,我们不能改变自变量句柄指向的东西。如下所示:
class Gizmo {
  public void spin() {}
}

public class FinalArguments {
  void with(final Gizmo g) {
    //! g = new Gizmo(); // Illegal — g is final
    g.spin();
  }
  void without(Gizmo g) {
    g = new Gizmo(); // OK — g not final
    g.spin();
  }
  // void f(final int i) { i++; } // Can’t change
  // You can only read from a final primitive:
  int g(final int i) { return i + 1; }
  public static void main(String[] args) {
    FinalArguments bf = new FinalArguments();
    bf.without(null);
    bf.with(null);
  }
}

6.8.2 final方法
之所以要使用final方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
采用final方法的第二个理由是程序执行的效率。将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
类内所有private方法都自动成为final。由于我们不能访问一个private方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可为一个private方法添加final指示符,但却不能为那个方法提供任何额外的含义。

1.如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会出现什么情况呢?默认的访问没有关键字,但它通常称为“友好”(Friendly)访问。这意味着当前包内的其他所有类都能访问“友好的”成员,但对包外的所有类来说,这些成员却是“私有”(Private)的,外界不得访问。由于一个编译单元(一个文件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动“友好”的。因此,我们也说友好元素拥有“包访问”权限。

2.由private来控制对象的生成

  class Sundae {
  private Sundae() {}
  static Sundae makeASundae() {
    return new Sundae();
  }
}

public class IceCream {
  public static void main(String[] args) {
    //! Sundae x = new Sundae();
    Sundae x = Sundae.makeASundae();
  }
} ///:~
这个例子向我们证明了使用private的方便:有时可能想控制对象的创建方式,并防止有人直接访问一个特定的构建器(或者所有构建器)。在上面的例子中,我们不可通过它的构建器创建一个Sundae对象;相反,必须调用makeASundae()方法来实现。

3.代码书写规范

为清楚起见,可考虑用特殊的样式创建一个类:将public成员置于最开头,后面跟随protected、友好以及private成员。这样做的好处是类的使用者可从上向下依次阅读,并首先看到对自己来说最重要的内容(即public成员,因为它们可从文件的外部访问),并在遇到非公共成员后停止阅读,后者已经属于内部实施细节的一部分了。然而,利用由javadoc提供支持的注释文档(已在第2章介绍),代码的可读性问题已在很大程度上得到了解决。

public class X {
  public void pub1( ) { /* . . . */ }
  public void pub2( ) { /* . . . */ }
  public void pub3( ) { /* . . . */ }
  private void priv1( ) { /* . . . */ }
  private void priv2( ) { /* . . . */ }
  private void priv3( ) { /* . . . */ }
  private int i;
  // . . .
}

4 类访问
(1) 每个编译单元(文件)都只能有一个public类。每个编译单元有一个公共接口的概念是由那个公共类表达出来的。根据自己的需要,它可拥有任意多个提供支撑的“友好”类。但若在一个编译单元里使用了多个public类,编译器就会向我们提示一条出错消息。
(2) public类的名字必须与包含了编译单元的那个文件的名字完全相符,甚至包括它的大小写形式。所以对于Widget来说,文件的名字必须是Widget.java,而不应是widget.java或者WIDGET.java。同样地,如果出现不符,就会报告一个编译期错误。
(3) 可能(但并常见)有一个编译单元根本没有任何公共类。此时,可按自己的意愿任意指定文件名。

1.主(数据)类型能从一个“较小”的类型自动转变成一个“较大”的类型。涉及过载问题时,这会稍微造成一些混乱。在这里,方法采用了容量更小、范围更窄的主类型值。若我们的自变量范围比它宽,就必须用括号中的类型名将其转为适当的类型。如果不这样做,编译器会报告出错。
大家可注意到这是一种“缩小转换”。也就是说,在造型或转型过程中可能丢失一些信息。这正是编译器强迫我们明确定义的原因——我们需明确表达想要转型的愿望。

2.若将基本类型(主类型)设为一个类的数据成员,情况就会变得稍微有些不同。由于任何方法都可以初始化或使用那个数据,所以在正式使用数据前,若还是强迫程序员将其初始化成一个适当的值,就可能不是一种实际的做法。然而,若为其赋予一个垃圾值,同样是非常不安全的。因此,一个类的所有基本类型数据成员都会保证获得一个初始值。

3.可考虑用构建器执行初始化进程。这样便可在编程时获得更大的灵活程度,因为我们可以在运行期调用方法和采取行动,从而“现场”决定初始化值。但要注意这样一件事情:不可妨碍自动初始化的进行,它在构建器进入之前就会发生。因此,假如使用下述代码:

class Counter {
int i;
Counter() { i = 7; }
// . . .

那么i首先会初始化成零,然后变成7。对于所有基本类型以及对象句柄,这种情况都是成立的,其中包括在定义时已进行了明确初始化的那些一些。考虑到这个原因,编译器不会试着强迫我们在构建器任何特定的场所对元素进行初始化,或者在它们使用之前——初始化早已得到了保证

4.Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:
class Spoon {
  static int i;
  static {
    i = 47;
  }
尽管看起来象个方法,但它实际只是一个static关键字,后面跟随一个方法主体。与其他static初始化一样,这段代码仅执行一次——首次生成那个类的一个对象时,或者首次访问属于那个类的一个static成员时(即便从未生成过那个类的对象)。

2004年10月21日

今天老婆让我去市场买一些水果,具体买什么自己定(哈,老婆放放权了!)。来到市场,我发现主要有一些水果:苹果(Apple),葡萄(Grape)和鸭梨(Pear)。

到底买什么好呢?我一阵思量。俗话说:“饭后一只烟,赛过活神仙。饭后吃苹果,西施见我躲。”为了老婆的漂亮,我决定买苹果。

好,言归正传,开始买吧!

 

主要有以下三种Factory模式:

Simple Factory模式

专门定义一个类来负责创建其它类的实例,被创建的实例通常都具有共同的父类。

Factory Method模式

将对象的创建交由父类中定义的一个标准方法来完成,而不是其构造函数,究竟应该创建何种对象由具体的子类负责决定。

Abstract Factory模式

提供一个共同的接口来创建相互关联的多个对象。

 

一、Simple Factory模式:

1、  在这里,我们先定义水果(Fruit)接口:

public interface Fruit {

  void plant();  //水果是被种植的

  void enableEat();  //水果能吃

}

2、  苹果(Apple)是对水果(Fruit)接口的实现:

public class Apple implements Fruit{

  public void plant(){

    System.out.println("种苹果!");

  }

  public void enableEat(){

    System.out.println("苹果好吃!");

  }

}

3、  葡萄(Grape)是对水果(Fruit)接口的实现:

public class Grape implements Fruit{

  public void plant(){

    System.out.println("种葡萄!");

  }

  public void enableEat(){

    System.out.println("葡萄好吃!");

  }

}

4、  鸭梨(Pear)是对水果(Fruit)接口的实现:

public class Pear implements Fruit{

  public void plant(){

    System.out.println("种鸭梨!");

  }

  public void enableEat(){

    System.out.println("鸭梨好吃!");

  }

}

5、定义买水果(BuyFruit)这一过程类:

public class BuyFruit {

  /**

  * 简单工厂方法

  */

  public static Fruit buyFruit(String which){

    if (which.equalsIgnoreCase("apple")) {  //如果是苹果,则返回苹果实例

      return new Apple();

    }

    else if (which.equalsIgnoreCase("pear")){  //如果是鸭梨,则返回鸭梨实例

      return new Strawberry();

    }

    else if (which.equalsIgnoreCase("grape")) { //如果是葡萄,则返回葡萄实例

      return new Grape();

    }

    else{

      return null;

    }

  }

}

6、  编写测试类:

public class FruitTest {

  public static void  main(String args[]){

    BuyFruit buy = new BuyFruit();   //开始买水果这个过程

    buy.buyFruit("apple").enableEat(); //调用苹果的enableEat()方法

  }

}

7、  说明:

A:我要购买苹果,只需向工厂角色(BuyFruit)请求即可。而工厂角色在接到请求后,会自行判断创建和提供哪一个产品。

B:但是对于工厂角色(BuyFruit)来说,增加新的产品(比如说增加草莓)就是一个痛苦的过程。工厂角色必须知道每一种产品,如何创建它们,以及何时向客户端提供它们。换言之,接纳新的产品意味着修改这个工厂。

C:因此Simple Factory模式的开放性比较差。

有什么办法可以解决这个问题吗?那就需要Factory Method模式来为我们服务了。

二、Factory Method模式:

1、同样,我们先定义水果(Fruit)接口:

public interface Fruit {

  void plant();  //水果是被种植的

  void enableEat();  //水果能吃

}

2、苹果(Apple)是对水果(Fruit)接口的实现:

public class Apple implements Fruit{

  public void plant(){

    System.out.println("种苹果!");

  }

  public void enableEat(){

    System.out.println("苹果好吃!");

  }

}

3、葡萄(Grape)是对水果(Fruit)接口的实现:

public class Grape implements Fruit{

  public void plant(){

    System.out.println("种葡萄!");

  }

  public void enableEat(){

    System.out.println("葡萄好吃!");

  }

}

4、鸭梨(Pear)是对水果(Fruit)接口的实现:

public class Pear implements Fruit{

  public void plant(){

    System.out.println("种鸭梨!");

  }

  public void enableEat(){

    System.out.println("鸭梨好吃!");

  }

}

5、在这里我们将买水果(BuyFruit)定义为接口类:

public interface BuyFruit{

  /**

  * 工厂方法

  */

  public Fruit buyFruit();   //定义买水果这一过程

}

6、买苹果是(BuyApple)对买水果(BuyFruit)这个接口的实现

public class BuyApple implements BuyFruit{

  public Fruit buyFruit(){

    return new Apple();  //返回苹果实例

}

}

7、买鸭梨是(BuyPear)对买水果(BuyFruit)这个接口的实现

public class BuyPear implements BuyFruit{

  public Fruit BuyPear (){

    return new Pear();  //返回鸭梨实例

}

}

8、买葡萄是(BuyGrape)对买水果(BuyFruit)这个接口的实现

public class BuyGrape implements BuyFruit{

  public Fruit BuyGrape (){

    return new Grape ();  //返回葡萄实例

}

}

9、编写测试类:

public class FruitTest {

  public static void  main(String args[]){

    BuyApple buy = new BuyApple(); //开始买水果这个过程

    buy.buyFruit().enableEat();      //调用苹果的enableEat()方法

  }

}

10、说明:

A:工厂方法模式和简单工厂模式在结构上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。工厂方法模式可以允许很多具体工厂类从抽象工厂类中将创建行为继承下来,从而可以成为多个简单工厂模式的综合,进而推广了简单工厂模式。

B:工厂方法模式退化后可以变得很像简单工厂模式。设想如果非常确定一个系统只需要一个具体工厂类,那么就不妨把抽象工厂类合并到具体的工厂类中去。由于反正只有一个具体工厂类,所以不妨将工厂方法改成为静态方法,这时候就得到了简单工厂模式。C:如果需要加入一个新的水果,那么只需要加入一个新的水果类以及它所对应的工厂类。没有必要修改客户端,也没有必要修改抽象工厂角色或者其他已有的具体工厂角色。对于增加新的水果类而言,这个系统完全支持“开-闭”原则。

D:对Factory Method模式而言,它只是针对一种类别(如本例中的水果类Fruit),但如果我们还想买肉,那就不行了,这是就必须要Factory Method模式帮忙了。

三、Abstract Factory模式

1、同样,我们先定义水果(Fruit)接口:

public interface Fruit {

  void plant();  //水果是被种植的

  void enableEat();  //水果能吃

}

2、苹果(Apple)是对水果(Fruit)接口的实现:

public class Apple implements Fruit{

  public void plant(){

    System.out.println("种苹果!");

  }

  public void enableEat(){

    System.out.println("苹果好吃!");

  }

}

3、葡萄(Grape)是对水果(Fruit)接口的实现:

public class Grape implements Fruit{

  public void plant(){

    System.out.println("种葡萄!");

  }

  public void enableEat(){

    System.out.println("葡萄好吃!");

  }

}

4、鸭梨(Pear)是对水果(Fruit)接口的实现:

public class Pear implements Fruit{

  public void plant(){

    System.out.println("种鸭梨!");

  }

  public void enableEat(){

    System.out.println("鸭梨好吃!");

  }

}

5、  定义肉(Meat)接口:

public interface Meat {

  void feed();  //肉是喂养的

  void enableEat();  //肉能吃

}

6、  猪肉(BigMeat)是对肉(Meat)接口的实现:

public class BigMeat implements Meat{

  public void feed(){

    System.out.println("养猪!");

  }

  public void enableEat(){

    System.out.println("猪肉好吃!");

  }

}

7、  牛肉(CowMeat)是对肉(Meat)接口的实现:

public class CowMeat implements Meat {

  public void feed(){

    System.out.println("养牛!");

  }

  public void enableEat(){

    System.out.println("牛肉好吃!");

  }

}

8、  我们可以定义买货人(Buyer)接口:

public interface Buyer {

  /**

   * 买水果工厂方法

   */

  public Fruit buyFruit(Fruit whichFruit);

  /**

   * 买肉的工厂方法

   */

  public Meat buyMeat(Meat whichMeat);

}

9、  我(MyBuyer)是对买货人(Buyer)接口的实现:

public class MyBuyer implements Buyer{

  /**

   * 买水果工厂方法

   */

  public Fruit buyFruit(Fruit whichFruit){

     return whichFruit;

  }

  /**

   * 买肉的工厂方法

   */

  public Meat buyMeat(Meat whichMeat){

     return whichMeat;

  }

}

10、编写测试类:

public class MyBuyerAbstractTest {

  public static void  main(String args[]){

    Fruit apple = new Apple();   //苹果实例

    Meat big = new BigMeat();  //猪肉实例

    MyBuyer my = new MyBuyer();  //我是买者的实例

    my.buyFruit(apple).enableEat();  //我买苹果

    my.buyMeat(big).enableEat();    //我买猪肉

  }

}

11、说明:

A:抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象。这就是抽象工厂模式的用意。

B抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。

C:抽象工厂模式与工厂方法模式的最大区别就在于,工厂方法模式针对的是一个产品(Fruit)等级结构;而抽象工厂模式则需要面对多个产品等级结构(FruitMeat)

今天老婆让我去市场买一些水果,具体买什么自己定(哈,老婆放放权了!)。来到市场,我发现主要有一些水果:苹果(Apple),葡萄(Grape)和鸭梨(Pear)。

到底买什么好呢?我一阵思量。俗话说:“饭后一只烟,赛过活神仙。饭后吃苹果,西施见我躲。”为了老婆的漂亮,我决定买苹果。

好,言归正传,开始买吧!

 

主要有以下三种Factory模式:

Simple Factory模式

专门定义一个类来负责创建其它类的实例,被创建的实例通常都具有共同的父类。

Factory Method模式

将对象的创建交由父类中定义的一个标准方法来完成,而不是其构造函数,究竟应该创建何种对象由具体的子类负责决定。

Abstract Factory模式

提供一个共同的接口来创建相互关联的多个对象。

 

一、Simple Factory模式:

1、  在这里,我们先定义水果(Fruit)接口:

public interface Fruit {

  void plant();  //水果是被种植的

  void enableEat();  //水果能吃

}

2、  苹果(Apple)是对水果(Fruit)接口的实现:

public class Apple implements Fruit{

  public void plant(){

    System.out.println("种苹果!");

  }

  public void enableEat(){

    System.out.println("苹果好吃!");

  }

}

3、  葡萄(Grape)是对水果(Fruit)接口的实现:

public class Grape implements Fruit{

  public void plant(){

    System.out.println("种葡萄!");

  }

  public void enableEat(){

    System.out.println("葡萄好吃!");

  }

}

4、  鸭梨(Pear)是对水果(Fruit)接口的实现:

public class Pear implements Fruit{

  public void plant(){

    System.out.println("种鸭梨!");

  }

  public void enableEat(){

    System.out.println("鸭梨好吃!");

  }

}

5、定义买水果(BuyFruit)这一过程类:

public class BuyFruit {

  /**

  * 简单工厂方法

  */

  public static Fruit buyFruit(String which){

    if (which.equalsIgnoreCase("apple")) {  //如果是苹果,则返回苹果实例

      return new Apple();

    }

    else if (which.equalsIgnoreCase("pear")){  //如果是鸭梨,则返回鸭梨实例

      return new Strawberry();

    }

    else if (which.equalsIgnoreCase("grape")) { //如果是葡萄,则返回葡萄实例

      return new Grape();

    }

    else{

      return null;

    }

  }

}

6、  编写测试类:

public class FruitTest {

  public static void  main(String args[]){

    BuyFruit buy = new BuyFruit();   //开始买水果这个过程

    buy.buyFruit("apple").enableEat(); //调用苹果的enableEat()方法

  }

}

7、  说明:

A:我要购买苹果,只需向工厂角色(BuyFruit)请求即可。而工厂角色在接到请求后,会自行判断创建和提供哪一个产品。

B:但是对于工厂角色(BuyFruit)来说,增加新的产品(比如说增加草莓)就是一个痛苦的过程。工厂角色必须知道每一种产品,如何创建它们,以及何时向客户端提供它们。换言之,接纳新的产品意味着修改这个工厂。

C:因此Simple Factory模式的开放性比较差。

有什么办法可以解决这个问题吗?那就需要Factory Method模式来为我们服务了。

二、Factory Method模式:

1、同样,我们先定义水果(Fruit)接口:

public interface Fruit {

  void plant();  //水果是被种植的

  void enableEat();  //水果能吃

}

2、苹果(Apple)是对水果(Fruit)接口的实现:

public class Apple implements Fruit{

  public void plant(){

    System.out.println("种苹果!");

  }

  public void enableEat(){

    System.out.println("苹果好吃!");

  }

}

3、葡萄(Grape)是对水果(Fruit)接口的实现:

public class Grape implements Fruit{

  public void plant(){

    System.out.println("种葡萄!");

  }

  public void enableEat(){

    System.out.println("葡萄好吃!");

  }

}

4、鸭梨(Pear)是对水果(Fruit)接口的实现:

public class Pear implements Fruit{

  public void plant(){

    System.out.println("种鸭梨!");

  }

  public void enableEat(){

    System.out.println("鸭梨好吃!");

  }

}

5、在这里我们将买水果(BuyFruit)定义为接口类:

public interface BuyFruit{

  /**

  * 工厂方法

  */

  public Fruit buyFruit();   //定义买水果这一过程

}

6、买苹果是(BuyApple)对买水果(BuyFruit)这个接口的实现

public class BuyApple implements BuyFruit{

  public Fruit buyFruit(){

    return new Apple();  //返回苹果实例

}

}

7、买鸭梨是(BuyPear)对买水果(BuyFruit)这个接口的实现

public class BuyPear implements BuyFruit{

  public Fruit BuyPear (){

    return new Pear();  //返回鸭梨实例

}

}

8、买葡萄是(BuyGrape)对买水果(BuyFruit)这个接口的实现

public class BuyGrape implements BuyFruit{

  public Fruit BuyGrape (){

    return new Grape ();  //返回葡萄实例

}

}

9、编写测试类:

public class FruitTest {

  public static void  main(String args[]){

    BuyApple buy = new BuyApple(); //开始买水果这个过程

    buy.buyFruit().enableEat();      //调用苹果的enableEat()方法

  }

}

10、说明:

A:工厂方法模式和简单工厂模式在结构上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。工厂方法模式可以允许很多具体工厂类从抽象工厂类中将创建行为继承下来,从而可以成为多个简单工厂模式的综合,进而推广了简单工厂模式。

B:工厂方法模式退化后可以变得很像简单工厂模式。设想如果非常确定一个系统只需要一个具体工厂类,那么就不妨把抽象工厂类合并到具体的工厂类中去。由于反正只有一个具体工厂类,所以不妨将工厂方法改成为静态方法,这时候就得到了简单工厂模式。C:如果需要加入一个新的水果,那么只需要加入一个新的水果类以及它所对应的工厂类。没有必要修改客户端,也没有必要修改抽象工厂角色或者其他已有的具体工厂角色。对于增加新的水果类而言,这个系统完全支持“开-闭”原则。

D:对Factory Method模式而言,它只是针对一种类别(如本例中的水果类Fruit),但如果我们还想买肉,那就不行了,这是就必须要Factory Method模式帮忙了。

三、Abstract Factory模式

1、同样,我们先定义水果(Fruit)接口:

public interface Fruit {

  void plant();  //水果是被种植的

  void enableEat();  //水果能吃

}

2、苹果(Apple)是对水果(Fruit)接口的实现:

public class Apple implements Fruit{

  public void plant(){

    System.out.println("种苹果!");

  }

  public void enableEat(){

    System.out.println("苹果好吃!");

  }

}

3、葡萄(Grape)是对水果(Fruit)接口的实现:

public class Grape implements Fruit{

  public void plant(){

    System.out.println("种葡萄!");

  }

  public void enableEat(){

    System.out.println("葡萄好吃!");

  }

}

4、鸭梨(Pear)是对水果(Fruit)接口的实现:

public class Pear implements Fruit{

  public void plant(){

    System.out.println("种鸭梨!");

  }

  public void enableEat(){

    System.out.println("鸭梨好吃!");

  }

}

5、  定义肉(Meat)接口:

public interface Meat {

  void feed();  //肉是喂养的

  void enableEat();  //肉能吃

}

6、  猪肉(BigMeat)是对肉(Meat)接口的实现:

public class BigMeat implements Meat{

  public void feed(){

    System.out.println("养猪!");

  }

  public void enableEat(){

    System.out.println("猪肉好吃!");

  }

}

7、  牛肉(CowMeat)是对肉(Meat)接口的实现:

public class CowMeat implements Meat {

  public void feed(){

    System.out.println("养牛!");

  }

  public void enableEat(){

    System.out.println("牛肉好吃!");

  }

}

8、  我们可以定义买货人(Buyer)接口:

public interface Buyer {

  /**

   * 买水果工厂方法

   */

  public Fruit buyFruit(Fruit whichFruit);

  /**

   * 买肉的工厂方法

   */

  public Meat buyMeat(Meat whichMeat);

}

9、  我(MyBuyer)是对买货人(Buyer)接口的实现:

public class MyBuyer implements Buyer{

  /**

   * 买水果工厂方法

   */

  public Fruit buyFruit(Fruit whichFruit){

     return whichFruit;

  }

  /**

   * 买肉的工厂方法

   */

  public Meat buyMeat(Meat whichMeat){

     return whichMeat;

  }

}

10、编写测试类:

public class MyBuyerAbstractTest {

  public static void  main(String args[]){

    Fruit apple = new Apple();   //苹果实例

    Meat big = new BigMeat();  //猪肉实例

    MyBuyer my = new MyBuyer();  //我是买者的实例

    my.buyFruit(apple).enableEat();  //我买苹果

    my.buyMeat(big).enableEat();    //我买猪肉

  }

}

最近想买一台电脑用于学习,因此我就去了一家电脑公司,经过分析,选用了下面的配置:

CPU    P2.4

主板   Intel

硬盘   80G

。。。

买过电脑的朋友可能都知道,我们选好配置后,电脑公司就会有专门的组装师(Assembler)来给我们装机。电脑(Computer)就是由这些东西(我们称之为Part)组成的。学过经济学的朋友可能都知道,如果这台组装好的电脑不卖掉,那它就不是商品(Commodity),而仅仅是台电脑而已。

1、  在这里,我们先定义商品(Commodity)类:

public class Commodity {

  String commodity =”";

  public Commodity (Part partA,Part partB,Part partC) {//由各个部分组成

    this. commodity = partA.part+”\n”;

    this. commodity = product+partB.part+”\n”;

    this. commodity = product+partC.part;

    System.out.println(“我的机器配置为:\n”+ commodity);

  }

}

2、  下来我们再定义电脑的组成部分(Part)类:

public class Part {

  String part=”";

  public Part(String part){

    this.part = part;

  }

}

3、  我们把电脑(Computer)定义成一个接口类:

public interface Computer {

//组装部件A 比如CPU

  void buildPartA();

 

//组装部件B  比如主板

  void buildPartB();

 

//组装部件C  比如硬盘

  void buildPartC();

 

//返回最后组装成品结果 (返回最后组装好的电脑)

//成品的组装过程不在这里进行,而是由组装师(Assembler)类完成的。

//从而实现了过程和部件的分离

  Product getProduct();

}

4、  定义电脑的组装师(Assembler)类:

public class Assembler {

  private Computer computer;

  public Assembler(Computer computer) {  //主要任务是装电脑

    this.computer = computer;

  }

 

// 将部件partA partB partC最后组成复杂对象

//这里是将主板、CPU和硬盘组装成PC的过程

  public void construct() {

    computer.buildPartA();

    computer.buildPartB();

    computer.buildPartC();

  }

}

5、  我的电脑是对电脑(Computer)接口的具体实现,因此再定义MyComputer实现类:

public class MyComputer implements Computer {

  Part partA, partB, partC;

  public void buildPartA() {

    partA = new Part(“P42.4 CPU”);

  }

  public void buildPartB() {

    partB = new Part(“Inter 主板“);

  }

  public void buildPartC() {

    partC = new Part(“80G硬盘“);

  }

  public Product getProduct() {

//返回最后组装成品结果

    Commodity myComputer = new Commodity (partA,partB,partC);

    return myComputer;

  }

}

6、  编写测试类:

public class MyComputerTest {

  public static void  main(String args[]){

    MyComputer myComputer = new MyComputer();      //组装我的电脑

    Assembler assembler = new Assembler( myComputer );  //派某一位组装师

    assembler.construct();    //组装师进行组装过程

    Commodity commodity = myComputer.getProduct();   //卖给我的电脑(商品)

  }

}

7、说明:

A代码只用来学习Builder模式,要运行的话,必须要做一点改动。

B将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。因为每个人的电脑配置可能都是不同的。

C:我们使用Builer是为了构建复杂对象的过程和它的部件解耦,也就是说将过程分的尽可能细,而且每一部分只用完成自己的功能即可(各司其职嘛)。

今年过年不收礼,收礼只收脑白金。听到这暗示性的广告词,我的脑袋突然一亮。因为最近因为要办某事,必须要给单位的领导要表示一下。到底送什么,还真让人头痛,还好有脑白金,奶奶的。。。,腐败啊,罪过!

首先要对送礼的对象进行分析,单位有两个领导,一正,一副。因此给不同的领导送的礼也是不同的(哈,收入要和产出成正比吗),好了言归正传。

1、在这里,先把领导定义成一个接口类:

public interface Leader{

  public void accept(Visitor visitor);  //主要任务—-visitor(拜访者)的礼

}

在把拜访者定义成另一个接口类:

public interface Visitor

{

   public void visitFirstHand(FirstHand first);  //拜访一把手(带的礼物)

   public void visitSecondHand(SecondHand second);  //拜访二把手(带的礼物)

   public void visitCollection(Collection collection);  //判断是拜访一把手还是二把手

}

2、下面我们要对这两个接口进行实现:

A:一把手

public class FirstHand implements Leader {

  private String value; //注意此处是String

  public FirstHand (String string) {//一把手的构造函数

    value = string;

  }

 

  public String getValue() { //获得礼物

    return value;

  }

 

//定义accept的具体内容 这里是很简单的一句调用

  public void accept(Visitor visitor) {

    visitor.visitFirstHand (this);  //接收拜访人送的礼

  }

}

B:二把手

public class SecondHand implements Leader {

  private Float value; //注意此处是Float

  public SecondHand (Float string) {//二把手的构造函数

    value = string;

  }

 

  public Float getValue() { //获得礼物

    return value;

  }

 

//定义accept的具体内容 这里是很简单的一句调用

  public void accept(Visitor visitor) {

    visitor.visitFirstHand (this);  //接收拜访人送的礼

  }

}

C:拜访人(我)

public class visitMe implements Visitor{

   public void visitCollection(Collection collection) {

      Iterator iterator = collection.iterator();

      while (iterator.hasNext()) {

               Object o = iterator.next();

         if (o instanceof Leader)         //判断要送给哪个领导

          ((Leader)o).accept(this);     //不同的领导进入不同的实现类

      }

    }

    public void visitFirstHand (FirstHand first) {

      System.out.println(“送的礼是:“+ first.getValue());

    }

    public void visitSecondHand (SecondHand second) {

      System.out.println(“送的礼是:” + second.getValue());

    }

}

3、编写测试类:

public class test {

   public static void  main(String args[]){

     Visitor visitor = new visitMe ();

     FirstHand present = new FirstHand (“十盒脑白金“);

     visitor.visitFirstHand (present);

 

     Collection list = new ArrayList();

     list.add(new FirstHand (“十盒脑白金“));

     list.add(new SecondHand (new Float(“一斤小点心“))); //为了说明不同,如要运行,要做类型转换。

     visitor.visitCollection(list);

 

   }

 

4、说明:

A代码只用来学习Visitor模式,要运行的话,必须要做一点改动。

BFirstHandSecondHand只是一个个具体实现,实际上还可以拓展为更多的实现,整个核心奥妙在accept方法中,在遍历Collection时,通过相应的accept方法调用具体类型的被访问者。这一步确定了被访问者类型

C:使用访问者模式是对象群结构中(Collection) 中的对象类型很少改变,也就是说领导很少变化。