因为原先发的贴子(1~4章)太长了,打开非常慢,所以决定第五章单独开一个贴子吧。为了方便大家阅读,我也提供了pdf版,您可以在下面的地址下载到:http://www.designersky.com/360/My360TeacherDay_Beta2.pdf因为csdn不能贴图,也不能显示尾注,如果您看完有疑问的话,建议您下载pdf版
-----------------------------------------------------------------------第五章 设计模式及在Java类库中的应用
先来说说设计模式(Design Pattern)在软件编码中所占的地位,首先你需要意识到设计模式大部分是前人经验的积累,是一套经过很多人验证的,成功的解决问题的方案,每一个模式都有自己的适用范围。如果放在武侠小说中,设计模式就是一些精妙的招式,招式虽妙,但能否击败敌人,还要看能否灵活运用,这需要一些经验。我们都知道“无招胜有招”这句话,掌握罕见的招式并不是最高境界,能够掌握武术的原理,随心所欲的创造招数才是大师的境界。显然,必须在“有招”的境界中经过相当长时间的沉寂,才能过渡到“无招”的境界。在后面一章“类的设计原则”中我们会说到“无招”的境界。 本书不会对各个模式做过于深入的介绍,如果想了解更多,推荐读者看看阎宏博士写的《Java与模式》,此书虽不能算同种类书中最好的  ,但确实是中国人自己写的为数不多的好的技术书籍之一,此书中对于中国传统文化的重新认识也是我最感兴趣的。
第一招:单态模式(Singleton)
如果希望在系统运行的过程中,一个类始终只存在一个实例时,就可以用单态模式。比如对一个程序而言,当前的显示屏、CPU、剪切板始终只有一个,应该用单态模式  来描述这些类。 在一个类中应用单态模式有以下两种常见的方式,称之为“懒汉式”和“饿汉式”。懒汉式的Singleton 饿汉式的Singleton
public class Test {
    private Test() { }    private static Test instance = new Test();    public static Test getInstance() {
        return instance;
    }
} public class Test {
    private Test() { }    private static Test instance = null;    public synchronized static Test getInstance() {
        if (instance == null) {
            instance = new Test();
        }
        return instance;
    }
}按照下面三个步骤就可以实现一个标准的Singleton:
①. 将构造函数声明成私有(从语法上防止其他类实例化)
②. 声明一个私有静态的自身对象
③. 声明一个共有静态的方法,返回自身对象上面的两端代码在2,3步略有不同,饿汉式可以确保当用户调用getInstance方法后才实例化自身对象,如果实例化的代价很昂贵的话,那么饿汉式可以避免无必要的实例化。但是在多用户的系统中可能会重复实例化,所以方法声明成了synchoronized,会影响一些效率。单态模式在Java类库中的应用是java.lang.Runtime这个类。还有两个类很像,但不是单态模式:java.lang.Math和java.awt.Toolkit,原因是前者没有返回唯一的自身对象,后者返回的是子类对象。
第二招:简单工厂(Simple Factory)
简单工厂一般用来返回不同的子类对象。 举个例子,我想开一家店,卖各种电脑配件,包括CPU,显示器,内存,硬盘等,产品结构图如下: 
图:产品结构 很容易看出,CPU是一个抽象类,有Intel的CPU和AMD的CPU两个子类,显示器则分为了电子管显示器和液晶显示器。好了,现在如果有顾客要来买CPU,该怎么处理呢?你不能直接给顾客一个CPU,因为CPU是抽象类,没有对象,必须要问“您需要什么样的CPU ?”,如果顾客答“我要Intel的”,就可以实例化一个IntelCPU对象给顾客了。 这个过程看上去理所当然,但是隐藏了一个问题,顾客必须清楚地知道存在IntelCPU这个子类,并了解它的特点,然而并非每个顾客都对自己要买的产品了如指掌。记得若干年前,我和大学同学准备去肯德基开开洋荤,第一次来这种“高档”的餐厅难免令人有些紧张,好不容易排到柜台前,我谨慎的对服务员说:“要两个汉堡”,服务员立刻问道:“请问要哪种汉堡?”,我立刻精神崩溃,啥,汉堡还分好几种?!我只得小声问道:“都有哪几种?”,服务员飞快的报出“香辣鸡腿堡、劲脆鸡腿堡、田园脆鸡堡……”,可怜的我因为紧张一个都没有记住,只能更小声的说“随便吧”…… 无论我的第一次肯德基经历会不会引起你的共鸣,都应该从这个例子中看出,强迫使用者记住每一个子类的名称和特性是不合适的,并且子类总是容易的变化的,比如现在CPU流行Intel、AMD,也许十年以后就流行龙芯、威盛,这时使用者的记忆就失去了价值,必须重新熟悉新的产品,可以使用简单类工厂来解决这个问题: 
图:使用简单类工厂
SimpleFactory.java
public class SimpleFactory {
  
  public static final int 便宜 = 0;
  public static final int 贵 = 1;
  public static final int 随便 = 2;
  
  public static CPU getCPU(int s) throws NoSuchThingException{
    switch (s) {
      case 便宜:
        return new AmdCPU();
      case 贵:
        return new IntelCPU();
      case 随便:
        return new IntelCPU();
      default:
        throw new NoSuchThingException();
    }
  }
  
  public static Display getDisplay(int s) throws NoSuchThingException{
    switch (s) {
      case 便宜:
        return new CRT();
      case 贵:
        return new LCD();
      case 随便:
        return new LCD();
      default:
        throw new NoSuchThingException();
    }
  }} 有了这个SimpleFactory,当我们想得到一个CPU的子类实例时,就不需要记住子类的名称,只要传入常量 “便宜”或者“贵”就好(甚至还可以传“随便”),SimpleFactory会为我们实例化相应的子类对象。而且,即使子类发生了变化,比如以后龙芯取代了Intel,只需要改变SimpleFactory就可以了。 总之,一个简单工厂可以帮助我们实例化各种子类对象,使用简单工厂我们就不需要熟悉子类的具体结构,只需要了解父类就可以了,而且当子类产生变化时(增加或减少子类),我们的程序不会受到影响。 注意:使用SimpleFactory获取对象时,应该用CPU c = SimpleFactory.getCPU(贵)这样的语句,而不是IntelCPU c = SimpleFactory.getCPU(贵),否则就丧失了简单工厂的意义。由此还可以推出,在子类中不应当添加父类中没有定义的公有方法。 一个简单工厂一般有如下几个特征:
①. 存在一个或多个产品簇(如本例的CPU产品簇,Display产品簇)
②. 创建子类的方法其返回类型为父类类型(如getCPU方法返回CPU类型)
③. 创建子类的方法一般需要传入一个标识(如getCPU方法传入一个整型) 简单工厂在Java类库中被大量使用,比如swing中的控件可以添加边框(Border),各种不用的边框子类实例可以通过简单工厂BorderFactory中的方法来得到。

解决方案 »

  1.   

    public synchronized static Test getInstance() {
            if (instance == null) {
                instance = new Test();
            }
            return instance;
        }
    synchronized static需要同时出现么?
      

  2.   


    另外一道:
    100台割草机割一块地需要200天,200台割草机割同一块地需要n天, 已知n < 100, 如果要设计一个系统,当输入n时,显示该草坪种的是什么草。您有什么解决
      

  3.   

    回: jfy3d(剑事) 
    synchronized static需要同时出现,static并不能保证给方法上锁
      

  4.   

    回:plokmmm(人从众)你在和这个贴子说话嘛?
      

  5.   

    public synchronized static Test getInstance() {
            if (instance == null) {
                instance = new Test();
            }
            return instance;
    }Singleton的工厂类有上锁的必要吗?
      

  6.   

    回复: FaJa(FaJa) 
    如果不上锁,在多用户并发访问的情况下有可能会将自己实例化两次,具体详情可以看一下《Java与模式》中的讨论
      

  7.   

    如果不上锁,在多用户并发访问的情况下有可能会将自己实例化两次,具体详情可以看一下《Java与模式》中的讨论上锁可能会被实例化两次,但是上锁所带来的时间开销对于这而言是不足以补偿的,况且多出的实例在垃圾收集器运行时会回收。
      

  8.   

    呵呵,确实如你所说,上缩会带来时间的开销,所以在java中一般不使用饿汉式的singleton至于你所说的“多出的实例在垃圾收集器运行时会回收”,这种想法有点可怕了,在某些应用中也许将自己实例化两次会带来不可预料的后果。
      

  9.   

    第三招:工厂方法(Factory Method)
    刚才我们使用简单工厂描述了一个卖电脑配件的小商铺,顾客要买东西只需要说自己想要便宜的还是贵的就可以得到相应的产品,但是其中有一个缺陷:就是所有的产品都在SimpleFactory类中被创建,这是个不折不扣的杂货铺,当产品的种类太多时,SimpleFactory会变得臃肿而难以维护。使用工厂方法可以解除不相关产品间的耦合。 我们考虑卖掉这个小商铺,改而开一系列的专卖店,比如CPU专卖店只卖CPU,Display专卖店只卖显示器,如下图所示: 
    图:两个专卖店 在这个例子中,我们实际上制作了两个简单工厂类:CPU专卖店和显示器专卖店,他们各司其职,不会相互影响,当新增加一种CPU的子类时,只需要改动CPU专卖店。接下来,我们应该能够看出CPU专卖店和显示器专卖店都是专卖店一种,于是我们再抽象出“专卖店”这个父类,让它具有抽象方法“getProduct”用来卖产品(专卖店也可以是接口)。 
    图:添加了抽象类专卖店和接口Product 读者可以想一想,Product接口在这里起的作用。一个工厂方法一般有如下几个特征:
    ①. 存在多个产品簇
    ②. 每一个产品簇都会对应一个简单工厂类
    ③. 产品簇和简单工厂类一般都有各自的接口Java类库中的例子有聚集:Java的聚集都直接或间接的实现了java.util.Collection接口(如ArrayList),Collection接口规定了iterator()方法,返回实现了Iterator接口的子类。
      

  10.   

    第四招:抽象工厂(Factory Method)
    请先看下面这段对话:顾客:我要一个汉堡。
    侍者:先生你可以尝尝我们新出的驴肉大餐,味道很不错。
    顾客:嗯……还是要汉堡吧。
    侍者:请问要鸡肉的还是牛肉的?
    顾客:嗯……鸡肉的吧。
    侍者:要辣的还是不辣的?
    顾客:嗯……辣的吧,再要一包薯条。
    侍者:要大包、中包还是小包?
    顾客:嗯……中包。
    侍者:好的,先生需要什么饮品吗?
    顾客:好,来杯可乐吧。
    侍者:要中杯、大杯还是小杯?
    顾客:嗯……中杯吧。
    侍者:只需要两元钱可以将薯条和可乐加大,需要吗?
    顾客:加大?……不用了。
    侍者:好的,1个汉堡鸡肉不辣的,1包中薯,1杯中可,请问先生您还需要别的吗?
    顾客:不用了……不知道读者看完这段罗嗦而又真实的对话有什么感想,除非服务员长的很漂亮,否则我可不愿意每次点餐都将上列对话重复一遍。还好快餐店都提供套餐这个东西:顾客:要一份2号餐。
    侍者:先生你可以尝尝我们新出的驴肉大餐,味道很不错。 
    顾客:不用了。
    侍者:好的,还需要其它的吗?
    顾客:不需要。相对简洁的对话,而且2号餐中已经包含了“1个汉堡鸡肉不辣的,1包中薯,1杯中可”这一系列食品。可见,当我们对产品不熟悉,又需要得到一系列产品时,“套餐”无疑是一个好的解决方案。回到我们刚才专卖店的例子,购买散件的顾客会觉得专卖店很不错,但是如果想DIY一台电脑,就必须分别光顾每一个专卖店,这太麻烦了。于是再卖掉着一系列专卖店,我们成立品牌公司,推出面向高端的型号“飞天”和面向工薪阶层的型号“遁地”。自然“飞天”会采用贵一些的Intel CPU和液晶显示器,而“遁地”采用AMD CPU和电子管显示器。 
    图:两种型号的品牌电脑从上图可以看出,顾客只需要在 “飞天”和“遁地”这两种型号之间作一次选择,就可以得到一系列的部件:CPU、显示器、主板、硬盘等,当然,选择不同型号得到的具体产品是不一样的。可以得出结论:当需要得到一系列产品时,可以采用抽象工厂模式。上图中的接口“品牌电脑”就是一个抽象工厂,“飞天”、“遁地”两个型号则是两个具体的工厂,具体的工厂会返回一系列具体的产品子类。一个抽象工厂一般有如下几个特征:
    ①. 存在一个抽象工厂和多个具体工厂
    ②. 存在多个成系列的产品簇
    ③. 每个具体工厂返回一系列的产品Java类库中的采用抽象工厂的典型就是图形包awt的设计,awt中的控件并没有完全使用java语言实现,而是采用一种Peer to Peer的方式显示的,示例图如下: 
    图:awt中的抽象工厂
      

  11.   

    这两天做了一个改版了网站,专门负责解决应聘问题,请大家看看,提提意见地址:http://www.designersky.com我当老师的360天(Beta 2)设的连接依然有效地址:http://www.designersky.com/360/360.htm