posts - 51, comments - 17, trackbacks - 0, articles - 9
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

Flyweight模式定义:
避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).

为什么使用?
面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要"求同存异",找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的,这也Flyweight中两个重要概念内部状态intrinsic和外部状态extrinsic之分.

说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式.Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个Flyweight pool(模式池)来存放内部状态的对象.

Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中.

如何使用?

我们先从Flyweight抽象接口开始:

public interface Flyweight
{
  public void operation( ExtrinsicState state );
}

//用于本模式的抽象数据类型(自行设计)
public interface ExtrinsicState { }

下面是接口的具体实现(ConcreteFlyweight) ,并为内部状态增加内存空间, ConcreteFlyweight必须是可共享的,它保存的任何状态都必须是内部(intrinsic),也就是说,ConcreteFlyweight必须和它的应用环境场合无关.

public class ConcreteFlyweight implements Flyweight {
  private IntrinsicState state;
  
  public void operation( ExtrinsicState state )
  {
      //具体操作
  }

}

当然,并不是所有的Flyweight具体实现子类都需要被共享的,所以还有另外一种不共享的ConcreteFlyweight:

public class UnsharedConcreteFlyweight implements Flyweight {

  public void operation( ExtrinsicState state ) { }

}

Flyweight factory负责维护一个Flyweight池(存放内部状态),当客户端请求一个共享Flyweight时,这个factory首先搜索池中是否已经有可适用的,如果有,factory只是简单返回送出这个对象,否则,创建一个新的对象,加入到池中,再返回送出这个对象.池

public class FlyweightFactory {
  //Flyweight pool
  private Hashtable flyweights = new Hashtable();

  public Flyweight getFlyweight( Object key ) {

    Flyweight flyweight = (Flyweight) flyweights.get(key);

    if( flyweight == null ) {
      //产生新的ConcreteFlyweight
      flyweight = new ConcreteFlyweight();
      flyweights.put( key, flyweight );
    }

    return flyweight;
  }
}

至此,Flyweight模式的基本框架已经就绪,我们看看如何调用:

FlyweightFactory factory = new FlyweightFactory();
Flyweight fly1 = factory.getFlyweight( "Fred" );
Flyweight fly2 = factory.getFlyweight( "Wilma" );
......

从调用上看,好象是个纯粹的Factory使用,但奥妙就在于Factory的内部设计上.

Flyweight模式在XML等数据源中应用
我们上面已经提到,当大量从数据源中读取字符串,其中肯定有重复的,那么我们使用Flyweight模式可以提高效率,以唱片CD为例,在一个XML文件中,存放了多个CD的资料.

每个CD有三个字段:
1.出片日期(year)
2.歌唱者姓名等信息(artist)
3.唱片曲目 (title)

其中,歌唱者姓名有可能重复,也就是说,可能有同一个演唱者的多个不同时期 不同曲目的CD.我们将"歌唱者姓名"作为可共享的ConcreteFlyweight.其他两个字段作为UnsharedConcreteFlyweight.

首先看看数据源XML文件的内容:


<?xml version="1.0"?>
<collection>

<cd>
<title>Another Green World</title>
<year>1978</year>
<artist>Eno, Brian</artist>
</cd>

<cd>
<title>Greatest Hits</title>
<year>1950</year>
<artist>Holiday, Billie</artist>
</cd>

<cd>
<title>Taking Tiger Mountain (by strategy)</title>
<year>1977</year>
<artist>Eno, Brian</artist>
</cd>

.......

</collection>


虽然上面举例CD只有3张,CD可看成是大量重复的小类,因为其中成分只有三个字段,而且有重复的(歌唱者姓名).

CD就是类似上面接口 Flyweight:


public class CD {

  private String title;
  private int year;
  private Artist artist;

  public String getTitle() {  return title; }
  public int getYear() {    return year;  }
  public Artist getArtist() {    return artist;  }

  public void setTitle(String t){    title = t;}
  public void setYear(int y){year = y;}
  public void setArtist(Artist a){artist = a;}

}

将"歌唱者姓名"作为可共享的ConcreteFlyweight:

public class Artist {

  //内部状态
  private String name;

  // note that Artist is immutable.
  String getName(){return name;}

  Artist(String n){
    name = n;
  }

}

再看看Flyweight factory,专门用来制造上面的可共享的ConcreteFlyweight:Artist

public class ArtistFactory {

  Hashtable pool = new Hashtable();

  Artist getArtist(String key){

    Artist result;
    result = (Artist)pool.get(key);
    ////产生新的Artist
    if(result == null) {
      result = new Artist(key);
      pool.put(key,result);
      
    }
    return result;
  }

}

当你有几千张甚至更多CD时,Flyweight模式将节省更多空间,共享的flyweight越多,空间节省也就越大.


给个例子,coffee商店

package FlyWeight.coffeeshop;

public class Table {
 
 private int number;

 public int getNumber() {
  return number;
 }

 public void setNumber(int number) {
  this.number = number;
 }

 public Table(int number) {
  super();
  // TODO Auto-generated constructor stub
  this.number = number;
 }

}


package FlyWeight.coffeeshop;

public abstract class Order {
 
 public abstract void serve(Table table);
 
 public abstract String getFlavor();

}


package FlyWeight.coffeeshop;

public class Flavor extends Order {

 private String flavor;

 public Flavor(String flavor) {
  super();
  // TODO Auto-generated constructor stub
  this.flavor = flavor;
 }

 public String getFlavor() {
  return flavor;
 }

 public void setFlavor(String flavor) {
  this.flavor = flavor;
 }

 public void serve(Table table) {
  System.out.println("Serving table " + table.getNumber() + " with flavor " + flavor );
 } 
}

package FlyWeight.coffeeshop;

public class FlavorFactory {

 private Order[] flavors = new Flavor[10];
 private int ordersMade = 0;//已经处理好的订单数
 private int totalFlavors = 0;//已购买的coffee风味种类数
 
 public Order getOrder(String flavorToGet){
  if(ordersMade > 0){
   for(int i=0; i<ordersMade; i++){
    if(flavorToGet.equalsIgnoreCase(flavors[i].getFlavor()))
     return flavors[i];
   }
  }
  flavors[ordersMade] = new Flavor(flavorToGet);
  totalFlavors++;
  return flavors[ordersMade++];
 }
 
 public int getTotalFlavorsMade(){
  return totalFlavors;
 }
}


package FlyWeight.coffeeshop;

public class Client {
 
 private static Order[] flavors = new Flavor[100];
 
 private static int ordersMade = 0;
 private static FlavorFactory flavorFactory;
 
 private static void takeOrders(String aFlavor){
  flavors[ordersMade++] = flavorFactory.getOrder(aFlavor);
 }
 
 public static void main(String[] args){
  flavorFactory = new FlavorFactory();
  
     takeOrders("Black Coffee");
     takeOrders("Capucino");
     takeOrders("Espresso");
     takeOrders("Espresso");
     takeOrders("Capucino");
     takeOrders("Capucino");
     takeOrders("Black Coffee");
     takeOrders("Espresso");
     takeOrders("Capucino");
     takeOrders("Black Coffee");
     takeOrders("Espresso");
    
     for(int i=0; i<ordersMade; i++){
      flavors[i].serve(new Table(i));
     }
    
     System.out.println("\nTotal Flavor objrcts made: " +
       flavorFactory.getTotalFlavorsMade());
 }
 
}

//-------------------------------------------------------------------

运行结果:

Serving table 0 with flavor Black Coffee
Serving table 1 with flavor Capucino
Serving table 2 with flavor Espresso
Serving table 3 with flavor Espresso
Serving table 4 with flavor Capucino
Serving table 5 with flavor Capucino
Serving table 6 with flavor Black Coffee
Serving table 7 with flavor Espresso
Serving table 8 with flavor Capucino
Serving table 9 with flavor Black Coffee
Serving table 10 with flavor Espresso

Total Flavor objrcts made: 3

posted @ 2007-03-23 19:45 chenweicai 阅读(291) | 评论 (0)编辑 收藏

关 键 词: Java  模式
阅读提示:装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

说明:

装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰模式的特点;

(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。

(2) 装饰对象包含一个真实对象的索引(reference)

(3) 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。

(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

下表格列举了装饰模式和继承的不同:

装饰模式 VS 继承

装饰模式 继承

用来扩展特定对象的功能 用来扩展一类对象的功能

不需要子类 需要子类

动态地 静态地

运行时分配职责 编译时分派职责

防止由于子类而导致的复杂和混乱 导致很多子类产生,在一些场合,报漏类的层次

更多的灵活性 缺乏灵活性

对于一个给定的对象,同时可能有不同的装饰对象,客户端可以通过它的需要选择合适的装饰对象发送消息。 对于所有可能的联合,客户期望

很容易增加任何的 困难

给个实例,打印发票



package Decorator.printorder;
import java.text.NumberFormat;
public class OrderLine {
 private String itemName;
 private int units;
 private double unitPrice;
 
 public String getItemName() {
  return itemName;
 }
 
 public void setItemName(String itemName) {
  this.itemName = itemName;
 }
 
 public double getUnitPrice() {
  return unitPrice;
 }
 
 public void setUnitPrice(double unitPrice) {
  this.unitPrice = unitPrice;
 }
 
 public int getUnits() {
  return units;
 }
 
 public void setUnits(int units) {
  this.units = units;
 }
 
 public void printLine(){
  System.out.println(itemName + "\t" + units
    + "\t" + formatCurrency(unitPrice)
    + "\t\t" + formatCurrency(getSubtotal()));
 }
 
 public double getSubtotal(){
  return units * unitPrice;
 }
 
 private String formatCurrency(double amnt){
  return NumberFormat.getCurrencyInstance().format(amnt);
 }
}


package Decorator.printorder;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Vector;
abstract public class Order {
 
 private OrderLine lnkOrderLine;
 protected String customerName;
 protected Date salesDate;
 protected Vector items = new Vector(10);
 
 public void print(){
  for(int i=0; i<items.size(); i++){
   OrderLine item = (OrderLine) items.get(i);
   item.printLine();
  }
 }
 public String getCustomerName() {
  return customerName;
 }
 public void setCustomerName(String customerName) {
  this.customerName = customerName;
 }
 public Date getSalesDate() {
  return salesDate;
 }
 public void setSalesDate(Date salesDate) {
  this.salesDate = salesDate;
 }
 
 public void addItem(OrderLine item){
  items.add(item);
 }
 
 public void remove(OrderLine item){
  items.remove(item);
 }
 
 public double getGrandTotal(){
  double amnt = 0.0D;
  for(int i=0; i<items.size(); i++){
   OrderLine item = (OrderLine) items.get(i);
   amnt += item.getSubtotal();
  }
  return amnt;
 }
 
 protected String formatCurrency(double amnt){
  return NumberFormat.getCurrencyInstance().format(amnt);
 }
}


package Decorator.printorder;
public class SalesOrder extends Order {
 public SalesOrder() {
  super();
  // TODO Auto-generated constructor stub
 }
 public void print() {
  // TODO Auto-generated method stub
  super.print();
 }
 
}


package Decorator.printorder;
public abstract class OrderDecorator extends Order{
 protected Order order;
 
 public OrderDecorator(Order order){
  this.order = order;
  this.setSalesDate(this.order.getSalesDate());
  this.setCustomerName(this.order.getCustomerName());
 }
}


package Decorator.printorder;
public class HeaderDecorator extends OrderDecorator {
 public HeaderDecorator(Order order) {
  super(order);
  // TODO Auto-generated constructor stub
 }
 public void print(){
  this.printHeader();
  super.order.print();
 }
 
 private void printHeader(){
  System.out.println("\t***\tI N V O I C E\t***\nXYZ Incorporated\nDate of Sale: "
    + order.getSalesDate());
        System.out.println("========================================================");
        System.out.println("Item\t\tUnits\tUnit Price\tSubtotal");
 }
}


package Decorator.printorder;
public class FooterDecorator extends OrderDecorator {
 public FooterDecorator(Order order) {
  super(order);
  // TODO Auto-generated constructor stub
 }
 public void print(){
  super.order.print();
  printFooter();
 }
 
 private void printFooter(){
        System.out.println("========================================================");
        System.out.println("Total\t\t\t\t\t" +
            formatCurrency(super.order.getGrandTotal()));
 }
}


package Decorator.printorder;
import java.util.Date;

public class Client {
 private static Order order;
 
 public static void main(String[] args){
  order = new SalesOrder();
  order.setSalesDate(new Date());
  order.setCustomerName("XYZ Repair Shop");
  
        OrderLine line1 = new OrderLine();
        line1.setItemName("FireWheel Tire");
        line1.setUnitPrice(154.23);
        line1.setUnits(4);
        order.addItem(line1);
        OrderLine line2 = new OrderLine();
        line2.setItemName("Front Fender");
        line2.setUnitPrice(300.45);
        line2.setUnits(1);
        order.addItem(line2);
       
        order = new HeaderDecorator(new FooterDecorator(order));
       
        order.print();
       
 }
}


posted @ 2007-03-23 15:56 chenweicai 阅读(959) | 评论 (0)编辑 收藏

小结:适配器模式用插座的适配器最为形象,插头是2口的,插座是3口的,中间的适配器就是同时支持2口和三口的。从对象的角度就是一般继承一个实现一个,总之,前方百计把两者都关联起来 。


通常,客户类(clients of class)通过类的接口访问它提供的服务。有时,现有的类(existing class)可以提供客户类的功能需要,但是它所提供的接口不一定是客户类所期望的。这是由于现有的接口太详细或者缺乏详细或接口的名称与客户类所查找的不同等诸多不同原因导致的。

  在这种情况下,现有的接口需要转化(convert)为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能。适配器模式(Adapter Pattern)可以完成这样的转化。适配器模式建议定义一个包装类,包装有不兼容接口的对象。这个包装类指的就是适配器(Adapter),它包装的对象就是适配者(Adaptee)。适配器提供客户类需要的接口,适配器接口的实现是把客户类的请求转化为对适配者的相应接口的调用。换句话说:当客户类调用适配器的方法时,在适配器类的内部调用适配者类的方法,这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于借口不兼容而不能交互的类可以一起工作(work together)。

  在上面讨论的接口:

  (1)    不是指在JAVA编程语言中接口的概念,虽然类的接口可以通过JAVA借扩来定义。

  (2)    不是指由窗体和GUI控件所组成的GUI应用程序的用户接口。

  (3)    而是指类所报漏的,被其他类调用的编程接口,

  类适配器(Class Adapter)VS对象适配器(Object Adapter)

  适配器总体上可以分为两类??类适配器(Class Adapter)VS对象适配器(Object Adapter)
    

 类适配器:


  类适配器是通过继承类适配者类(Adaptee Class)实现的,另外类适配器实现客户类所需要的接口。当客户对象调用适配器类方法的时候,适配器内部调用它所继承的适配者的方法。
    

 对象适配器:

  对象适配器包含一个适配器者的引用(reference),与类适配器相同,对象适配器也实现了客户类需要的接口。当客户对象调用对象适配器的方法的时候,对象适配器调它所包含的适配器者实例的适当方法。

  下表是类适配器(Class Adapter)和对象适配器(Object Adapter)的详细不同

  
    
  例子:

  让我们建立一个验证给定客户地址的应用。这个应用是作为大的客户数据管理应用的一部分。

  让我们定义一个Customer类:

Customer 



Figure 20.1: Customer Class 
Listing 20.1: Customer Class 
  1. class Customer { 
  2.   public static final String US = "US"
  3.   public static final String CANADA = "Canada"
  4.   private String address; 
  5.   private String name; 
  6.   private String zip, state, type; 
  7.   public boolean isValidAddress() { 
  8.           … 
  9.           … 
  10.   } 
  11.   public Customer(String inp_name, String inp_address, 
  12.                   String inp_zip, String inp_state, 
  13.                   String inp_type) { 
  14.     name = inp_name; 
  15.     address = inp_address; 
  16.     zip = inp_zip; 
  17.     state = inp_state; 
  18.     type = inp_type; 
  19.   } 
  20. }//end of class 
  不同的客户对象创建Customer对象并调用(invoke)isValidAddress方法验证客户地址的有效性。为了验证客户地址的有效性, Customer类期望利用一个地址验证类(address validator class),这个验证类提供了在接口 AddressValidator中声明的接口。

  Listing 20.2: AddressValidator as an Interface 
  1. public interface AddressValidator { 
  2.   public boolean isValidAddress(String inp_address, 
  3.      String inp_zip, String inp_state); 
  4. }//end of class 

  让我们定义一个USAddress的验证类,来验证给定的U.S地址。

  Listing 20.3: USAddress Class 
  1. class USAddress implements AddressValidator { 
  2.   public boolean isValidAddress(String inp_address, 
  3.      String inp_zip, String inp_state) { 
  4.    if (inp_address.trim().length() < 10) 
  5.      return false
  6.    if (inp_zip.trim().length() < 5) 
  7.      return false
  8.    if (inp_zip.trim().length() > 10) 
  9.      return false
  10.    if (inp_state.trim().length() != 2) 
  11.      return false
  12.    return true
  13.   } 
  14. }//end of class 

  USAddress类实现AddressValidator接口,因此Customer对象使用USAddress实例作为验证客户地址过程的一部分是没有任何问题的。

  Listing 20.4: Customer Class Using the USAddress Class 
  1. class Customer { 
  2.           … 
  3.           … 
  4.  public boolean isValidAddress() { 
  5.    //get an appropriate address validator 
  6.    AddressValidator validator = getValidator(type); 
  7.    //Polymorphic call to validate the address 
  8.    return validator.isValidAddress(address, zip, state); 
  9.  } 
  10.  private AddressValidator getValidator(String custType) { 
  11.    AddressValidator validator = null
  12.    if (custType.equals(Customer.US)) { 
  13.      validator = new USAddress(); 
  14.    } 
  15.    return validator; 
  16.  } 
  17. }//end of class 
 


Figure 20.2: Customer/USAddress Validator?Class Association 

  但是当验证来自加拿大的客户时,就要对应用进行改进。这需要一个验证加拿大客户地址的验证类。让我们假设已经存在一个用来验证加拿大客户地址的使用工具类CAAddress。

从下面的CAAdress类的实现,可以发现CAAdress提供了客户类Customer类所需要的验证服务。但是它所提供的接口不用于客户类Customer所期望的。

  Listing 20.5: CAAdress Class with Incompatible Interface 
  1. class CAAddress { 
  2.   public boolean isValidCanadianAddr(String inp_address, 
  3.      String inp_pcode, String inp_prvnc) { 
  4.    if (inp_address.trim().length() < 15) 
  5.      return false
  6.    if (inp_pcode.trim().length() != 6) 
  7.      return false
  8.    if (inp_prvnc.trim().length() < 6) 
  9.      return false
  10.    return true
  11.   } 
  12. }//end of class 

  CAAdress类提供了一个isValidCanadianAddr方法,但是Customer期望一个声明在AddressValidator接口中的isValidAddress方法。

  接口的不兼容使得Customer对象利用现有的CAAdress类是困难的。一种意见是改变CAAdress类的接口,但是可能会有其他的应用正在使用CAAdress类的这种形式。改变CAAdress类接口会影响现在使用CAAdress类的客户。

  应用适配器模式,类适配器CAAdressAdapter可以继承CAAdress类实现AddressValidator接口。

 


  Figure 20.3: Class Adapter for the CAAddress Class 
Listing 20.6: CAAddressAdapter as a Class Adapter 
  1. public class CAAddressAdapter extends CAAddress 
  2.   implements AddressValidator { 
  3.   public boolean isValidAddress(String inp_address, 
  4.      String inp_zip, String inp_state) { 
  5.     return isValidCanadianAddr(inp_address, inp_zip, 
  6.            inp_state); 
  7.   } 
  8. }//end of class 

  因为适配器CAAdressAdapter实现了AddressValidator接口,客户端对象访问适配器CAAdressAdapter对象是没有任何问题的。当客户对象调用适配器实例的isValidAddress方法的时候,适配器在内部把调用传递给它继承的 isValidCanadianAddr方法。

  在Customer类内部,getValidator私有方法需要扩展,以至于它可以在验证加拿大客户的时候返回一个CAAdressAdapter实例。返回的对象是多态的,USAddress和CAAddressAdapter都实现了AddressValidator接口,所以不用改变。

Listing 20.7: Customer Class Using the CAAddressAdapter Class 
  1. class Customer { 
  2.           … 
  3.           … 
  4.   public boolean isValidAddress() { 
  5.     //get an appropriate address validator 
  6.     AddressValidator validator = getValidator(type); 
  7.     //Polymorphic call to validate the address 
  8.     return validator.isValidAddress(address, zip, state); 
  9.   } 
  10.   private AddressValidator getValidator(String custType) { 
  11.     AddressValidator validator = null
  12.     if (custType.equals(Customer.US)) { 
  13.       validator = new USAddress(); 
  14.     } 
  15.     if (type.equals(Customer.CANADA)) { 
  16.       validator = new CAAddressAdapter(); 
  17.     } 
  18.     return validator; 
  19.   } 
  20. }//end of class 
  CAAddressAdapter设计和对AddressValidator(声明期望的接口)对象的多态调用使Customer可以利用接口不兼容CAAddress类提供的服务。

 


  Figure 20.4: Address Validation Application?Using Class Adapter 

 


  Figure 20.5: Address Validation Message Flow?Using Class Adapter 

  作为对象适配器的地址适配器

  当讨论以类适配器来实现地址适配器时,我们说客户类期望的AddressValidator接口是Java接口形式。现在,让我们假设客户类期望 AddressValidator接口是抽象类而不是java接口。因为适配器CAAdapter必须提供抽象类AddressValidatro中声明的接口,适配器必须是AddressValidator抽象类的子类、实现抽象方法。
  1. Listing 20.8: AddressValidator as an Abstract Class 
  2. public abstract class AddressValidator { 
  3.   public abstract boolean isValidAddress(String inp_address, 
  4.      String inp_zip, String inp_state); 
  5. }//end of class 
  6. Listing 20.9: CAAddressAdapter Class 
  7. class CAAddressAdapter extends AddressValidator { 
  8.           … 
  9.           … 
  10.   public CAAddressAdapter(CAAddress address) { 
  11.     objCAAddress = address; 
  12.   } 
  13.   public boolean isValidAddress(String inp_address, 
  14.      String inp_zip, String inp_state) { 
  15.           … 
  16.           … 
  17.   } 
  18. }//end of class 

  因为多继承在JAVA中不支持,现在适配器CAAddressAdapter不能继承现有的CAAddress类,它已经使用了唯一一次继承其他类的机会。

  应用对象适配器模式,CAAddressAdapter可以包含一个适配者CAAddress的一个实例。当适配器第一次创建的时候,这个适配者的实例通过客户端传递给适配器。通常,适配者实例可以通过下面两种方式提供给包装它的适配器。

  (1)    对象适配器的客户端可以传递一个适配者的实例给适配器。这种方式在选择类的形式上有很大的灵活性,但是客户端感知了适配者或者适配过程。这种方法在适配器不但需要适配者对象行为而且需要特定状态时很适合。

  (2)    适配器可以自己创建适配者实例。这种方法相对来说缺乏灵活性。适用于适配器只需要适配者对象的行为而不需要适配者对象的特定状态的情况。

 


  Figure 20.6: Object Adapter for the CAAddress Class 

  Listing 20.10: CAAddressAdapter as an Object Adapter 
  1. class CAAddressAdapter extends AddressValidator { 
  2.   private CAAddress objCAAddress; 
  3.   public CAAddressAdapter(CAAddress address) { 
  4.     objCAAddress = address; 
  5.   } 
  6.   public boolean isValidAddress(String inp_address, 
  7.      String inp_zip, String inp_state) { 
  8.     return objCAAddress.isValidCanadianAddr(inp_address, 
  9.            inp_zip, inp_state); 
  10.   } 
  11. }//end of class 

  当客户对象调用CAAddressAdapter(adapter)上的isValidAddress方法时, 适配器在内部调用CAAddress(adaptee)上的isValidCanadianAddr方法。


 


  Figure 20.7: Address Validation Application?Using Object Adapter 

  从这个例子可以看出,适配器可以使Customer(client)类访问借口不兼容的CAAddress(adaptee)所提供的服务!

 



  Figure 20.8: Address Validation Message Flow?Using Object Adapter

posted @ 2007-03-23 13:12 chenweicai 阅读(334) | 评论 (0)编辑 收藏

Spring的模块化是很强的,各个功能模块都是独立的,我们可以选择的使用。这一章先从Spring的IoC开始。所谓IoC就是一个用XML来定义生成对象的模式,我们看看如果来使用的。
1、数据模型。
1、如下图所示有三个类,Human(人类)是接口,Chinese(中国人)是一个子类,American(美国人)是另外一个子类。


源代码如下:
package cn.com.chengang.spring;
public interface Human {
       void eat();
       void walk();
}
 
package cn.com.chengang.spring;
public class Chinese implements Human {
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#eat()
     */
    public void eat() {
        System.out.println("中国人对吃很有一套");
    }
 
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#walk()
     */
    public void walk() {
        System.out.println("中国人行如飞");
    }
}
 
package cn.com.chengang.spring;
public class American implements Human {
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#eat()
     */
    public void eat() {
        System.out.println("美国人主要以面包为主");
    }
 
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#walk()
     */
    public void walk() {
        System.out.println("美国人以车代步,有四肢退化的趋势");
    }
}
 
2、对以上对象采用工厂模式的用法如下
创建一个工厂类Factory,如下。这个工厂类里定义了两个字符串常量,所标识不同的人种。getHuman方法根据传入参数的字串,来判断要生成什么样的人种。
package cn.com.chengang.spring;
public class Factory {
    public final static String CHINESE = "Chinese";
    public final static String AMERICAN = "American";
 
    public Human getHuman(String ethnic) {
        if (ethnic.equals(CHINESE))
            return new Chinese();
        else if (ethnic.equals(AMERICAN))
            return new American();
        else
            throw new IllegalArgumentException("参数(人种)错误");
    }
}
 
下面是一个测试的程序,使用工厂方法来得到了不同的“人种对象”,并执行相应的方法。
package cn.com.chengang.spring;
public class ClientTest {
    public static void main(String[] args) {
        Human human = null;
        human = new Factory().getHuman(Factory.CHINESE);
        human.eat();
        human.walk();
        human = new Factory().getHuman(Factory.AMERICAN);
        human.eat();
        human.walk();
    }
}
 
控制台的打印结果如下:
 
3、采用Spring的IoC的用法如下:
1、在项目根目录下创建一个bean.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
       <bean id="Chinese" class="cn.com.chengang.spring.Chinese"/>
       <bean id="American" class="cn.com.chengang.spring.American"/>
</beans>
bean.xml的位置如下图,注意不要看花眼把它看成是lib目录下的了,它是在myspring目录下的。
 
2、修改ClientTest程序如下:
package cn.com.chengang.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class ClientTest {
    public final static String CHINESE = "Chinese";
    public final static String AMERICAN = "American";
 
    public static void main(String[] args) {
        //        Human human = null;
        //        human = new Factory().getHuman(Factory.CHINESE);
        //        human.eat();
        //        human.walk();
        //        human = new Factory().getHuman(Factory.AMERICAN);
        //        human.eat();
        //        human.walk();
 
        ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
        Human human = null;
        human = (Human) ctx.getBean(CHINESE);
        human.eat();
        human.walk();
        human = (Human) ctx.getBean(AMERICAN);
        human.eat();
        human.walk();
    }
}
从这个程序可以看到,ctx就相当于原来的Factory工厂,原来的Factory就可以删除掉了。然后又把Factory里的两个常量移到了ClientTest类里,整个程序结构基本一样。
再回头看原来的bean.xml文件的这一句
<bean id="Chinese" class="cn.com.chengang.spring.Chinese"/>
id就是ctx.getBean的参数值,一个字符串。class就是一个类(包名+类名)。然后在ClientTest类里获得Chinese对象就是这么一句
human = (Human) ctx.getBean(CHINESE);
因为getBean方法返回的是Object类型,所以前面要加一个类型转换。
 
4、总结
(1)也许有人说,IoC和工厂模式不是一样的作用吗,用IoC好象还麻烦一点。
       举个例子,如果用户需求发生变化,要把Chinese类修改一下。那么前一种工厂模式,就要更改Factory类的方法,并且重新编译布署。而IoC只需 要将class属性改变一下,并且由于IoC利用了Java反射机制,这些对象是动态生成的,这时我们就可以热插拨Chinese对象(不必把原程序停止 下来重新编译布署)
 
       (2)也许有人说,即然IoC这么好,那么我把系统所有对象都用IoC方式来生成。
       注意,IoC的灵活性是有代价的:设置步骤麻烦、生成对象的方式不直观、反射比正常生成对象在效率上慢一点。因此使用IoC要看有没有必要,我认为比较通用的判断方式是:用到工厂模式的地方都可以考虑用IoC模式。
 
       (3)在上面的IoC的方式里,还有一些可以变化的地方。比如,bean.xml不一定要放在项目录下,也可以放在其他地方,比如cn.com.chengang.spring包里。不过在使用时也要变化一下,如下所示:
new FileSystemXmlApplicationContext("src/cn/com/chengang/spring/bean.xml");
另外,bean.xml也可以改成其他名字。这样我们在系统中就可以分门别类的设置不同的bean.xml。
 
(4)关于IoC的低侵入性。
什 么是低侵入性?如果你用过Struts或EJB就会发现,要继承一些接口或类,才能利用它们的框架开发。这样,系统就被绑定在Struts、EJB上了, 对系统的可移植性产生不利的影响。如果代码中很少涉及某一个框架的代码,那么这个框架就可以称做是一个低侵入性的框架。
Spring的侵入性很低,Humen.java、Chinese.java等几个类都不必继承什么接口或类。但在ClientTest里还是有一些Spring的影子:FileSystemXmlApplicationContext类和ctx.getBean方式等。
现在,低侵入性似乎也成了判定一个框架的实现技术好坏的标准之一。
 
(5)关于bean.xml的用法
bean.xml 的用法还有很多,其中内容是相当丰富的。假设Chinese类里有一个humenName属性(姓名),那么原的bean.xml修改如下。此后生成 Chinese对象时,“陈刚”这个值将自动设置到Chinese类的humenName属性中。而且由于singleton为true这时生成 Chinese对象将采用单例模式,系统仅存在一个Chinese对象实例。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
       <bean id="Chinese" class="cn.com.chengang.spring.Chinese"  singleton="true">
              <property name="humenName">
                     <value>陈刚</value>
              </property>
       </bean>
       <bean id="American" class="cn.com.chengang.spring.American"/>
</beans>
 
关于bean.xml的其它用法,不再详细介绍了,大家自己拿Spring的文档一看就明白了。

posted @ 2007-03-21 21:15 chenweicai 阅读(153) | 评论 (0)编辑 收藏

  Action类是用户请求和业务逻辑之间的桥梁。每个Action充当用户的一项业务代理。在   RequestProcessor类预处理请求时,在创建了Action的实例后,就调用自身的processActionPerform()方法,该方法再调用Action类的execute()方法。Action的execute()方法调用模型的业务方法,完成用户请求的业务逻辑,然后根据执行结果把请求转发给其他合适的Web组件。在实际的应用中,主要有以下几种比较常见的使用方法:  
   
  1.普通的Action应用  
   
  <action   path="/normalAction"  
  type="package.OneActionClass">  
  name="oneForm"  
  input="page.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  <forward   name="failure"   path="failure.jsp"/>  
  </action>  
   
          Struts的ActionServlet接收到一个请求,然后根据struts-config.xml的配置定位到相应的mapping(映射);接下来如果form的范围是request或者在定义的范围中找不到这个form,创建一个新的form实例,如果找到则重用;取得form实例以后,调用其   reset()方法,然后将表单中的参数放入form,如果validate属性不为false,调用validate()方法;如果validate   ()返回非空的ActionErrors,将会被转到input属性指定的URI,如果返回空的ActionErrors,那么执行Action的   execute()方法,根据返回的ActionForward确定目标URI。即execute()仅当validate()成功以后才执行;   input属性指定的是一个URI。  
   
   
  2.有Form的Action应用  
   
  <action   path="/formAction"  
  type="org.apache.struts.actions.ForwardAction"  
  name="oneForm"  
  input="page.jsp"  
  parameter="another.jsp"  
  />  
   
          Struts会在定义的scope搜寻oneForm,如果找到则重用,如果找不到则新建一个实例;取得form实例以后,调用其reset()方法,然后将表单中的参数放入form,如果validate属性不为false,调用validate()方法;如果validate()返回非空的   ActionErrors,将会被转到input属性指定的URI,如果返回空的ActionErrors,那么转到parameter属性指定的目标   URI。  
   
          这种方法使得没有action类可以存放我们的业务逻辑,所以所有需要写入的逻辑都只能写到form的reset()或者validate()方法中。   validate()的作用是验证和访问业务层。因为这里的action映射不包括forward,所以不能重定向,只能用默认的那个forward。这种仅有form的action可以用来处理数据获取并forward到另一个JSP来显示。  
   
   
  3.仅有Action的Action应用  
   
  <action   path="/actionAction"  
  type="package.OneActionClass">  
  input="page.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  <forward   name="failure"   path="failure.jsp"/>  
  </action>  
   
          ActionServlet接收到请求后,取得action类实例,调用execute()方法;然后根据返回的ActionForward在配置中找   forward,forward到指定的URI或action。这样就没有form实例被传入execute()方法,于是execute()必须自己从请求中获取参数。Action可以被forward或者重定向。这种action不能处理通过HTML   FORM提交的请求,只能处理链接式的请求。  
   
   
  4.仅有JSP的Action应用  
   
  <action   path="/jspAction"  
  type="org.apache.struts.actions.ForwardAction"  
  parameter="another.jsp"  
  />  
   
          ActionServlet接到请求后调用ForwardAction的execute()方法,execute()根据配置的parameter属性值来forward到那个URI。这种情况下,没有任何form被实例化,比较现实的情形可能是form在request更高级别的范围中定义;或者这个   action被用作在应用程序编译好后充当系统参数,只需要更改这个配置文件而不需要重新编译系统。  
   
   
  5.两个Action对应一个Form(和第四种方式部分的Action作用相近)  
   
  <action   path="/oneAction"  
  type="package.OneActionClass">  
  name="oneForm"  
  input="one.jsp"  
  <forward   name="success"   path="/anotherAction.do"/>  
  </action>  
  <action   path="/anotherAction"  
  type="package.AnotherActionClass">  
  name="oneForm"  
  input="another.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  </action>  
   
          这个组合模式可以被用来传递form对象,就每个单独的action来讲,处理上并没有和完整的action有什么实质的区别。需要注意的是在后一个   action中同样会调用form的reset()和validate()方法,因此我们必须确保form中的信息不被重写。这种情况分两种方式处理:   a)   在request中放入一个指示器表明前一个action有意向后一个action传递form,从而在后一个action可以保留那个form中的值,这一方式只能在使用forward时使用。b)   当使用redirect而不是forward时,可以把指示器放在session或更高的级别,在命令链的最后一环将这个指示器清除。  
   
   
  6.两个Action对应两个form  
   
  <action   path="/oneAction"  
  type="package.oneActionClass">  
  name="oneForm"  
  input="one.jsp"  
  <forward   name="successful"   path="/anotherAction.do"   redirect="true"/>  
  </action>  
  <action   path="/anotherAction"  
  type="package.AnotherActionClass">"  
  name="anotherForm"  
  input="another.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  </action>  
   
          这个组合方式跟前一种在流程上没有太大区别,只是我们现在对于两个action分别提供了form,于是代码看上去更加清晰。于是我们可以分别处理WEB   应用程序的输入和输出。值得注意的是,后一个action同样会尝试往form中写入那些参数,不过我们可以这样处理:a)   在后一个form中使用另一套属性名;b)   只提供getter而不提供setter。  
   
  基本处理过程:  
          前一个action接收输入、验证、然后将数据写入业务层或持久层,重定向到后一个action,后一个action手动的从业务层/持久层取出数据,写入form(通过其他方式),交给前台JSP显示。这样做的好处是不必保留输入form中的值,因此可以使用redirect而不是forward。这样就降低了两个action之间的耦合度,同时也避免了不必要的重复提交。  
   
  注明:文中所提及的“仅有Form”指的是没有继承Struts提供的Action类,而是直接使用了Struts自身提供的Action类;“仅有Action”指的是仅继承了Struts提供的Action类而没有使用Form。

posted @ 2007-03-20 15:30 chenweicai 阅读(199) | 评论 (0)编辑 收藏

使用Struts有一段时间了,但也仅仅涉及到一部分内容,比如Action做逻辑控制、FormBean对象化用户提交的Form数据、国际化资源文件,Struts很重要的一部分Struts Tag却一直没有使用到,一是因为要熟练的使用Struts Tag需要一定的时间来,二也是因为自己有一套比较好的Tag可用。JSP页面上Tag使用的较多虽然能让页面看起来比较整洁,但可读性会相对降低,开发时的灵活性也会降低。相对来说还是比较看中Action的逻辑控制部分。

重新翻看Struts书本,特别注意了Tag部分,也还是觉得有些眩晕,或许使用熟练是才能体验出其中的玄妙。摘录几个Tag放在这里。

1、<bean:message key="hello.jsp.page.heading"/>
用于输出资源文件中的内容

2、<html:errors/>或者写成<html:error property="xxx"/>
用于输出错误信息,当指定peoperty时,则只显示对应的错误信息,如:
ActionErrors errors = new ActionErrors();
errors.add("xxx",new ActionError("username.null"));

3、<html:form action="/Helloworld">
Form表单

4、<html:text property="userName" size="16"/>
表单中的输入域

5、<html:submit property="submit" value="Submit"/>
提交按钮

6、<html:reset/>
重置按钮

7、<bean:write name="bitiliu" property="userName"/>
从request中或者session中获得bitiliu对象,并输出userName属性的值,可指定scope

8、非空逻辑判断
<logic:notEmpty name="bitiliu" property="userName" scope="request">
 do something...  
</logic:notEmpty>

9、<html:link>
用于生成链接,可以增加参数
<%
 pageContext.setAttribute("name","I am a boy!");

 HashMap myMap = new HashMap(); 
 myMap.put("name","bitiliu");
 myMap.put("password",new String[]{"1","2","3"});
 pageContext.setAttribute("myMap",myMap);
%>
<html:link page="/Test.jsp" paramId="haha" paramName="name" name="myMap">Test</html:link>
生成的超链接为:
<a href="/Struts2/Test.jsp?password=1&amp;password=2&amp;password=3&amp;haha=I+am+a+boy%21&amp;name=bitiliu">Test</a>

10、<html:img>
生成图片标记,如<html:img page="/logo.gif"/>,生成的HTML为<img src="/web/logo.gif">
<html:img>也可以包含请求参数,可参考<html:link>

11、<html:cancel>
增加取消按钮,点击取消按钮也会请求form的action事件,可以在execute方法中通过方法isCancelled(request)来判断是否点击了取消按钮。

12、<html:hidden>
生成隐藏域,如<html:hidden property="name"/>,生成的HTML为<input type="hidden" name="name"/>

13、<html:checkbox property="check">
对应的Form中应该有一对应属性check类型为boolean

14、<html:multibox property="strArray" value="value1">
对应的Form中对应属性strArray类型为String

15、<html:radio property="strArray" value="value1">
同<html:multibox>,但为单选框

16、<html:select>
下拉选择框,可指定property、size和multiple(true\false),标签内可包含<html:option>、<html:options>和<html:optionCollections>

17、<html:option>
可写成:<html:option value="color.orange" key="color.orange"/>或<html:option value="color.orange">Orange</html:option>

18、<html:options>
一次生成多个option,使用示例:
Vector vec = new Vector();
vec.add(new org.apache.struts.util.LabelValueBean("label1","value1"));
vec.add(new org.apache.struts.util.LabelValueBean("label2","value2"));
pageContext.setAttribute("vec",vec);

<html:options collection="vec" property="value" labelProperty="label"/>

19、<html:file>
用于实现文件上传,其中<html:form>的enctype="multipart/form-date",Form的对应属性类型应为File类型,

逻辑判断标签:
1、<logic:equal>
相等,示例如下:
<logic:equal name="strValue" value="112" scope="request">equal</logic:equal>
标签从request中获得strValue对象,然后和112比较,如果相等,则输入字符串equal,否则不输出。下面标签相同。

2、<logic:greateEqual>
大于等于

3、<logic:greaterThan>
大于

4、<logic:lessEqual>
小于等于

5、<logic:lessThan>
小于

6、<logic:notEqual>
不等于

7、<logic:match>
指定的值是变量的子串

8、<logic:notMatch>
指定的值不是变量的子串

9、<logic:iterate>
叠代标签

其它:
1、在国际化文件中添加两个errors.header和errors.footer用于指定错误信息显示时前后追加的内容。

posted @ 2007-03-17 14:32 chenweicai 阅读(635) | 评论 (1)编辑 收藏

struts标签bean

<!-- ################################################################ -->
<!--
访问cookie的信息内容
此标签主要是用于在jsp中将一个cookie附给一个指定的变量
以便于在jsp中应用
-->
<bean:cookie id="唯一标识符" name="存在的cookie属性名"/>
<!-- ################################################################ -->
<!--
访问cookie的信息内容
此标签主要是用于在jsp中将scop中一个的属性附给一个指定的变量
以便于在jsp中应用
-->
<bean:define
  id="唯一标识符"
  name="在page|request|response|session中存在的标识符"
  property="对象中的成员变量"
  scope="page|request|response|session"
  toScope="存放此对象的范围page|request|response|session"
  type="此对象的数据类型(权限定类名)"
  value="默认初始化值"/>
<!-- ################################################################ -->
<!--
访问头部元素的信息内容
<bean:header>标签是用于将头部信息中的一个元素属性附给一个指定的变量
以便于在jsp中应用
-->
<bean:header id="唯一标识符" name="头部元素中存在的属性名"/>
<!-- ################################################################ -->
<!--
访问头部元素的信息内容
<bean:include>标签是用于web应用程序中的一个资源引进当前jsp中,
并且将指向它的一个地址附给指定的变量
以便于在jsp中应用
-->
<bean:include id="唯一标识符" page="包含的web组件的uri路径,以 / 开头" />
<!-- ################################################################ -->
<!--
<bean: page>标签主要用于访问jsp中的隐含对象,
  page|request|response|session|application
  将此属性附给一个指定变量
-->
<bean: page id="唯一标识符" name="jsp中的隐含对象"/>
<!-- ################################################################ -->
<!--
<bean: parameter>标签
  用于访问请求参数 ,
  将此属性附给一个指定的变量,便于在当前jsp中应用
-->
<bean: parameter
  id="唯一标识符"
  name="参数名"
  value="默认值"
  multiple="??????"/>
<!-- ################################################################ -->
<!--
<bean:resource>
访问系统配置中的资源绑定信息 Resource Bundle
此标签的作用是,将指向系统配置中的某个资源的指针,附给指定变量,以便于在当前页中调用
-->
<bean:resource
  id="唯一标识符"
  name="包含的 web uri 路径,以 / 开头"
  input="?????" />
<!-- ################################################################ -->
<!--
<bean:size> 用于取得某个指定数据容器的深度大小 ,并且将此值附给一个指定变量名
-->
<bean:size
  id="唯一标识符"
  name="page|request|response|session中存在的属性变量名"
  property="变量中的成员变量名"
  scope="作用范围page|request|response|session"
  collection="java.util.Collection类变量" />
<!-- ################################################################ -->
<!--
<bean:write> 用于输出指定变量的内容值
-->
<bean:write
  name="page|request|response|session中存在的属性变量名"
  property="变量中的成员变量名"
  filter="true|false"
  format="书写格式"
  formatKey="索引主键,此主键与系统配置文件中的主键区配"
  scope="作用范围page|request|response|session"
  bundle="??????" ignore="??????" locale="??????"
  />
<!-- ################################################################ -->
<!--
<bean:message> 用于输出资源配置中的信息内容
-->
<bean:message
  bundle="系统配置文件中的绑定参数"
  key="与系统配置中的资源文件中的主键区配" />
<bean:message
  bundle="系统配置文件中的绑定参数"
  name="scope中存在的属性名"
  scope="page|request|response|session"
  property="属性对象中的成员变量名,并且此属性变量的值与key的值相同" />

struts标签html

<!--######################################################################-->
<!-- 不带参数的page连接方式 -->
<html:link [page="/XXX.do"|action="/XXX"]>连接内容</html:link>
<!--带参数的一种连接方式-->
<html:link
  [page="/XXX.do?paramName1=value1&amp;paramName2=value2"|
  page="/XXX?paramName1=value1&amp;paramName2=value2"]>
        连接内容
      </html:link>
<html:link [page="/XXX.do"|action="/XXX"]
           paramId="参数名"
           paramName="在page|request|response|session中存在的属性名">
        连接内容
      </html:link>
<!--带参数的一种连接方式-->
<html:link [page="/XXX.do"|action="/XXX"]
           paramId="参数名"
           paramName="配置文件中的BEAN的配置名称"
           paramProperty="配置对象中的成员变量">
        连接内容
      </html:link>
<html:link action="/XXX"
           name="在page|request|response|session中存在的属性名">
        连接内容
      </html:link>
<html:link href="完整的url路径">
        连接内容
      </html:link>
<html:link page="相对于当前操作路径的url">
        连接内容
      </html:link>
<html:link forward="struts配置文件中存在的<global-forwards>元素的子元素<forwar>的name值">
        连接内容
      </html:link>
<!--######################################################################-->
<html:img page="相对于当前操作路径的url" />
<!--######################################################################-->
<html:img src="完整的uri路径"
  paramId="参数名"
  paramName="page|request|response|session中存在的属性名"/>
<!--######################################################################-->
<html:form
  action="xxx.do"
  focus="焦点"
  method="GET|POST|DELETE|PUT|HEAD|OPTIONS" >
<!--######################################################################-->
  <!-- 单行输入框 -->
  <html:text property="prptName">初始值</html:text>
<!--######################################################################-->
  <!-- 隐藏字段 -->
  <html:hidden property="prptName" />
<!--######################################################################-->
  <!-- 密码输入框 -->
  <html: password property="prptName"></html: password>
<!--######################################################################-->
  <!-- 文件获取输入框 -->
  <html:file property="prptName">初始值</html:file>
<!--######################################################################-->
  <!-- 按钮 -->
  <html:button property="prptName">初始值</html:button>
<!--######################################################################-->
  <!-- 提交按钮 -->
  <html:submit property="prptName">初始值</html:submit>
<!--######################################################################-->
  <!-- 取消按钮 -->
  <html:cancel property="prptName">初始值</html:cancel>
<!--######################################################################-->
  <!-- 重置按钮 -->
  <html:reset property="prptName">初始值</html:reset>
<!--######################################################################-->
  <!-- 图片按钮 -->
  <html:image onclick="" src="url" >初始值</html:image>
<!--######################################################################-->
  <!-- 复选框 -->
  <html:checkbox property="prptName">初始值</html:checkbox>
<!--######################################################################-->
  <!--多选框的表达方式一-->
  <html:multibox property="属性名" value="初始值"/>
<!--######################################################################-->
  <!--多选框的表达方式二-->
  <html:multibox property="属性名">初始值</html:multibox>
<!--######################################################################-->
  <!-- 选择列表 -->
  <html:select
    property="prptName"
    size="指定在网页上显示的可选的数目"
    multiple="true|false,此属性用于指定列表是否允许多选">
<!--######################################################################-->
    <html:option
      value="绑定的属性名"
      bundle="系统文件中绑定的属性名"
      key="资源文件中绑定的属性" >lable</html:option>
<!--######################################################################-->
    <html:options
      collection="Vector对象,此对象中放置org.apache.struts.util.LabelValueBean对象"
      property="网页中的value值其值一般是value"
      labelProperty="网页中显示的标签,其值一般是lable"/>
<!--######################################################################-->
    <html:optionsCollection
      name="page|request|response|session中存在的属性名称"
      property="属性对象中的成员变量"
      label="成员变量中的成员变量"
      value="成员变量中的成员变量" />
  </html:select>
</html:form>

struts标签logic

<!--#################################################################-->
<!-- <logic:equal>和<logic:notEqual>判断变量的值与指定常量是否相等 -->
<logic:equal
  name="request,page,response,session中存在的以此名称命名的变量"
  property="此变量中的成员变量"
  parameter="将要取得的变量的标识符,此变量存在于request|page|response|session中"
  scope="取得变量的范围 request|page|response|session"
  value=" 参加比对的值 ">
  若判断成立,则输出此处的内容!
  </logic:equal>
<!--=====================================================================-->
<logic:notEqual
  name="request,page,response,session中存在的以此名称命名的变量"
  property="此变量中的成员变量"
  parameter="将要取得的变量的标识符,此变量存在于request|page|response|session中"
  scope="取得变量的范围 request|page|response|session"
  value=" 参加比对的值 ">
  若判断成立,则输出此处的内容!
  </logic:notEqual>
<!--#################################################################-->
<logic:iterate
  id="唯一标识符"
  name="在request|response|session|page中的标识符"
  property="若是自定义类对象,此处用语表示此对象中的属性名"
  type="取得的对象的权限定类名"
  indexId="indexid"
  offset="起始位置"
  length="循环的长度">
  <li><em><bean:write name="与logic:iterate的属性id的内容一致" /></em>&nbsp;[<bean:write name="index"/>]</li>
</logic:iterate>

<!--#################################################################-->
<!--<logic:empty>与<logic:notEmpty>用于判断指定参数的属性值是否是null值或是空字符串""-->
<!--判断scope中存在的指定参数名的变量值是否是null值或是空字符串,若是null或空字符串则输出标签之间的内容-->
<logic:empty
  name="对象的唯一标识符"
  scope="page|request|response|session"
  property="对象中存在的成员变量">
empty
</logic:empty>
<!--判断scope中存在的指定参数名的变量值是否不是null值或是空字符串,若不是null或空字符串则输出标签之间的内容-->
<logic:notEmpty
  name="对象的唯一标识符"
  scope="page|request|response|session"
  property="对象中存在的成员变量">
notEmpty
</logic:notEmpty>
<!--#################################################################-->
<!--<logic: present>与<logic:notPresent>用于判断指定参数的属性是否存在-->
<!--判断cookie中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic: present
  cookie="cookie中存在的变量名">
  此处是输出内容!
  </logic: present>
<!--判断header中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic: present
  header="取得头部元素中存在的变量">
  此处是输出内容!
  </logic: present>
<!--判断scope中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic: present
  name="属性名"
  property="对象中存在的成员变量"
  scope="page|request|response|session">
  此处是输出内容!
  </logic: present>
<!--=================================================================-->
<!--判断cookie中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic:notPresent
  cookie="cookie中可能存在的变量名">
  此处是输出内容!
  </logic:notPresent>
<!--判断header中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic:notPresent
  header="头部元素中存在的变量">
  此处是输出内容!
  </logic:notPresent>
<!--判断scope中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic:notPresent
  name="属性名"
  property="对象中可能存在的成员变量"
  scope="page|request|response|session">
  此处是输出内容!
  </logic:notPresent>
<!--#################################################################-->
<!--<logic:match>与<logic:notMatch>用于判断指定参数的字符串是否区配某个给定标准-->
<!--判断cookie中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:match
  cookie="cookie中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:match>
<!--判断header中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:match
  header="header中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:match>
<!--判断scope中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:match
  name="存在的属性名"
  property="属性对象中的成员变量"
  scope="page|request|response|session"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:match>
<!--===================================================================-->
<!--判断cookie中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:notMatch
  cookie="cookie中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:notMatch>
<!--判断header中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:notMatch
  header="header中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:notMatch>
<!--判断scope中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:notMatch
  name="存在的属性名"
  property="属性对象中的成员变量"
  scope="page|request|response|session"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:notMatch>
<!--#################################################################-->
<!--<logic:forward>用于地址转向到指定位置-->
<logic:forward name="与系统配置文件中的<global-forward>元素中的子元素<forward>区配"/>
<!--#################################################################-->
<!-- <logic:redirect>用于地址重定向到指定位置 -->
<logic:redirect
  anchor=""
  forward=""
  href=""
  name=""
  page=""
  paramId=""
  paramName=""
  paramProperty=""
  paramScope=""
  property=""
  scope=""
  transaction="">连接内容</logic:redirect>

posted @ 2007-03-17 14:29 chenweicai 阅读(1328) | 评论 (0)编辑 收藏

当序列化遇到继承…

当一个父类实现Serializable接口后,他的子类都将自动的实现序列化。

以下验证了这一点:
package InherSerialTest;

import java.io.Serializable;

public class SuperA implements Serializable {

 private int supervalue;

 public SuperA(int supervalue) {
  super();
  // TODO Auto-generated constructor stub
  this.supervalue = supervalue;
 }
 
 public int getSupervalue() {
  return supervalue;
 }

 public void setSupervalue(int supervalue) {
  this.supervalue = supervalue;
 }

 public String toString(){
  return "supervalue is :" + supervalue;
 }
}

package InherSerialTest;

public class SubB extends SuperA {

 private int subvalue;

 public SubB(int supervalue, int subvalue) {
  super(supervalue);
  // TODO Auto-generated constructor stub
  this.subvalue = subvalue;
 }

 public int getSubvalue() {
  return subvalue;
 }

 public void setSubvalue(int subvalue) {
  this.subvalue = subvalue;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return super.toString() + " ,subvalue " + subvalue;
 }
 
}

package InherSerialTest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class InherSerialTest {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub

  SubB sub = new SubB(100,200);
  
  try{
   ObjectOutputStream oos = new ObjectOutputStream(
     new FileOutputStream("c:\\InherSerialTest.txt"));
   oos.writeObject(sub);
   oos.close();
   
   ObjectInputStream ois = new ObjectInputStream(
     new FileInputStream("c:\\InherSerialTest.txt"));
   SubB sub2 = (SubB)ois.readObject();
   System.out.println(sub2);
   ois.close();
  }catch(Exception e){
   e.printStackTrace();
  }
 }

}


结果是:supervalue is :100 ,subvalue 200

怎管让子类实现序列化看起来是一件很简单的事情,但有的时候,往往我们不能够让父类实现Serializable接口,原因是有时候父类是抽象的(这并没有关系),并且父类不能够强制每个子类都拥有序列化的能力。换句话说父类设计的目的仅仅是为了被继承。

要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类是一件很麻烦的事情。java docs中提到:

“To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime. ”

也就是说,要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类要做两件事情:

其一,父类要有一个无参的constructor;

其二,子类要负责序列化(反序列化)父类的域。
如果我们将上列中的SuperA没有实现Serializable接口,而是在SubB类中实现Serializable接口的话,即:

public class SuperA  {}
public class SubB extends SuperA implements Serializable{}

我们再次运行时,将会产生错误:

java.io.InvalidClassException: InherSerialTest.SubB; no valid constructor
 at java.io.ObjectStreamClass.<init>(Unknown Source)
 at java.io.ObjectStreamClass.lookup(Unknown Source)
 at java.io.ObjectOutputStream.writeObject0(Unknown Source)
 at java.io.ObjectOutputStream.writeObject(Unknown Source)
 at InherSerialTest.InherSerialTest.main(InherSerialTest.java:21)

果真如docs中所说的一样,父类缺少无参构造函数是不行的。



接下来,按照docs中的建议我们改写这个例子:
package InherSerialTest2;

import java.io.Serializable;

public abstract class SuperC {

 int supervalue;

 public SuperC(int supervalue) {
  super();
  // TODO Auto-generated constructor stub
  this.supervalue = supervalue;
 }
 
// 父类没有实现Serializable接口,子类要可序列化的话,父类必须要有无参构造函数
 public SuperC(){
  
 }

 public int getSupervalue() {
  return supervalue;
 }

 public void setSupervalue(int supervalue) {
  this.supervalue = supervalue;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return "supervalue : " + supervalue;
 }

}

package InherSerialTest2;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

import InherSerialTest.SuperA;

public class SubC extends SuperC implements Serializable{
 
 private int subvalue;

 public SubC(int supervalue, int subvalue) {
  super(supervalue);
  // TODO Auto-generated constructor stub
  this.subvalue = subvalue;
 }

 public int getSubvalue() {
  return subvalue;
 }

 public void setSubvalue(int subvalue) {
  this.subvalue = subvalue;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return super.toString() + " ,subvalue :" + subvalue;
 }
 
 private void writeObject(java.io.ObjectOutputStream oos)throws IOException{
  
  //先序列化对象
  oos.defaultWriteObject();
  //在序列化父类的域
  oos.writeInt(supervalue);
 }
 
 private void readObject(ObjectInputStream ois)throws IOException, ClassNotFoundException{
  
  //先反序列化对象
  ois.defaultReadObject();
  //再反序列化父类的域
  supervalue = ois.readInt();
 }
}

测试成功!

posted @ 2007-03-09 22:44 chenweicai 阅读(458) | 评论 (0)编辑 收藏

序列化概述

简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!

问题的引出:

如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就是对象引用!举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!

以下序列化机制的解决方案:

1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)

2.当要保存一个对象时,先检查该对象是否被保存了。

3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象

通过以上的步骤序列化机制解决了对象引用的问题!

序列化的实现

将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
package serializable;

import java.io.Serializable;

public class Employee implements Serializable {

 private String name;
 
 private double salary;

 public Employee(String name, double salary) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
  this.salary = salary;
 }
 
 public void raiseSalary(double byPercent){
  double temp = salary * byPercent / 100;
  salary += temp;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return getClass().getName() +
   "[ Name = " + name + ", salary = " + salary +"]";
 }

package serializable;

public class Manager extends Employee {
 
 private Employee secretary;

 public Manager(String name, double salary) {
  super(name, salary);
  // TODO Auto-generated constructor stub
  secretary = null;
 }

 public Employee getSecretary() {
  return secretary;
 }

 public void setSecretary(Employee secretary) {
  this.secretary = secretary;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return super.toString() + "[ secretary = " + secretary +"]";
 }
 
}

package serializable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
 
 public static void main(String[] args){
  
  Employee employee = new Employee("LiLei", 1000);
  Manager manager1 = new Manager("Jim", 20000);
  manager1.setSecretary(employee);
  
  Employee[] staff = new Employee[2];
  staff[0] = employee;
  staff[1] = manager1;
  
  try{
   ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
   oos.writeObject(staff);
   oos.close();
   
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.dat"));
   Employee[] newStaff = (Employee[])ois.readObject();
   ois.close();
   
   newStaff[0].raiseSalary(1000);
   
   for(int i=0; i<newStaff.length; i++)
    System.out.println(newStaff[i]);
   
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

修改默认的序列化机制 

在序列化的过程中,有些数据字段我们不想将其序列化,对于此类字段我们只需要在定义时给它加上transient关键字即可,对于transient字段序列化机制会跳过不会将其写入文件,当然也不可被恢复。但有时我们想将某一字段序列化,但它在SDK中的定义却是不可序列化的类型,这样的话我们也必须把他标注为transient,可是不能写入又怎么恢复呢?好在序列化机制为包含这种特殊问题的类提供了如下的方法定义:

private void readObject(ObjectInputStream in) throws

IOException, ClassNotFoundException;

private void writeObject(ObjectOutputStream out) throws

IOException;

(注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的)

使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。

下面是一个典型的例子,java.awt.geom包中的Point2D.Double类就是不可序列化的,因为该类没有实现Serializable接口,在我的例子中将把它当作LabeledPoint类中的一个数据字段,并演示如何将其序列化
package transientTest;

import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class LabeledPoint implements Serializable {

 private String label;
 transient private Point2D.Double point;
 
 public LabeledPoint(String label, double x, double y) {
  super();
  // TODO Auto-generated constructor stub
  this.label = label;
  this.point = new Point2D.Double(x,y);
 }
 
 private void writeObject(ObjectOutputStream oos)throws IOException{
  
  oos.defaultWriteObject();
  oos.writeDouble(point.getX());
  oos.writeDouble(point.getY());
 }
 
 private void readObject(ObjectInputStream ois)throws IOException, ClassNotFoundException{
  
  ois.defaultReadObject();
  double x = ois.readDouble() + 1.0;
  double y = ois.readDouble() + 1.0;
  point = new Point2D.Double(x,y);
 }

 public String toString() {
  // TODO Auto-generated method stub
  return getClass().getName() + "[ Label = " + label + ", point.getX() = "
   + point.getX() + ", point.getY() = " + point.getY() + "]";
 }
}

package transientTest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class transientTest {

 public static void main(String[] args){
  LabeledPoint label = new LabeledPoint("Book", 5.0, 5.0);
  
  try{
   System.out.println("before:\n" + label);
   
   ObjectOutputStream oos = new ObjectOutputStream(
     new FileOutputStream("c:\\label.txt"));
   oos.writeObject(label);
   oos.close();
   
   System.out.println("after:\n" + label);
   ObjectInputStream ois = new ObjectInputStream(
     new FileInputStream("c:\\label.txt"));
   LabeledPoint label1 = (LabeledPoint)ois.readObject();
   ois.close();
   
   System.out.println("after add 1.0:\n" + label);
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

posted @ 2007-03-09 20:13 chenweicai 阅读(370) | 评论 (0)编辑 收藏

摘要:本文介绍了J2EE的分层结构,深入研究了如何使用Session Facade模式和ValueObject 模式设计EJB,并对其开发过程做了较详细的说明。

关键字:EJB ;值对象模式;会话外观模式
一、概述

  与传统的二层体系结构相比,J2EE有两个特点:

  1、定义了一套标准化组件,通过为这些组件提供完整的服务。

  2、使用多层分布式的应用程序模型。应用程序的逻辑根据其实现的不同功能被封装到不同的组件中。如图1所示。





  这种多层结构使企业级应用具有很强的伸缩性,允许各层专注于某种特定的角色:

  1、Client Tier用于显示。

  2、Web Tier用于生成动态显示。

  3、Business Tier用于实现业务逻辑。

  4、EIS Tier用于数据库服务。

  其中,用于实现业务逻辑的EJB组件架构是J2EE的基础和最重要的部分。

  正是认识到J2EE平台作为一种可扩展的、全功能的平台,可以将关键的企业应用扩展到任何Web浏览器上并可适合多种不同的Internet数据流、可连接到几乎任何一种传统数据库和解决方案,J2EE已经成为开发电子商务应用的事实标准。

  为了使开发者开发出规范的、可重用的应用程序,J2EE为我们提供了大量的模式。模式尽管有时不易理解,但使用却非常简单,它提供了强大的可重用机制,避免了开发者和设计者的重复投资。

  可是,面对如此多的模式,初学者往往不知如何下手,为此,作者结合以往的开发经验,详细介绍如何使用模式完成EJB的设计。
二、设计与实现

  1.值对象模式

  J2EE应用程序把服务器端业务组件实现为会话Bean和实体Bean。对于实体Bean的创建,开发人员通常采用CMP(容器管理持久性)模式,其好处在于容器提供公共的服务,例如目录服务、事务管理、安全性、持久性、资源缓冲池以及容错性等,使开发人员不必维护将会集成到业务逻辑中的系统级代码,只需专注于商业逻辑。

  一般来说,有了实体bean,就可以通过调用业务组件的一些方法向客户端返回数据。初学者往往会认为既然客户端可以与服务器通信,那么任务就算完成了。可是,问题恰恰出在这里。业务组件的get方法只能返回一个属性值,这就导致需要获得所有属性值的客户端需要多次调用业务对象的get方法,如图2-1所示。每次调用都是一次网络调用,都会造成系统性能的退化,当调用次数增多时,系统性能就会严重下降。

  这就要求有一种方法使客户端可以一次调用得到所需的大量数据,这种方法就是Value Object(值对象)模式。值对象是任意的可串行化的Java对象,也被称为值的对象,它在一次网络传输中包含和封装了大量的数据并被保存在内存中。这样,当客户端需要再次使用数据的时候,不用再次到数据库中查询,而是直接在内存中读取值对象,节省了大量的时间和系统开销,如图2-2。

  值对象模式有两种策略――可更新的值对象策略和多值对象策略。

  可更新的值对象策略中,业务对象负责创建值对象,并且在客户端请求时把该值对象返回给客户端;同时,业务对象也可以从客户端接收数据,形成值对象,并使用该对象来完成更新。

  例如,在银行系统的例子中,Account 中提供一个以AccountValue为参数的setAccountValueObject方法,这样客户端可以通过这个方法来设置值对象的值,而不采用实体bean--Account中设置每个属性的方法(setBalance()),因为后一种方法会导致大量的网络负载。由于值对象的易变性,所以值对象类必须给每个可以被客户端更新的属性提供设置方法。例如,AccountValue中的setBalance()方法。这样,一旦某客户端拥有来自业务对象的值对象,客户端就可以在本地调用必要的设置方法来更改属性值,然后调用业务对象的setAccountValueObject()方法更新业务对象。

  多值对象策略

  一些应用程序业务对象往往比较复杂,在这种情况下,根据客户端请求不同,有可能单个业务对象会产生多个不同的值对象。在这种情况下,可以考虑采用多值对象策略。这种策略的实现比较简单,就是在entity bean中增加不同的Get×××ValueObject()方法和set×××ValueObject()方法。

  2.Session Facade 模式

  有了实体Bean,客户端就可以直接调用它以获得数据。也就是说实体Bean封装了业务数据,并把他们的接口暴露给客户,因而也就把分布式服务的复杂性暴露给客户。在对J2EE 应用程序环境下,一般会产生如下问题:

  1、紧密耦合,这回导致客户端和业务对象的直接依赖关系

  2、客户端和服务器之间的网络方法调用太多,容易导致网络性能问题

  3、缺乏统一的客户访问策略,容易误用业务对象

  4、如果实体bean的API改动,那么用户端的一些代码也要修改,扩展性很差



  解决这些问题的方法就是把客户端和实体bean分割开。本文采用Session Facade模式,如图3-2所示。该模式通过一个Session Bean,为一系列的实体bean提供统一的接口来实现流程。事实上,客户端只是使用这个接口来触发流程。这样,所有关于实体bean实现流程所需要的改变,都和客户端无关。当实体bean改变时,我们不用改变客户端的代码,只要对Session Bean做出相应的改变即可,大大提高了系统的可维护性。

  通过实体bean来表示业务对象是session facade的最常见用法。但多个实体bean参与某用例时,不必向客户暴露所有实体bean。相反的,用session bean 包装这些实体bean ,并且提供粗粒度方法来执行所需的业务功能,从而隐藏了实体bean交互的复杂性。

  但是千万不要以为Facade模式就是简单的用Session Bean把Entity Bean的所有方法统统封装起来,而不提供任何额外的抽象。其实这是对Facade模式的滥用。这样做并不是降低整个系统的复杂性,而是把复杂性转移到另一个对象上。

  正确应用Facade模式应遵循三条基本原则:

  1、他们自己不作实际工作,而是委派其他对象作实际工作。

  2、他们提供简单的接口。

  3、他们是底层系统的客户端接口。他们应该把特定于子系统的信息封装起来,并且不应该在不必要的情况下公开它。
三、具体代码

  下面用一个简单的银行系统的例子来解释Facade模式和Value Object模式的具体应用。

  创建Entity Bean。其中对每个属性的get和set方法是自动生成的,我们不去管它。

public interface Account extends javax.ejb.EJBObject {
private AccountValue creaeAccountValueObject();
void setAccountVauleObject(AccountValue v);
AccountValue getAccountValueObject();
……}

  其中

private AccountValue createAccountValueObject(){
AccountValue vo=new AccountValue();
vo. accountNumber=accountNumber;
Vo.balance=balance;
……}
public AccountValue getAccountValueObject(){
return createAccountValueObject();
}
public void setAccountValueObject(AccountValue v){
accountNumber=v. accountNumber;
balance=v.balance;
……}

  用值对象封装Entity Bean数据。

public class AccountValue implements java.io.Serializable {
private java.lang.String accountNumber;
private double balance;
void setBalance(double newValue)
……}

  用Factory或者是Action类逻辑方法,涉及到数据的地方使用值对象。

public class AccountFactory {
 private static AccountHome accountHome = null;
  ……
 public java.util.Vector getAccounts(String userid) throws FactoryException {
  try { Vector vect = new Vector();
   AccountHome home = getAccountHome();
   Enumeration accountRefs = home.findByUserid(userid);
   while (accountRefs.hasMoreElements()) {
    Account acc = (Account)accountRefs.nextElement();
    AccountValue valueObject =acc.getAccountValueObjcet();
    vect.addElement(valueObject);}
    return vect; }
  ……}

  在Session Bean的get方法中调用Factory或者是Action对象。

public java.util.Vector getAccounts(String userid) throws FactoryException {
 AccountFactory fact = new AccountFactory();
 Vector result = fact.getAccounts(userid);
 return result;
}

  正如代码所示,使用session facade模式,可以

  1、提供统一的接口:会话外观抽象了业务组件交互的复杂性,并且向客户端提供一个更简单的接口。

  2、减少耦合提高可管理性:会话外观分离了业务对象和客户端,这样可以减少紧密耦合,以及客户端对业务对象的依赖性。

  3、提供粗粒度访问:会话外观减少客户端和服务器之间的网络负载。客户端与业务数据的说有交互都是通过会话外观以粗粒度的发拿过是进行的。

  通过使用

  从上述代码可以看出,使用模式之后,大大改善了系统性能,也提高了代码的可重用性。此外,开发者也可以采用其他的小模式来提高系统性能,比如服务器定位模式,在此不作进一步介绍。

  四、总结

  综上所述,本文详细地介绍了使用值对象模式和会话模式设计商业逻辑层的方法,很好的实现了数据封装和合理分层,大大提高了系统的可维护性和可伸缩性,也显著的简化了具有可伸缩性和高度负责的企业级应用的开发。

  注:航天自然基金赞助支持

posted @ 2007-03-03 15:33 chenweicai 阅读(144) | 评论 (0)编辑 收藏

仅列出标题
共6页: 上一页 1 2 3 4 5 6 下一页