The NoteBook of EricKong

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  611 Posts :: 1 Stories :: 190 Comments :: 0 Trackbacks

#

确保一个特定的类只有一个对象被实例化。

Singleton:用于单线程应用程序
public class Singleton {

  private Singleton(){}

  //在自己内部定义自己一个实例,是不是很奇怪?
  //注意这是private 只供内部调用

  private static Singleton instance = new Singleton();

  //这里提供了一个供外部访问本class的静态方法,可以直接访问  
  public static Singleton getInstance() {
    return instance;   
   }

 

Double-Checked Locking:用于多线程应用程序

public class Singleton {
  private static Singleton instance = null;

  public static synchronized Singleton getInstance() {

  //这个方法比上面有所改进,不用每次都进行生成对象,只是第一次     
  //使用时生成实例,提高了效率!
  if (instance==null)
    instance=new Singleton();
  return instance;   }
}
 

posted @ 2010-09-09 14:21 Eric_jiang 阅读(194) | 评论 (0)编辑 收藏

原型模式(Prototype)属于创建型模式,实现了对象的创建;具体而言,它实现了对象的复制(拷贝)。

关于对象的复制,我们需要考虑以下两个问题:

1.对象实时状态的复制;

2.对象引用成员变量的复制。

如果通过new方式来实例化对象,只能得到对象的初始状态,这显然不行。在Java中,所有类都继承于Object类,而Object有一个clone()方法。通过查看JDK文档,该clone()方法虽然能够实现实时状态的复制(解决了问题1),但是只能实现“浅拷贝”,即只能实现基本数据类型(包含String)的拷贝,不能实现引用数据类型的拷贝,并不能满足原型模式的要求(问题2不能得到解决)。


怎么办?重写类的clone()方法,通过对象的写入写出来实现“深拷贝”和实时状态复制。重写(Override)需要遵循的条件是:方法名,参数列表和父类一致;返回值是父类返回值的子类。重写clone方法完全满足了条件。(无参数,方法名一致,返回的所有类都是Object的子类;当然可以不重写,方法名改用其它,但是不建议。)

可以想象,若一个类含有若干个引用数据类型(类),而该引用数据类型(类)中又含有若干个引用数据类型…这样嵌套下去,需要复制的就不是一个类,而是一个“类树”(树结构)。Java通过将对象序列化到文件或内存,底层封装了对类树的遍历和复制过程。这个机制已经封装,我们无需了解。

考虑这样一个应用场景:

一个RPG游戏,英雄类含有两个成员变量:一个是英雄名字,为普通成员变量;一个是英雄属性,为引用成员变量(封装了英雄的状态,如生命值、魔法值)。为了实现对象实时拷贝的高效率,我们选择将对象序列化到内存(不是文件(硬盘))。通过输入输出流实现对象的“深拷贝”。

具体的实现代码如下:


package com.csufox.Prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

class Property implements java.io.Serializable{
    int blood = 100;  //生命值
    int magic = 100;  //魔法值
}

class Hero implements java.io.Serializable{ //需要实现序列化接口
    String name = "英雄";
    Property property = new Property(); //含有引用成员,需要“深”拷贝

    public Hero clone(){
        Hero copy = null;
        try{
            //实例化ObjectOutputStream,写入字节数组输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            byte[] data = baos.toByteArray();  //data是字节数组,存储着对象的拷贝


            //实例化ObjectInputStream,从字节数组输入流中读取对象
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(bais);
            copy = (Hero)ois.readObject();
        }catch(Exception ex){ex.printStackTrace();}
        return copy;
    }
}

public class Prototype {
    public static void main(String[] args) throws Exception {
        Hero hero = new Hero();
        hero.property.blood -= 20;  //原英雄状态发生改变,生命值减少20
        Hero copy = hero.clone();
        System.out.println(copy.property.blood);  //输出“80”,说明实现了实时拷贝和深拷贝
    }
}
 

由上述代码可知,原型模式具有以下特点:

实现Serializable序列化接口;
在类中重写clone()方法(由Object继承而来);
通过Java I/0,将对象写入内存,然后再从内存读取(反序列化)。

posted @ 2010-09-09 14:13 Eric_jiang 阅读(370) | 评论 (0)编辑 收藏

Builder模式定义:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.

Builder模式是一步一步创建一个复杂的对象,它允许用户可以只通过指定复杂对象的类型和内容就可以构建它们.用户不知道内部的具体构建细节.Builder模式是非常类似抽象工厂模式,细微的区别大概只有在反复使用中才能体会到.

为何使用?
是为了将构建复杂对象的过程和它的部件解耦.注意: 是解耦过程和部件.

因为一个复杂的对象,不但有很多大量组成部分,如汽车,有很多部件:车轮 方向盘 发动机还有各种小零件等等,部件很多,但远不止这些,如何将这些部件装配成一辆汽车,这个装配过程也很复杂(需要很好的组装技术),Builder模式就是为了将部件和组装过程分开.

如何使用?
首先假设一个复杂对象是由多个部件组成的,Builder模式是把复杂对象的创建和部件的创建分别开来,分别用Builder类和Director类来表示.

首先,需要一个接口,它定义如何创建复杂对象的各个部件:
 
public interface Builder {

  //创建部件A  比如创建汽车车轮
  void buildPartA();
  //创建部件B 比如创建汽车方向盘
  void buildPartB();
  //创建部件C 比如创建汽车发动机
  void buildPartC();

  //返回最后组装成品结果 (返回最后装配好的汽车)
  //成品的组装过程不在这里进行,而是转移到下面的Director类中进行.
  //从而实现了解耦过程和部件
  Product getResult();


 

用Director构建最后的复杂对象,而在上面Builder接口中封装的是如何创建一个个部件(复杂对象是由这些部件组成的),也就是说Director的内容是如何将部件最后组装成成品:
  
public class Director {

  private Builder builder;

  public Director( Builder builder ) {
    this.builder = builder;
  }
  // 将部件partA partB partC最后组成复杂对象
  //这里是将车轮 方向盘和发动机组装成汽车的过程
  public void construct() {
    builder.buildPartA();
    builder.buildPartB();
    builder.buildPartC();

  }


 

Builder的具体实现ConcreteBuilder:
通过具体完成接口Builder来构建或装配产品的部件;
定义并明确它所要创建的是什么具体东西;
提供一个可以重新获取产品的接口:

 
public class ConcreteBuilder implements Builder {

  Part partA, partB, partC;
  public void buildPartA() {
    //这里是具体如何构建partA的代码

  };
  public void buildPartB() {
    //这里是具体如何构建partB的代码
  };
   public void buildPartC() {
    //这里是具体如何构建partB的代码
  };
   public Product getResult() {
    //返回最后组装成品结果
  };


}
 

复杂对象:产品Product:

public interface Product { } 


复杂对象的部件:

public interface Part { }

 

我们看看如何调用Builder模式:

ConcreteBuilder builder = new ConcreteBuilder();
Director director = new Director( builder );

director.construct();
Product product = builder.getResult();

Builder模式的应用
在Java实际使用中,我们经常用到"池"(Pool)的概念,当资源提供者无法提供足够的资源,并且这些资源需要被很多用户反复共享时,就需要使用池.

"池"实际是一段内存,当池中有一些复杂的资源的"断肢"(比如数据库的连接池,也许有时一个连接会中断),如果循环再利用这些"断肢",将提高内存使用效率,提高池的性能.修改Builder模式中Director类使之能诊断"断肢"断在哪个部件上,再修复这个部件.

 

 

posted @ 2010-09-09 13:58 Eric_jiang 阅读(195) | 评论 (0)编辑 收藏

在设计模式中,Factory Method也是比较简单的一个,但应用非常广泛,EJB,RMI,COM,CORBA,Swing中都可以看到此模式的影子,它是最重要的模式之一.在很多地方我们都会看到xxxFactory这样命名的类,那么,什么是Factory Method,为什么要用这个模式,如何用Java语言来实现该模式,这就是本文想要带给大家的内容.
基本概念

FactoryMethod是一种创建性模式,它定义了一个创建对象的接口,但是却让子类来决定具体实例化哪一个类.当一个类无法预料要创建哪种类的对象或是一个类需要由子类来指定创建的对象时我们就需要用到Factory Method 模式了.简单说来,Factory Method可以根据不同的条件产生不同的实例,当然这些不同的实例通常是属于相同的类型,具有共同的父类.Factory Method把创建这些实例的具体过程封装起来了,简化了客户端的应用,也改善了程序的扩展性,使得将来可以做最小的改动就可以加入新的待创建的类. 通常我们将Factory Method作为一种标准的创建对象的方法,当发现需要更多的灵活性的时候,就开始考虑向其它创建型模式转化

 

简单分析

图1是Factory Method 模式的结构图,这里提供了一些术语,让我们可以进行更方便的描述:


Product: 需要创建的产品的抽象类.
ConcreteProduct: Product的子类,一系列具体的产品.
Creator: 抽象创建器接口,声明返回Product类型对象的Factory Method.
ConcreteCreator: 具体的创建器,重写Creator中的Factory Method,返回ConcreteProduct类型的实例.

图1: Factory Method 模式结构
 

由此可以清楚的看出这样的平行对应关系: Product <====> Creator ; ConreteProduct <====> ConreteCreator

抽象产品对应抽象创建器,具体产品对应具体创建器.这样做的好处是什么呢?为什么我们不直接用具体的产品和具体的创建器完成需求呢?实际上我们也可以这样做.但通过Factory Method模式来完成,客户(client)只需引用抽象的Product和Creater,对具体的ConcreteProduct和ConcreteCreator可以毫不关心,这样做我们可以获得额外的好处:

首先客户端可以统一从抽象创建器获取产生的实例,Creator的作用将client和产品创建过程分离开来,客户不用操心返回的是那一个具体的产品,也不用关心这些产品是如何创建的.同时,ConcreteProduct也被隐藏在Product后面,ConreteProduct继承了Product的所有属性,并实现了Product中定义的抽象方法,按照Java中的对象造型(cast)原则,通过ConcreteCreator产生的ConcreteProduct可以自动的上溯造型成Product.这样一来,实质内容不同的ConcreteProduct就可以在形式上统一为Product,通过Creator提供给client来访问.
其次,当我们添加一个新的ConcreteCreator时,由于Creator所提供的接口不变,客户端程序不会有丝毫的改动,不会带来动一发而牵全身的灾难, 这就是良好封装性的体现.但如果直接用ConcreteProduct和ConcreteCreator两个类是无论如何也做不到这点的. 优良的面向对象设计鼓励使用封装(encapsulation)和委托(delegation),而Factory Method模式就是使用了封装和委托的典型例子,这里封装是通过抽象创建器Creator来体现的,而委托则是通过抽象创建器把创建对象的责任完全交给具体创建器ConcreteCreator来体现的.
现在,请再回头看看基本概念中的那段话,开始也许觉得生涩难懂,现在是不是已经明朗化了很多.

下面让我们看看在 Java 中如何实现Factory Method模式,进一步加深对它的认识.

 

具体实施

先说明一点,用Factory Method模式创建对象并不一定会让我们的代码更短,实事上往往更长,我们也使用了更多的类,真正的目的在于这样可以灵活的,有弹性的创建不确定的对象.而且,代码的可重用性提高了,客户端的应用简化了,客户程序的代码会大大减少,变的更具可读性.

标准实现: 这里我采用Bruce Eckel 用来描述OO思想的经典例子 Shape.这样大家会比较熟悉一些.我完全按照图1中所定义的结构写了下面的一段演示代码.这段代码的作用是创建不同的Shape实例,每个实例完成两个操作:draw和erase.具体的创建过程委托ShapeFactory来完成.
1.a 首先定义一个抽象类Shape,定义两个抽象的方法.

abstract class Shape {
  // 勾画shape
  public abstract void draw();
  // 擦去 shape
  public abstract void erase();
  public String name;
  public Shape(String aName){
    name = aName;
  }
}


1.b 定义 Shape的两个子类: Circle, Square,实现Shape中定义的抽象方法

// 圆形子类
class Circle extends Shape {
  public void draw() {
    System.out.println("It will draw a circle.");
  }
  public void erase() {
    System.out.println("It will erase a circle.");
  }
  // 构造函数
  public Circle(String aName){
    super(aName);
  }
}
// 方形子类
class Square extends Shape {
  public void draw() {
    System.out.println("It will draw a square.");
  }
  public void erase() {
    System.out.println("It will erase a square.");
  }
  // 构造函数
  public Square(String aName){
    super(aName);
  }
}
 


1.c 定义抽象的创建器,anOperation调用factoryMethod创建一个对象,并对该对象进行一系列操作.

abstract class ShapeFactory { 
  protected abstract Shape factoryMethod(String aName);
  // 在anOperation中定义Shape的一系列行为
public void anOperation(String aName){
    Shape s = factoryMethod(aName);
    System.out.println("The current shape is: " + s.name);
    s.draw();
    s.erase();
  }
}


1.d 定义与circle和square相对应的两个具体创建器CircleFactory,SquareFactory,实现父类的methodFactory方法

// 定义返回 circle 实例的 CircleFactory
class CircleFactory extends ShapeFactory {
  // 重载factoryMethod方法,返回Circle对象
  protected Shape factoryMethod(String aName) {
    return new Circle(aName + " (created by CircleFactory)");
  }
}
 
// 定义返回 Square 实例的 SquareFactory
class SquareFactory extends ShapeFactory {
  // 重载factoryMethod方法,返回Square对象
protected Shape factoryMethod(String aName) {
    return new Square(aName + " (created by SquareFactory)");
  }
}
 


1.e 测试类:请注意这个客户端程序多么简洁,既没有罗嗦的条件判断语句,也无需关心ConcreteProduct和ConcreteCreator的细节(因为这里我用anOperation封装了Product里的两个方法,所以连Product的影子也没看见,当然把Product里方法的具体调用放到客户程序中也是不错的).

class Main {
  public static void main(String[] args){
    ShapeFactory sf1 = new SquareFactory();
    ShapeFactory sf2 = new CircleFactory();
    sf1.anOperation("Shape one");
    sf2.anOperation("Shape two");
  }

 


运行结果如下:

The current shape is: Shape one (created by SquareFactory)

It will draw a square.

It will erase a square.

The current shape is: Shape two (created by CircleFactory)

It will draw a circle.

It will erase a circle.

参数化的Factory Method: 这种方式依靠指定的参数作为标志来创建对应的实例,这是很常见的一种办法.比如JFC中的BorderFactory就是个很不错的例子. 以下的这个例子是用字符串作为标记来进行判断的,如果参数的类型也不一样,那就可以用到过载函数来解决这个问题,定义一系列参数和方法体不同的同名函数,这里java.util.Calendar.getInstance()又是个极好的例子.参数化的创建方式克服了Factory Method模式一个最显著的缺陷,就是当具体产品比较多时,我们不得不也建立一系列与之对应的具体构造器. 但是在客户端我们必须指定参数来决定要创建哪一个类.
2.a 我们在第一种方法的基础上进行修改,首先自定义一个的异常,这样当传入不正确的参数时可以得到更明显的报错信息.

class NoThisShape extends Exception {
  public NoThisShape(String aName) {
    super(aName);
  }
}
 


2.b去掉了ShapeFactory的两个子类,改为由ShapeFactory直接负责实例的创建. ShapeFactory自己变成一个具体的创建器,直接用参数化的方法实现factoryMethod返回多种对象.

abstract class ShapeFactory { 
  private static Shape s;
  private ShapeFactory() {}
   
  static Shape factoryMethod(String aName, String aType) throws NoThisShape{
    if (aType.compareTo("square")==0)
      return new Square(aName);
    else if (aType.compareTo("circle")==0)
      return new Circle(aName);
    else throw new NoThisShape(aType); 
  }
 
  // 在anOperation中定义Shape的一系列行为
  static void anOperation(String aName, String aType) throws NoThisShape{
    s = factoryMethod(aName, aType);
    System.out.println("The current shape is: " + s.name);
    s.draw();
    s.erase();
  }
}
 


2.c 测试类:这里客户端必须指定参数来决定具体创建哪个类.这个例子里的anOperation是静态函数,可以直接引用.

class Main {
  public static void main(String[] args) throws NoThisShape{
    ShapeFactory.anOperation("Shape one","circle");
    ShapeFactory.anOperation("Shape two","square");
    ShapeFactory.anOperation("Shape three", "delta");
  }
}
 


运行结果如下:

The current shape is: Shape one
It will draw a circle.
It will erase a circle.
The current shape is: Shape two
It will draw a square.
It will erase a square.
Exception in thread "main" NoThisShape: delta
        at ShapeFactory.factoryMethod(ShapeFactory.java:10)
        at ShapeFactory.anOperation(ShapeFactory.java:15)
        at Main.main(Main.java:5)
 


动态装载机制:
有的时候我们会把ConcreteProduct的实例传给创建器作为参数,这种情况下,如果在创建器里完成创建过程,就必须判断参数的具体类型(用instanceof),然后才能产生相应的实例,那么比较好的做法是利用Java的动态装载机制来完成这件事.比如:

我们得到一个Shape的子类s,但不知道具体是那个子类,就可以利用Class类自带的方法newInstance()得到实例

return (Shape)s.getClass().newInstance();

这种方法有兴趣得读者可以自己尝试,限于篇幅,不写具体代码出来了.

 

 


 回页首
 

 

后话:

看完这篇文章后,相信读者对Factory Method模式有一个比较清楚的了解了.我想说的是,我们不仅应该关心一个具体的模式有什么作用,如何去实现这个模式,更应该透过现象看本质,不但知其然,还要知其所以然.要通过对模式的学习加深对面向对象思想的理解,让自己的认识得到升华.Factory Method模式看似简单,实则深刻.抽象,封装,继承,委托,多态,针对接口编程等面向对象中的概念都在这里得到了一一的体现.只有抓住了它的本质,我们才能够不拘于形式的灵活运用,而不是为了使用模式而使用模式.

posted @ 2010-09-09 12:08 Eric_jiang 阅读(418) | 评论 (0)编辑 收藏

一、 Proxy模式定义:
为其他对象提供一种代理以控制这个对象的访问。
二、 模式解说
Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层,这个访问层也叫代理。Proxy模式是最常见的模式,在我们生活中处处可见,例如我们买火车票不一定非要到火车站去买,可以到一些火车票的代售点去买。寄信不一定是自己去寄,可以把信委托给邮局,由邮局把信送到目的地,现实生活中还有很多这样的例子,就不一一列举了。
三、 结构图
Proxy模式结构图如下:


 四、 一个例子
举一个比较俗的例子,一个男孩boy喜欢上了一个女孩girl,男孩一直想认识女孩,直接去和女孩打招呼吧,又觉得不好意思(这个男孩比较害羞)。于是男孩想出了一个办法,委托女孩的室友Proxy去帮他搞定这件事(获得一些关于女孩的信息,如有没有BF等,这就叫知己知彼,才能百战不殆)。下面给出这个例子的程序实现:

 interface GirlInfo{
  public void hasBoyFriend();
 }
 
 class Girl implements GirlInfo{

  public void hasBoyFriend(){
   System.out.println("还没有男朋友");
  }

 }
 
 class Proxy implements GirlInfo{
  
  private GirlInfo girl;
  
  public Proxy(GirlInfo girl){
   this.girl=girl;
  }
  public void hasBoyFriend(){
   this.girl.hasBoyFriend();
  }

 }
 
 public class ProxyClient {

 public static void main(String[] args) {
  GirlInfo girl=new Girl();
  Proxy proxy=new Proxy(girl);
  proxy.hasBoyFriend();
 }

 }
 从这个例子我们可以看出,Proxy模式是不是和Adapter模式差不多,都是调用一个已有对象的方法来完成功能。但是他们之间还是有区别的,那就是Proxy模式的目标类必须要实现某个接口,代理类没有必要实现该接口,模式是死的,它的实现是活的,如果一味的相信某些书上的实现,学习模式也就失去了意义。有的书上称这种实现为静态代理,之所以这样是为了区别于Proxy模式在jdk中的另一种实现,jdk中的实现称为动态代理。下面用jdk中的方法给出这个例子的实现。代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

 interface GirlInfo{
  public void hasBoyFriend();
 }
 
 class Girl implements GirlInfo{

  public void hasBoyFriend(){
   System.out.println("还没有男朋友");
  }

 }
 
 class GirlProxy implements InvocationHandler{
  private Object delegate;
  public Object bind(Object delegate){
   this.delegate=delegate;
   return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(),this);
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   method.invoke(delegate, args);
   return null;
  }
 
 }
 
 public class ProxyClient {

  public static void main(String[] args) {
   GirlProxy girlProxy=new GirlProxy();
   GirlInfo girl=(GirlInfo)girlProxy.bind(new Girl());
   girl.hasBoyFriend();
  }

 }

五、 适用性
1) 假如有一个外部组件包,不允许实现其接口,则就只能使用其动态代理了。
2) 直接访问一个对象很困难,或者说不能访问,此时只能是找个代理去访问,然后把结果反馈给自己。
六、 优缺点
1) 优点: 向客户端隐藏了访问某个对象的细节及复杂性;可以动态地调用一个对象中的方法,且无需实现固定的接口。
2) 缺点:暂时没发现


 

posted @ 2010-09-09 11:47 Eric_jiang 阅读(1250) | 评论 (0)编辑 收藏

抽象工厂模式(Abstract Factory):为创建一组相关或者互相依赖的对象提供一个接口,而无需指定它们对应的具体类。
通用类图如下:


例如,我们现在有两种产品A和B,而产品A和B分别都有两个厂商1和2进行生产,在这里我们把不同的厂商1、厂商2理解为牌子1和牌子2,那么厂商1就生产A1、B1,厂商2生产A2、B2。
此时,不同的厂商肯定要生产自己的产品(如A1、B1),每个厂商都有自己的一系列产品,这就与上面抽象工厂模式的描述类似了。
该例子的类图如下:

具体代码实现如下:
//定义不同的产品之间的一定具备的标准,用interface实现
//其中的method()方法可看作提取出不同产品的共性,如手机都有类似的功能
interface IProductA{
  public void method();
}

interface IProductB{
  public void method();
}

//实现了产品标准实现的一系列具体产品
//由于已经设计好A1由厂商1生产,故以下输出代码有“厂商x”
class ProductA1 implements IProductA{
  public void method() {
    System.out.println("厂商1    生产ProductA1 ...");
  }
}

class ProductA2 implements IProductA{
  public void method() {
    System.out.println("厂商2    生产ProductA2 ...");
  }
}

class ProductB1 implements IProductB{
  public void method() {
    System.out.println("厂商1    生产ProductB1 ...");
  }
}

class ProductB2 implements IProductB{
  public void method() {
    System.out.println("厂商2    生产ProductB2 ...");
  }
}

//每一种牌子的产品生产工厂,即不同的厂商负责自己牌子产品的生产
abstract class Factory1{
  abstract IProductA getProductA1();
  abstract IProductB getProductB1();
}

abstract class Factory2{
  abstract IProductA getProductA2();
  abstract IProductB getProductB2();
}

//具体的工厂用来生产相关的产品
class ConcreteFactory1 extends Factory1{
  public IProductA getProductA1() {
    return new ProductA1();
  }
  public IProductB getProductB1() {
    return new ProductB1();
  }
}

class ConcreteFactoryB extends Factory2{
  public IProductA getProductA2() {
    return new ProductA2();
  }
  public IProductB getProductB2() {
    return new ProductB2();
  }
}

//测试类
public class Client {
  public static void main(String[] args) {
    //厂商1负责生产产品A1、B1
    Factory1 factory1 = new ConcreteFactory1();
    IProductA productA1 = factory1.getProductA1();
    IProductB productB1 = factory1.getProductB1();
    
    productA1.method();
    productB1.method();
    
    //厂商2负责生产产品A2、B2
    Factory2 factory2 = new ConcreteFactoryB();
    IProductA productA2 = factory2.getProductA2();
    IProductB productB2 = factory2.getProductB2();
    
    productA2.method();
    productB2.method();
  }
}
运行结果:
厂商1  生产ProductA1 ...
厂商1  生产ProductB1 ...
厂商2  生产ProductA2 ...
厂商2  生产ProductB2 ...

 
其实,上例中本来可以不用到了两个抽象工厂,只剩一个也可,但是这样子与我们现实生活中不同厂商各自生产自己的产品有点不同,所以就将其分开为两个了,也就是将两个厂商完全地分离开。
由上面类图可以知道,一旦任何一个厂商开发出了产品C的话,那么这个厂商肯定需要扩展自己的工厂来投入到C产品的生产中,那么此时工厂1和工厂2就需要进行一定的改变了,这很明显不符合开闭原则。

posted @ 2010-09-09 11:34 Eric_jiang 阅读(228) | 评论 (0)编辑 收藏

Facade模式是这样描述的:
       为子系统中的一组接口提供一个统一接口。Facade模式定义了一个更高层的接口,使子系统更加容易使用。

       大致意思是说:使用一种比原有方式更简单的办法与系统交互。例如,我们拿一个文件,这个文件放在了第二抽屉里,而第二个抽屉的钥匙放在了第一个抽屉里,我们要想取出这个文件,第一步肯定要拿到第一个抽屉的钥匙,然后打开它再拿出第二个抽屉的钥匙,最后打开第二个抽屉取出文件。
 我就上面说的那个情形写一下实现代码

class DrawerOne {

    public void open(){

       System.out.println("第一个抽屉被打开了");

       getKey();

    }

    public void getKey(){

       System.out.println("得到第二个抽屉的钥匙");

    }

}

class DrawerTwo{

    public void open(){

       System.out.println("第二个抽屉被打开了");

       getFile();

    }

    public void getFile(){

       System.out.println("得到这个重要文件");

    }

}

public class Client{

    public static void main(String []args){

       DrawerOne darwerOne=new DrawerOne();

       DrawerTwo darwerTwo=new DrawerTwo();

       darwerOne.open();

       darwerTwo.open();

    }

}

由于没有使用Fa?ade模式,可以看到要想得到这个文件要首先打开第一个抽屉,然后再打开第二个抽屉,在我们实际所开发的系统中,有时候客户要实现某一操作,并不需要知道实现这一操作的详细步骤,而是简单地点击某一个按钮就可以得到自己想要的结果。下面对上面的代码使用Fa?ade模式进行改进,建立一个FacadeDrawer类:

class DrawerFacade{

    DrawerOne darwerOne=new DrawerOne();

    DrawerTwo darwerTwo=new DrawerTwo();

    public void open(){

       darwerOne.open();

       darwerTwo.open();

    }

}

修改Client类:

public class DrawerClient{

    public static void main(String []args){

       DrawerFacade drawer=new DrawerFacade();

       drawer.open();

    }

}

输出结果如下:

第一个抽屉被打开了

得到第二个抽屉的钥匙

第二个抽屉被打开了

得到这个重要文件

正如上面所说,客户端client,它并不需要关心子系统,而是关心DrawerFacade所留下来的和外部交互的接口,而子系统在DrawerFacade的聚合。

以上只是个人拙见,哪里有不正确的地方,希望大家多多批评指正。^_^

Facade模式主要适用于以下几种情况:

1)不需要使用一个复杂系统的所有功能,而且可以创建一个新的类,包含访问系统的所有规则。如果只需要使用系统的部分功能,那么你为新类所创建的API将比原系统的API简单的多。

2)希望封装或者隐藏系统原系统。

3)希望使用原系统的功能,而且还希望增加一些新的功能。

4) 编写新类的成本小于所有人学会使用或者未来维护原系统上所需的成本。

 

 

posted @ 2010-09-09 11:22 Eric_jiang 阅读(188) | 评论 (0)编辑 收藏

一、FlyWeight模式定义:

运用共享技术有效地支持大量细粒度对象。

二、模式解说

也就是说在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象。在Flyweight模式中,由于要产生各种各样的对象,所以在Flyweight(享元)模式中常出现Factory模式。Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个对象存储池(Flyweight Pool)来存放内部状态的对象。Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度。

三、结构图

 

享元模式所涉及的角色有抽象享元角色、具体(单纯)享元角色、复合享元角色、享员工厂角色,以及客户端角色等。

抽象享元角色(Flyweight):此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口或抽象类。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。

具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。

复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。这个角色一般很少使用。

享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。

客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。

四、一个例子

 

import java.util.Hashtable;


/**
 *
 * @author <a href="mailto:flustar2008@163.com">flustar</a>
 * @version 1.0
 * Creation date: Jan 25, 2008 1:33:49 PM
 */

  // "Flyweight"

  abstract class Character
  {
    protected char symbol;
    protected int width;
    protected int height;
    protected int ascent;
    protected int descent;
    protected int pointSize;

    public abstract void Display(int pointSize);
  }

  // "ConcreteFlyweight"

  class CharacterA extends Character
  {
    // Constructor
    public CharacterA()
    {
      this.symbol = 'A';
      this.height = 100;
      this.width = 120;
      this.ascent = 70;
      this.descent = 0;
    }

    public  void Display(int pointSize)
    {
      this.pointSize = pointSize;
     System.out.println(this.symbol +
        " (pointsize " + this.pointSize + ")");
    }
  }

  // "ConcreteFlyweight"

  class CharacterB extends Character
  {
    // Constructor
    public CharacterB()
    {
      this.symbol = 'B';
      this.height = 100;
      this.width = 140;
      this.ascent = 72;
      this.descent = 0;
    }

    public  void Display(int pointSize)
    {
      this.pointSize = pointSize;
     System.out.println(this.symbol +
        " (pointsize " + this.pointSize + ")");
    }

  }

  //  C, D, E, etc.

  // "ConcreteFlyweight"

  class CharacterZ extends Character
  {
    // Constructor
    public CharacterZ()
    {
      this.symbol = 'Z';
      this.height = 100;
      this.width = 100;
      this.ascent = 68;
      this.descent = 0;
    }

    public  void Display(int pointSize)
    {
      this.pointSize = pointSize;
      System.out.println(this.symbol +
        " (pointsize " + this.pointSize + ")");
    }
  }
  // "FlyweightFactory"

  class CharacterFactory
  {
    private Hashtable characters = new Hashtable();

    public Character GetCharacter(char key)
    {
      // Uses "lazy initialization"
      Character character = (Character)characters.get(key);
      if (character == null)
      {
        switch (key)
        {
          case 'A': character = new CharacterA(); break;
          case 'B': character = new CharacterB(); break;
            //
          case 'Z': character = new CharacterZ(); break;
        }
        characters.put(key, character);
      }
      return character;
    }
  }
// test application
  public class Test
  {
    public static void main(String []args)
    {
      // Build a document with text
      String document = "AAZZBBZB";
      char[] chars = document.toCharArray();

      CharacterFactory f = new CharacterFactory();

      // extrinsic state
      int pointSize = 10;

      // For each character use a flyweight object
      for(char c : chars)
      {
        pointSize++;
        Character character = f.GetCharacter(c);
        character.Display(pointSize);
      }
    }
  }

 

五、 适用性

Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用Flyweight模式。

1)一个应用程序使用了大量的对象。

2)完全由于使用大量的对象,造成很大的存储开销。

3)对象的大多数状态都可变为外部状态。

4)如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。

5)应用程序不依赖对象标识。

六、优缺点

1)享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。

2)享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

posted @ 2010-09-09 11:13 Eric_jiang 阅读(225) | 评论 (0)编辑 收藏

 

设计模式之Composite(组合)

Composite定义:
将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性.

Composite比较容易理解,想到Composite就应该想到树形结构图。组合体内这些对象都有共同接口,当组合体一个对象的方法被调用执行时,Composite将遍历(Iterator)整个树形结构,寻找同样包含这个方法的对象并实现调用执行。可以用牵一动百来形容。

所以Composite模式使用到Iterator模式,和Chain of Responsibility模式类似。

Composite好处:
1.使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
2.更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。

如何使用Composite?
首先定义一个接口或抽象类,这是设计模式通用方式了,其他设计模式对接口内部定义限制不多,Composite却有个规定,那就是要在接口内部定义一个用于访问和管理Composite组合体的对象们(或称部件Component).

下面的代码是以抽象类定义,一般尽量用接口interface,

public abstract class Equipment
{
  private String name;
  //网络价格
  public abstract double netPrice();
  //折扣价格
  public abstract double discountPrice();
  //增加部件方法  
  public boolean add(Equipment equipment) { return false; }
  //删除部件方法
  public boolean remove(Equipment equipment) { return false; }
  //注意这里,这里就提供一种用于访问组合体类的部件方法。
  public Iterator iter() { return null; }
  
  public Equipment(final String name) { this.name=name; }
}


抽象类Equipment就是Component定义,代表着组合体类的对象们,Equipment中定义几个共同的方法。

public class Disk extends Equipment
{
  public Disk(String name) { super(name); }
  //定义Disk网络价格为1
  public double netPrice() { return 1.; }
  //定义了disk折扣价格是0.5 对折。
  public double discountPrice() { return .5; }
}


Disk是组合体内的一个对象,或称一个部件,这个部件是个单独元素(Primitive)。
还有一种可能是,一个部件也是一个组合体,就是说这个部件下面还有'儿子',这是树形结构中通常的情况,应该比较容易理解。现在我们先要定义这个组合体:

abstract class CompositeEquipment extends Equipment
{
  private int i=0;
  //定义一个Vector 用来存放'儿子'
  private Lsit equipment=new ArrayList();

  public CompositeEquipment(String name) { super(name); }

  public boolean add(Equipment equipment) {
     this.equipment.add(equipment);
     return true;
   }

  public double netPrice()
  {
    double netPrice=0.;
    Iterator iter=equipment.iterator();
    for(iter.hasNext())
      netPrice+=((Equipment)iter.next()).netPrice();
    return netPrice;
  }

  public double discountPrice()
  {
    double discountPrice=0.;
    Iterator iter=equipment.iterator();
    for(iter.hasNext())
      discountPrice+=((Equipment)iter.next()).discountPrice();
    return discountPrice;
  }
  

  //注意这里,这里就提供用于访问自己组合体内的部件方法。
  //上面dIsk 之所以没有,是因为Disk是个单独(Primitive)的元素.
  public Iterator iter()
  {
    return equipment.iterator() ;
  {
  //重载Iterator方法
   public boolean hasNext() { return i<equipment.size(); }
  //重载Iterator方法
   public Object next()
   {
    if(hasNext())
       return equipment.elementAt(i++);
    else
        throw new NoSuchElementException();
   }
  

}


上面CompositeEquipment继承了Equipment,同时为自己里面的对象们提供了外部访问的方法,重载了Iterator,Iterator是Java的Collection的一个接口,是Iterator模式的实现.

我们再看看CompositeEquipment的两个具体类:盘盒Chassis和箱子Cabinet,箱子里面可以放很多东西,如底板,电源盒,硬盘盒等;盘盒里面可以放一些小设备,如硬盘 软驱等。无疑这两个都是属于组合体性质的。

public class Chassis extends CompositeEquipment
{
   public Chassis(String name) { super(name); }
   public double netPrice() { return 1.+super.netPrice(); }
   public double discountPrice() { return .5+super.discountPrice(); }
}

public class Cabinet extends CompositeEquipment
{
   public Cabinet(String name) { super(name); }
   public double netPrice() { return 1.+super.netPrice(); }
   public double discountPrice() { return .5+super.discountPrice(); }
}


至此我们完成了整个Composite模式的架构。

我们可以看看客户端调用Composote代码:

Cabinet cabinet=new Cabinet("Tower");

Chassis chassis=new Chassis("PC Chassis");
//将PC Chassis装到Tower中 (将盘盒装到箱子里)
cabinet.add(chassis);
//将一个10GB的硬盘装到 PC Chassis (将硬盘装到盘盒里)
chassis.add(new Disk("10 GB"));

//调用 netPrice()方法;
System.out.println("netPrice="+cabinet.netPrice());
System.out.println("discountPrice="+cabinet.discountPrice());

上面调用的方法netPrice()或discountPrice(),实际上Composite使用Iterator遍历了整个树形结构,寻找同样包含这个方法的对象并实现调用执行.

Composite是个很巧妙体现智慧的模式,在实际应用中,如果碰到树形结构,我们就可以尝试是否可以使用这个模式。

以论坛为例,一个版(forum)中有很多帖子(message),这些帖子有原始贴,有对原始贴的回应贴,是个典型的树形结构,那么当然可以使用Composite模式,那么我们进入Jive中看看,是如何实现的.

Jive解剖
在Jive中 ForumThread是ForumMessages的容器container(组合体).也就是说,ForumThread类似我们上例中的 CompositeEquipment.它和messages的关系如图:
[thread]
   |- [message]
   |- [message]
      |- [message]
      |- [message]
         |- [message]

我们在ForumThread看到如下代码:

public interface ForumThread {
   ....
   public void addMessage(ForumMessage parentMessage, ForumMessage newMessage)
         throws UnauthorizedException;

   public void deleteMessage(ForumMessage message)
         throws UnauthorizedException;

  
   public Iterator messages();
      ....

}
类似CompositeEquipment, 提供用于访问自己组合体内的部件方法: 增加 删除 遍历.

三. 测试程序与源程序的组织结构,“分离但等同”原则
四. junit.jar加入到classpath中,可以被javac任务找到,但不能java任务识别。

junit与ant构建工具的集成

如果junit任务下面没有设置<formatter>子元素,那将无法打印出详细的信息。
一个junit任务下可以有多个<formatter>子元素,<formatter>的usefile属性决定是否生成文件。
junitreport任务首先把多个test-....xml文件合并成一个汇总的xml文件,其todir属性指定该文件的存储目录.其report子元素用于指定生成的html文件的目录。在用junitreport之前,要小心junit的haltonfaiure。

batchtest指定的java源文件或class文件,而不是要运行的测试类名称,junit从这些文件中找出要运行的测试类名。

五. junit与ant构建工具的集成的例子,
<project name="junitlesson" default="run">

<property name="src.java.dir" value="src/java"/><!--被测试的java源文件的路径-->
<property name="src.test.dir" value="src/test"/><!--测试的java源文件的路径-->

<property name="classes.java.dir" value="classes/java"/><!--被测试的class文件的路径-->
<property name="classes.test.dir" value="classes/test"/><!--测试的class文件的路径-->

<target name="init">
   <mkdir dir="${classes.java.dir}"/>
   <mkdir dir="${classes.test.dir}"/>
</target>

<target name="compileJava" depends="init">
   <javac srcdir="${src.java.dir}/cn/itcast" destdir="${classes.java.dir}"/>
</target>

<target name="compileTest" depends="init">
   <javac srcdir="${src.test.dir}/cn/itcast" destdir="${classes.test.dir}">
    <classpath>
     <pathelement path="."/>
     <pathelement path="${classes.java.dir}"/>
    </classpath>
   </javac>
</target>

<target name="compile" depends="compileJava,compileTest"/>

<target name="run" depends="compile">
   <mkdir dir="testResult"/>
   <junit fork="true" printsummary="on"><!-- haltonfailure="true"--><!--注意:这句一加上去的话如果程序里面出错了后面的junitreport就不执行了-->
    <classpath>
     <pathelement path="${classes.test.dir}"/>
     <pathelement path="${classes.java.dir}"/>
    </classpath>
    <test name="cn.itcast.TestCalculator" todir=".\testResult" outfile="result"/>
    <formatter type="xml" />
   </junit>
 
   <junitreport todir=".\testResult"> 
       <fileset dir=".\testResult"> 
           <include name="result.xml"/> 
       </fileset> 
       <report format="frames" todir=".\testResult"/> 
     </junitreport> 
</target>

</project>
<!--正确执行的话打开src所在的目录就会看到一个新建的testResult目录,里面已经生成了一大堆测试报告的网页 :) -->

 

posted @ 2010-09-09 10:59 Eric_jiang 阅读(286) | 评论 (0)编辑 收藏

这是一堂关于UML基础知识的补习课;现在我们做项目时间都太紧了,基本上都没有做过真正的Class级别的详细设计,更别提使用UML来实现规范建模了;本篇主要就以前自己一直感觉很迷糊的几种class之间的关系进行整理,让我们在真正用UML进行比如类图设计时能够更加清晰明了;以下就分别介绍这几种关系:

继承
指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性;












实现
指的是一个class类实现interface接口(可以是多个)的功能;实现是类与接口之间最常见的关系;在Java中此类关系通过关键字implements明确标识,在设计时一般没有争议性;

 


依赖
可以简单的理解,就是一个类A使用到了另一个类B,而这种使用关系是具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A;比如某人要过河,需要借用一条船,此时人与船之间的关系就是依赖;表现在代码层面,为类B作为参数被类A在某个method方法中使用


关联
他体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的;表现在代码层面,为被关联类B以类属性的形式出现在关联类A中,也可能是关联类A引用了一个类型为被关联类B的全局变量


聚合
聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系此时整体与部分之间是可分离的他们可以具有各自的生命周期部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分


组合
组合也是关联关系的一种特例,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束;比如你和你的大脑;表现在代码层面,和关联关系是一致的,只能从语义级别来区分


对于继承、实现这两种关系没多少疑问,他们体现的是一种类与类、或者类与接口间的纵向关系;其他的四者关系则体现的是类与类、或者类与接口间的引用、横向关系,是比较难区分的,有很多事物间的关系要想准备定位是很难的,前面也提到,这几种关系都是语义级别的,所以从代码层面并不能完全区分各种关系;但总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖;

关联和依赖区别是什么

       对类而言依赖存在的理由有:B作为一个参数被传递给A众所定义的一个方法(参数可见性);B在A的一个方法众被声明未局部对象(局部声明可见性);B对A全局可见(全局可见性)而关联一般应来描述普通的属性可见性(B是A的一个属性,是一种相对长久的可见性,是普遍存在的)

依赖是比关联弱的关系,关联代表一种结构化的关系,体现在生成的代码中,以java为例:  
  若类A单向关联指向类B,则在类A中存在一个属性B   b。  
  若类A依赖类B,则不会有这个属性,类B的实例可能存在于某个方法调用的参数中,或某个方法的局部变量中。

例如代码:
依赖:----存在于某个方法调用的参数中,或某个方法的局部变量中。

Person类与Screwdriver类的依赖关系
public class Person{   
    /** 拧螺丝 */  
    public void screw(Screwdriver screwdriver){   
        screwdriver.screw();   
    }   
}  

关联:---存在一个属性
公司(Company)和员工(Employee)的关联关系
public class Company{   
    private Employee employee;   
    public Employee getEmployee(){   
        return employee;   
    }   
    public void setEmployee(Employee employee){   
        this.employee=employee;   
    }   
    //公司运作   
    public void run(){   
        employee.startWorking();   
    }   
}  

组合和聚合的区别

组合:

组合(也有人称为包容):一般是实心菱形加实线箭头表示,如上图所示,表示的是C8被C7包容,而且C8不能离开C7而独立存在。       但这是视问题域而定的,例如在关心汽车的领域里,轮胎是一定要组合在汽车类中的,因为它离开了汽车就没有意义了。但是在卖轮胎的店铺业务里,就算轮胎离开了汽车,它也是有意义的,这就可以用聚合了。

在《敏捷开发》中还说到,A组合B,则A需要知道B的生存周期,即可能A负责生成或者释放B,或者A通过某种途径知道B的生成和释放。

代码:

class C7 {
    C8 theC8 ;
} ;

class C8 {
   
} ;

聚合:

表示C9聚合C10,但是C10可以离开C9而独立存在(独立存在的意思是在某个应用的问题域中这个类的存在有意义。)。

class C9 {
    C10 theC10 ;
} ;

class C10 {

} ;

可以看到,代码和聚合是一样的。具体如何区别,可能就只能用语义来区分了。

 


posted @ 2010-09-09 10:31 Eric_jiang 阅读(279) | 评论 (0)编辑 收藏

仅列出标题
共57页: First 上一页 43 44 45 46 47 48 49 50 51 下一页 Last