题记什么是优雅?就是当你用“呕”传达你的目的,这是谈吐优雅;用“呕”实现你的思想,这是设计优雅;用“呕”充斥你的视听,这是十分优雅。
听到这里,L老师在我背上施以一记老拳,气急败坏地跺脚:“吐出来,全部吐出来。”
正文 在讨论中,我们经常会说到XO或者XXO,我搜集整理一下,列在下面:
VO——Value Object 是值对象的缩写。意即很简单的仅仅保存数据的类,在Java中又可称为JavaBean,形如:
//Order Data
public class OrderData {
private Long id;
private Integer iType; //the type of order, enumerate
private String strName; // order name
private java.util.Vector vctOrderDetail; // Order details
private PosData posData; // POS、柜台
public Long getID(){
return ID;
}
public void setID(Long id){
this.id = id;
}
public Integer getIType() {
return iType;
}
public void setIType(Integer iType){
this.iType = iType;
}
public String getStrName () {
return strName;
}
public void setStrTitle(String strName){
this.strName = strName;
}
public java.util.Vector getVctOrderDetail () {
return vctOrderDetail;
}
public void setVctOrderDetail(Vector vctOrderDetail){
this.vctOrderDetail = vctOrderDetail;
}
public PosData getPosData () {
return posData;
}
public void setPosData(PosData data){
this.posData = data;
}
}
...//Here skip OrderDetail and PosData
| 在.Net中也有类似的Setter和Getter的写法。显然这样的写法对于用代码行数统计工作量是很不准确的。
DTO——Data Transition Object 还是Value Object,只是用于特殊的目的。在Java中对EJB的调用,组织好的数据会以Value Object的方式传递,如调用参数、返回值。之所以加上“Transition”是因为对EJB的调用往往是RMI方式。
随着“J2EE without EJB”的趋势,往往会用DTO来代替VO对值对象的称呼,原因是VO有歧义,它还有View Object的意思。
另外一种解释是Data Object的简称,还是值对象的意思。
VO——View Object 我们往往用VO表示View Object,而不再指Value Object。在分层结构中,表示层(presentation)用到的数据类。在C/S、Applet、Jsp/Servlet这些结构中,往往还是二进制(值对象)的形式;而在XMLHttp协议的应用中,则可能是一段文本(XML或者JavaScript对象)。下面是使用XMLHttp中的一段文本示例:
<OrderData>
<id>1</id>
<iType>1</iType>
<strName>This is a test order.</strName>
<OrderDetail>
<ID>20012</ID>
<Goods_Name>鼠标</Goods_Name>
<amount>5</amount>
<price>25.00</price>
<OrderDetail>
<OrderDetail>
<ID>20013</ID>
<Goods_Name>键盘</Goods_Name>
<amount>10</amount>
<price>15.00</price>
<OrderDetail>
...
</OrderData>
| 当浏览器得到这份文本,通过JavaScript解析就可以把相应的数据显示在画面上。当然你也可以认为这份数据是由表示层组织好后向后传递的。但是,我们不能忽略这样一些因素,业务数据并不能涵盖所有的需要显示的数据,比如我们需要将所有的订单类型列出来供客户选择;在WEB编程中,我们往往需要记住一些状态,以提供给浏览器,如token的使用。这样我们需要对前例中的Value Object做些相应的调整,如增加显示用的单元:
public class OrderDataVO {
private Long id;
private Integer iType; //the type of order, enumerate
private String strName; // order name
private java.util.Vector vctOrderDetail; // Order details
private PosData posData; // POS、柜台
private String strTypeName; // current type name
private String[] iTypeNames; // for type list
private Integer[] iTypeIDs; // for type list
...
}
|
BO 即Business Object,在分层结构中,业务实现用到的数据。如前例Value Object就可以认为是一个BO。由于BO与PO紧密联系,而且这里有领域模型与失血模型的说法,我们现在看到的是失血模型。关于BO的介绍我将在PO中继续。
我们可以看到,表现层后面、业务层的前面有一个工作:将BO组织成VO提供给表现层,将VO提取成BO提交给业务层,这是控制层的工作之一。
PO——Persistent Object 在面向对象的编程(OOP)中,我们必须关心对象的持久化(简单点,就是数据的保存)。PO就是用来做持久化的类。前例的Value Object不是一个PO。在这里我们假设数据源是关系型数据库(我们大多也只会用到这样的数据源)。
PO对应的是数据库中一个或一组数据。如上述的Order,在持久化时需要保存一条主单信息,多条明细信息。如果我们的映射工具(O/R Mapping)足够强大,使我们可以忽略数据库中的关系(外键,如表设计时,需要在明细中记录主单ID),这样的PO可以认为是与BO等价的。我们可以执行“OrderPO.save()”就能够实现多条数据的持久化。
然而如果我们的工具不够强大,不能完整地解决所有映射关系,那我们需要将一个BO拆解成多个PO去持久。也就是以个PO往往与一个表对应。在Order例中,我们需要先保存主单PO,然后取出主单ID,插入到所有的明细PO中,再对明细持久化,并且这一系列操作应该在一个事务中实现。
PO有三种状态:
1.Transient,未被持久化的,此时的PO就是一个Value Object,由VM管理其生命周期;
2.Persistent,已被持久化的,且在框架工具管理的Session内。此时映射数据库数据,由数据库管理生命周期。也就是说,此时的PO对应的是数据库中的记录,它的创建、更改和删除对应的是数据库中记录的Insert、Update和Delete;而内存中其他的数据对象,它们的生命周期是由虚拟机来维护的,对应的是New、Set和Destroy。
3.Detached,曾被持久化的,但现在和Session已经分离,以Value Object的身份在运行。
在O/R Mapping工具成熟之前,由于每个项目都会遇到类似问题,我们是如何处理的?
继承抽象类的方式:
public abstract class TO{
private int _getNewId(Connection cnct) {. . .}
private int insert(Connection cnct){. . .}
private int update{Connection cnct}{. . .}
public void deleteByID(Integer id, Connection conn){. . .}
public void getByID(Integer id, Connection conn){. . .}
public int getID(){. . .}
public int save(Connection conn){. . .}
public abstract String getIdentity();
public abstract String getTBName();
}
| 这个TO提供了大部分的对表操作的公共方法,如果具体对某个表进行操作,我们只需要继承这个抽象类即可:
public TBOrder extends TO{
public Long id;
public Integer iType; //the type of order, enumerate
public String strName; // order name
public java.util.Vector vctOrderDetail; // Order details
public PosData posData; // POS、柜台
public String getIdentity(){
return “id”;
}
public String getTBName(){
return “TB_Order”;
}
...
}
| 如果构造的TBOrder属性与实际表TB_Order字段一致,TO利用反射就可以构造操作表的SQL。如果要维护主从表这种一对多关系,我们可以要求开发者在实现TBOrder时进行硬代码,先存主TBOrder,再存TBDetail。我们也可以用配置文件记录这些类与表的映射。
还有一种方式也有一定的代表性,用动态Map,这样开发时不必写过多的类。
public final class PO{
private Map mapData;
private String strClassName;
private int iState = 0; //PO状态,0:初始化未入库时;1:从库中取出,可修改;2:删除标志
private int iSubscript = -1; //PO_Class[]中的下标,默认为-1
private int iDataChage = 0; //check for update
private boolean bLobFlag = false;
. . .
}
| 上例是一个PO类通过配置文件,映射了所有的数据库表。这种方式在开发时会带来一些不便,请比较一下“PO.getID()”与“(Long)PO.getProperty(“ID”)”在开发时的异同(我不是指敲的字不一样多哦)。
因此由于需要记录状态位,PO最不可能的便是值对象,但我们使用Hibernate或者JDO这些工具时,并没有额外要求我们继承某个特殊的接口或者留下状态位。它们是如何做的? 这里稍微引用点IOC & AOP的知识,我们知道AOP的织入(Wave)时间有编译时、载入时和运行时三种。
JDO使用了编译时:我们写完Value Object类后,需要用JDO提供的小工具对编译后的二进制跑一下,生成了真正的PO。这个被称为buildtime class enhancement。
JDO2和Hibernate则利用CGLIB2,在载入时/运行时对我们写的Value Object类进行增强,使其具有状态位。如果你在运行时把Hibernate中的PO导出并反编译,会发现此PO非彼PO。不过由于要在第一次加载Class到VM的时候要做动态产生bytecode和内存占用较多(由于要缓存产生的bytecode),这是他们的缺点。
TO——Table Object 实际是PO的一种,只是框架没有提供更强的关系映射,只能用类来映射某张表,如上例中用到的TO。
DAO——Data Access Object 请注意,这是一种模式,而不是某个类。应用这个模式最大的目的是抽象数据源,提供对数据的透明访问:
1.管理Transaction
2.封装DataSource,JDBC
3.便于进行统计/log操作
4.便于进行权限控制
SDO——Service Data Object 这是IBM的SOA架构中几个要素中的一个,是以上提到的DTO、VO、BO等的泛称。拥有动态模型(类似于Map)和静态模型(类似于ValueObject)两种。
如果接触Java,可能还会听到以下两个O:
PICO 是杰出的IOC & AOP 容器之一,全面是PICOContainer。
POJO
Plain-Old Java Object,即Value Object。是在O/R Mapping中被推广的一种做法:不依赖继承;可以轻松的换掉持久框架而不影响程序。
后记LB听到这里,诡异地笑了笑,“听过DSO么?”
“不知道,愿闻其详。”
“我们‘摸大象’的都知道DSO是‘Data Store Object’,在诊察终了后的用来存储缓存数据的。小样,你新来的吧?!”
L老师点点头,语重心长得说,“我们出来吐的,迟早是要吃回去的。不过也没关系,年轻人,吐啊吐啊的就习惯了。”
跋 文中故事纯系虚构,文中概念纯属抄袭。 |
|