linugb118--java space

Java

#

DSL 用于Java开发中

使用Java实现内部领域特定语言

作者 Alex Ruiz and Jeff Bay 译者 沙晓兰 发布于 2008年3月12日 上午1时4分

社区
Java
主题
领域特定语言
标签
语言特性,
模式,
语言

简介

领域特定语言(DSL)通常被定义为一种特别针对某类特殊问题的计算机语言,它不打算解决其领域外的问题。对于DSL的正式研究已经持续很多年,直到最近,在程序员试图采用最易读并且简炼的方法来解决他们的问题的时候,内部DSL意外地被写入程序中。近来,随着关于Ruby和其他一些动态语言的出现,程序员对DSL的兴趣越来越浓。这些结构松散的语言给DSL提供某种方法,使得DSL允许最少的语法以及对某种特殊语言最直接的表现。但是,放弃编译器和使用类似Eclipse这样最强大的现代集成开发环境无疑是该方式的一大缺点。然而,作者终于成功地找到了这两个方法的折衷解决方式,并且,他们将证明该折衷方法不但可能,而且对于使用Java这样的结构性语言从面向DSL的方式来设计API很有帮助。本文将描述怎样使用Java语言来编写领域特定语言,并将建议一些组建DSL语言时可采用的模式。

Java适合用来创建内部领域特定语言吗?

在我们审视Java语言是否可以作为创建DSL的工具之前,我们首先需要引进“内部DSL”这个概念。一个内部DSL在由应用软件的主编程语言创建,对定制编译器和解析器的创建(和维护)都没有任何要求。Martin Fowler曾编写过大量各种类型的DSL,无论是内部的还是外部的,每种类型他都编写过一些不错的例子。但使用像Java这样的语言来创建DSL,他却仅仅一笔带过。

另外还要着重提出的很重要的一点是,在DSL和API两者间其实很难区分。在内部DSL的例子中,他们本质上几乎是一样的。在联想到DSL这个词汇的时候,我们其实是在利用主编程语言在有限的范围内创建易读的API。“内部DSL”几乎是一个特定领域内针对特定问题而创建的极具可读性的API的代名词。

任何内部DSL都受它基础语言的文法结构的限制。比如在使用Java的情况下,大括弧,小括弧和分号的使用是必须的,并且缺少闭包和元编程有可能会导致DSL比使用动态语言创建来的更冗长。

但从光明的一面来看,通过使用Java,我们同时能利用强大且成熟的类似于Eclipse和IntelliJ IDEA的集成开发环境,由于这些集成开发环境“自动完成(auto-complete)”、自动重构和debug等特性,使得DSL的创建、使用和维护来的更加简单。另外,Java5中的一些新特性(比如generic、varargs 和static imports)可以帮助我们创建比以往任何版本任何语言都简洁的API。

一般来说,使用Java编写的DSL不会造就一门业务用户可以上手的语言,而会是一种业务用户也会觉得易读的语言,同时,从程序员的角度,它也会是一种阅读和编写都很直接的语言。和外部DSL或由动态语言编写的DSL相比有优势,那就是编译器可以增强纠错能力并标识不合适的使用,而Ruby或Pearl会“愉快接受”荒谬的input并在运行时失败。这可以大大减少冗长的测试,并极大地提高应用程序的质量。然而,以这样的方式利用编译器来提高质量是一门艺术,目前,很多程序员都在为尽力满足编译器而非利用它来创建一种使用语法来增强语义的语言。

利用Java来创建DSL有利有弊。最终,你的业务需求和你所工作的环境将决定这个选择正确与否。

将Java作为内部DSL的平台

动态构建SQL是一个很好的例子,其建造了一个DSL以适合SQL领域,获得了引人注意的优势。

传统的使用SQL的Java代码一般类似于:

String sql = "select id, name " +
"from customers c, order o " +
"where " +
"c.since >= sysdate - 30 and " +
"sum(o.total) > " + significantTotal + " and " +
"c.id = o.customer_id and " +
"nvl(c.status, 'DROPPED') != 'DROPPED'";

从作者最近工作的系统中摘录的另一个表达方式是:

Table c = CUSTOMER.alias();
Table o = ORDER.alias();
Clause recent = c.SINCE.laterThan(daysEarlier(30));
Clause hasSignificantOrders = o.TOTAT.sum().isAbove(significantTotal);
Clause ordersMatch = c.ID.matches(o.CUSTOMER_ID);
Clause activeCustomer = c.STATUS.isNotNullOr("DROPPED");
String sql = CUSTOMERS.where(recent.and(hasSignificantOrders)
.and(ordersMatch)
.and(activeCustomer)
.select(c.ID, c.NAME)
.sql();

这个DSL版本有几项优点。后者能够透明地适应转换到使用PreparedStatement的方法——用String拼写SQL的版本则需要大量的修改才能适应转换到使用捆绑变量的方法。如果引用不正确或者一个integer变量被传递到date column作比较的话,后者版本根本无法通过编译。代码“nvl(foo, 'X') != 'X'”是Oracle SQL中的一种特殊形式,这个句型对于非Oracle SQL程序员或不熟悉SQL的人来说很难读懂。例如在SQL Server方言中,该代码应该这样表达“(foo is null or foo != 'X')”。但通过使用更易理解、更像人类语言的“isNotNullOr(rejectedValue)”来替代这段代码的话,显然会更具阅读性,并且系统也能够受到保护,从而避免将来为了利用另一个数据库供应商的设施而不得不修改最初的代码实现。

使用Java创建内部DSL

创建DSL最好的方法是,首先将所需的API原型化,然后在基础语言的约束下将它实现。DSL的实现将会牵涉到连续不断的测试来肯定我们的开发确实瞄准了正确的方向。该“原型-测试”方法正是测试驱动开发模式(TDD-Test-Driven Development)所提倡的。

在使用Java来创建DSL的时候,我们可能想通过一个连贯接口(fluent interface)来创建DSL。连贯接口可以对我们所想要建模的领域问题提供一个简介但易读的表示。连贯接口的实现采用方法链接(method chaining)。但有一点很重要,方法链接本身不足以创建DSL。一个很好的例子是Java的StringBuilder,它的方法“append”总是返回一个同样的StringBuilder的实例。这里有一个例子:

StringBuilder b = new StringBuilder();
b.append("Hello. My name is ")
.append(name)
.append(" and my age is ")
.append(age);

该范例并不解决任何领域特定问题。

除了方法链接外,静态工厂方法(static factory method)和import对于创建简洁易读的DSL来说是不错的助手。在下面的章节中,我们将更详细地讲到这些技术。

1.方法链接(Method Chaining)

使用方法链接来创建DSL有两种方式,这两种方式都涉及到链接中方法的返回值。我们的选择是返回this或者返回一个中间对象,这决定于我们试图要所达到的目的。

1.1 返回this

在可以以下列方式来调用链接中方法的时候,我们通常返回this

  • 可选择的
  • 以任何次序调用
  • 可以调用任何次数

我们发现运用这个方法的两个用例:

  1. 相关对象行为链接
  2. 一个对象的简单构造/配置

1.1.1 相关对象行为链接

很多次,我们只在企图减少代码中不必要的文本时,才通过模拟分派“多信息”(或多方法调用)给同一个对象而将对象的方法进行链接。下面的代码段显示的是一个用来测试Swing GUI的API。测试所证实的是,如果一个用户试图不输入她的密码而登录到系统中的话,系统将显示一条错误提示信息。

DialogFixture dialog = new DialogFixture(new LoginDialog());
dialog.show();
dialog.maximize();
TextComponentFixture usernameTextBox = dialog.textBox("username");
usernameTextBox.clear();
usernameTextBox.enter("leia.organa");
dialog.comboBox("role").select("REBEL");
OptionPaneFixture errorDialog = dialog.optionPane();
errorDialog.requireError();
errorDialog.requireMessage("Enter your password");

尽管代码很容易读懂,但却很冗长,需要很多键入。

下面列出的是在我们范例中所使用的TextComponentFixture的两个方法:

public void clear() {
target.setText("");
}

public void enterText(String text) {
robot.enterText(target, text);
}

我们可以仅仅通过返回this来简化我们的测试API,从而激活方法链接:

public TextComponentFixture clear() {
target.setText("");
return this;
}

public TextComponentFixture enterText(String text) {
robot.enterText(target, text);
return this;
}

在激活所有测试设施中的方法链接之后,我们的测试代码现在缩减到:

DialogFixture dialog = new DialogFixture(new LoginDialog());
dialog.show().maximize();
dialog.textBox("username").clear().enter("leia.organa");
dialog.comboBox("role").select("REBEL");
dialog.optionPane().requireError().requireMessage("Enter your password");

这个结果代码显然更加简洁易读。正如先前所提到的,方法链接本身并不意味着有了DSL。我们需要将解决领域特定问题的对象的所有相关行为相对应的方法链接起来。在我们的范例中,这个领域特定问题就是Swing GUI测试。

1.1.2 对象的简单构造/配置

这个案例和上文的很相似,不同是,我们不再只将一个对象的相关方法链接起来,取而代之的是,我们会通过连贯接口创建一个“builder”来构建和/或配置对象。

下面这个例子采用了setter来创建“dream car”:

DreamCar car = new DreamCar();
car.setColor(RED);
car.setFuelEfficient(true);
car.setBrand("Tesla");

DreamCar类的代码相当简单:

// package declaration and imports

public class DreamCar {

private Color color;
private String brand;
private boolean leatherSeats;
private boolean fuelEfficient;
private int passengerCount = 2;

// getters and setters for each field
}

尽管创建DreamCar非常简单,并且代码也十分可读,但我们仍能够使用car builder来创造更简明的代码:

// package declaration and imports

public class DreamCarBuilder {

public static DreamCarBuilder car() {
return new DreamCarBuilder();
}

private final DreamCar car;

private DreamCarBuilder() {
car = new DreamCar();
}

public DreamCar build() { return car; }

public DreamCarBuilder brand(String brand) {
car.setBrand(brand);
return this;
}

public DreamCarBuilder fuelEfficient() {
car.setFuelEfficient(true);
return this;
}

// similar methods to set field values
}

通过builder,我们还能这样重新编写DreamCar的创建过程:

DreamCar car = car().brand("Tesla")
.color(RED)
.fuelEfficient()
.build();

使用连贯接口,再一次减少了代码噪音,所带来的结果是更易读的代码。需要指出的很重要的一点是,在返回this的时候,链中任何方法都可以在任何时候被调用,并且可以被调用任何次数。在我们的例子中,color这个方法我们可想调用多少次就调用多少次,并且每次调用都会覆盖上一次调用所设置的值,这在应用程序的上下文中可能是合理的。

另一个重要的发现是,没有编译器检查来强制必需的属性值。一个可能的解决方案是,如果任何对象创建和/或配置规则没有得到满足的话(比如,一个必需属性被遗忘),在运行时抛出异常。通过从链中方法返回中间对象有可能达到规则校验的目的。

1.2 返回中间对象

从连贯接口的方法中返回中间对象和返回this的方式相比,有这样一些优点:

  • 我们可以使用编译器来强制业务规则(比如:必需属性)
  • 我们可以通过限制链中下一个元素的可用选项,通过一个特殊途径引导我们的连贯接口用户
  • 在用户可以(或必须)调用哪些方法、调用顺序、用户可以调用多少次等方面,给了API创建者更大的控制力

下面的例子表示的是通过带参数的构建函数来创建一个vacation对象的实例:

Vacation vacation = new Vacation("10/09/2007", "10/17/2007",
"Paris", "Hilton",
"United", "UA-6886");

这个方法的好处在于它可以迫使我们的用户申明所有必需的参数。不幸的是,这儿有太多的参数,而且没有表达出他们的目的。“Paris”和“Hilton”所指的分别是目的地的城市和酒店?还是我们同事的名字?:)

第二个方法是将setter方法对每个参数进行建档:

Vacation vacation = new Vacation();
vacation.setStart("10/09/2007");
vacation.setEnd("10/17/2007");
vacation.setCity("Paris");
vacation.setHotel("Hilton");
vacation.setAirline("United");
vacation.setFlight("UA-6886");

现在我们的代码更易读,但仍然很冗长。第三个方案则是创建一个连贯接口来构建vacation对象的实例,如同在前一章节提供的例子一样:

Vacation vacation = vacation().starting("10/09/2007")
.ending("10/17/2007")
.city("Paris")
.hotel("Hilton")
.airline("United")
.flight("UA-6886");

这个版本的简明和可读性又进了一步,但我们丢失了在第一个版本(使用构建函数的那个版本)中所拥有的关于遗忘属性的校验。换句话说,我们并没有使用编译器来校验可能存在的错误。这时,对这个方法我们所能做的最好的改进是,如果某个必需属性没有设置的话,在运行时抛出异常。

以下是第四个版本,连贯接口更完善的版本。这次,方法返回的是中间对象,而不是this:

Period vacation = from("10/09/2007").to("10/17/2007");
Booking booking = vacation.book(city("Paris").hotel("Hilton"));
booking.add(airline("united").flight("UA-6886");

这里,我们引进了PeriodBookingLocationBookableItemHotelFlight)、以及 Airline的概念。在这里的上下文中,airline作为Flight对象的一个工厂;LocationHotel的工厂,等等。我们所想要的booking的文法隐含了所有这些对象,几乎可以肯定的是,这些对象在系统中会有许多其他重要的行为。采用中间对象,使得我们可以对用户行为可否的限制进行编译器校验。例如,如果一个API的用户试图只通过提供一个开始日期而没有明确结束日期来预定假期的话,代码则不会被编译。正如我们之前提到,我们可以创建一种使用文法来增强语义的语言。

我们在上面的例子中还引入了静态工厂方法的应用。静态工厂方法在与静态import同时使用的时候,可以帮助我们创建更简洁的连贯接口。若没有静态import,上面的例子则需要这样的代码:

Period vacation = Period.from("10/09/2007").to("10/17/2007");
Booking booking = vacation.book(Location.city("Paris").hotel("Hilton"));
booking.add(Flight.airline("united").flight("UA-6886");

上面的例子不及采用了静态import的代码那么易读。在下面的章节中,我们将对静态工厂方法和import做更详细的讲解。

这是关于使用Java编写DSL的第二个例子。这次,我们将Java reflection的使用进行简化:

Person person = constructor().withParameterTypes(String.class)
.in(Person.class)
.newInstance("Yoda");

method("setName").withParameterTypes(String.class)
.in(person)
.invoke("Luke");

field("name").ofType(String.class)
.in(person)
.set("Anakin");

在使用方法链接的时候,我们必须倍加注意。方法链接很容易会被烂用,它会导致许多调用被一起链接在单一行中的“火车残骸”现象。这会引发很多问题,包括可读性的急剧下滑以及异常发生时栈轨迹(stack trace)的含义模糊。

2. 静态工厂方法和Imports

静态工厂方法和imports可以使得API更加简洁易读。我们发现,静态工厂方法是在Java中模拟命名参数的一个非常方便的方法,是许多程序员希望开发语言中所能够包含的特性。比如,对于这样一段代码,它的目的在于通过模拟一个用户在一个JTable中选择一行来测试GUI:

dialog.table("results").selectCell(6, 8); // row 6, column 8 

没有注释“// row 6, column 8”,这段代码想要实现的目的很容易被误解(或者说根本没有办法理解)。我们则需要花一些额外的时间来检查文档或者阅读更多行代码才能理解“6”和“8”分别代表什么。我们也可以将行和列的下标作为变量来声明,而非像上面这段代码那样使用常量:

int row = 6;
int column = 8;
dialog.table("results").selectCell(row, column);

我们已经改进了这段代码的可读性,但却付出了增加需要维护的代码的代价。为了将代码尽量简化,理想的解决方案是像这样编写代码:

dialog.table("results").selectCell(row: 6, column: 8); 

不幸的是,我们不能这样做,因为Java不支持命名参数。好的一面的是,我们可以通过使用静态工厂方法和静态imports来模拟他们,从而可以得到这样的代码:

dialog.table("results").selectCell(row(6).column(8)); 

我们可以从改变方法的签名(signature)开始,通过包含所有参数的对象来替代所有这些参数。在我们的例子中,我们可以将方法selectCell(int, int)修改为:

selectCell(TableCell); 

TableCell will contain the values for the row and column indices:

TableCell将包含行和列的下标值:

public final class TableCell {

public final int row;
public final int column;

public TableCell(int row, int column) {
this.row = row;
this.column = column;
}
}

这时,我们只是将问题转移到了别处:TableCell的构造函数仍然需要两个int值。下一步则是将引入一个TableCell的工厂,这个工厂将对初始版本中selectCell的每个参数设置一个对应的方法。另外,为了迫使用户使用工厂,我们需要将TableCell的构建函数修改为private

public final class TableCell {

public static class TableCellBuilder {
private final int row;

public TableCellBuilder(int row) {
this.row = row;
}

public TableCell column(int column) {
return new TableCell(row, column);
}
}

public final int row;
public final int column;

private TableCell(int row, int column) {
this.row = row;
this.column = column;
}
}

通过TableCellBuilder工厂,我们可以创建对每个参数都有一个调用方法的TableCell。工厂中的每个方法都表达了其参数的目的:

selectCell(new TableCellBuilder(6).column(8)); 

最后一步是引进静态工厂方法来替代TableCellBuilder构造函数的使用,该构造函数没有表达出6代表的是什么。如我们在之前所实现的那样,我们需要将构造函数设置为private来迫使用户使用工厂方法:

public final class TableCell {

public static class TableCellBuilder {
public static TableCellBuilder row(int row) {
return new TableCellBuilder(row);
}

private final int row;

private TableCellBuilder(int row) {
this.row = row;
}

private TableCell column(int column) {
return new TableCell(row, column);
}
}

public final int row;
public final int column;

private TableCell(int row, int column) {
this.row = row;
this.column = column;
}
}

现在我们只需要selectCell的调用代码中增加内容,包含对TableCellBuilderrow方法的静态import。为了刷新一下我们的记忆,这是如何实现调用selectCell的代码:

dialog.table("results").selectCell(row(6).column(8)); 

我们的例子说明,一点点额外的工作可以帮助我们克服主机编程语言中的一些限制。正如之前提到的,这只是我们通过使用静态工厂方法和imports来改善代码可读性的很多方法中的一个。下列代码段是以另一种不同的方法利用静态工厂方法和imports来解决相同的table坐标问题:

/**
* @author Mark Alexandre
*/
public final class TableCellIndex {

public static final class RowIndex {
final int row;
RowIndex(int row) {
this.row = row;
}
}

public static final class ColumnIndex {
final int column;
ColumnIndex(int column) {
this.column = column;
}
}

public final int row;
public final int column;
private TableCellIndex(RowIndex rowIndex, ColumnIndex columnIndex) {
this.row = rowIndex.row;
this.column = columnIndex.column;
}

public static TableCellIndex cellAt(RowIndex row, ColumnIndex column) {
return new TableCellIndex(row, column);
}

public static TableCellIndex cellAt(ColumnIndex column, RowIndex row) {
return new TableCellIndex(row, column);
}

public static RowIndex row(int index) {
return new RowIndex(index);
}

public static ColumnIndex column(int index) {
return new ColumnIndex(index);
}
}

这个方案的第二个版本比第一个版本更具灵活性,因为这个版本允许我们通过两种途径来声明行和列的坐标:

dialog.table("results").select(cellAt(row(6), column(8));
dialog.table("results").select(cellAt(column(3), row(5));

组织代码

相比返回中间对象的的方式来说,返回this的方式更加容易组织连贯接口的代码。前面的案例中,我们的最后结果是使用更少的类来封装连贯接口的逻辑,并且使得我们可以在组织非DSL代码的时候使用同样的规则或约定。

采用中间对象作为返回类型来组织连贯接口的代码更具技巧性,因为我们将连贯接口的逻辑遍布在一些小的类上。由于这些类结合在一起作为整体而形成我们的连贯接口,这使得将他们作为整体对待更为合理,我们可能不想将他们和DSL外的其他一些类混淆一起,那么我们有两个选择:

  • 将中间对象作为内嵌类创建
  • 将中间对象至于他们自己的顶级类中,将所有这些中间对象类放入同一个包中

分解我们的系统所采用的方式取决于我们想要实现的文法的几个因素:DSL的目的,中间对象(如果有的话)的数量和大小(以代码的行数来计),以及DSL如何来与其它的代码库及其它的DSL相协调。

对代码建档

在组织代码一章节中提到,对方法返回this的连贯接口建档比对返回中间对象的连贯接口建档来的简单的多,尤其是在使用Javadoc来建档的情况下。

Javadoc每次显示一个类的文档,这对于使用中间对象的DSL来说可能不是最好的方式:因为这样的DSL包含一组类,而不是单个的类。由于我们不能改变Javadoc显示我们的API文档的方式,我们发现在package.html文件中,加入一个使用连贯接口(包含所有相关类)、且对链中每个方法提供链接的例子,可以将Javadoc的限制的影响降到最低。

我们需要注意不要创建重复文档,因为那样会增加API创建者的维护代价。最好的方法是尽可能依赖于像可执行文档那样的测试。

结论

Java适用于创建开发人员易读易写的、并且对于商业用户用样易读的内部领域特定语言。用Java创建的DSL可能比那些由动态语言创建的DSL来的冗长。但好的一面是,通过使用Java,我们可以利用编译器来增强DSL的语义。另外,我们依赖于成熟且强大的Java集成开发环境,从而使DSL的创建、使用和维护更加简单。

使用Java创建DSL需要API设计者做更多的工作,有更多的代码和文档需要创建和维护。但是,付出总有回报。使用我们API的用户在他们的代码库中会看到更多的优化。他们的代码将会更加简洁,更易于维护,这些将使得他们的生活更加轻松。

使用Java创建DSL有很多种不同的方式,这取决于我们试图达到的目的是什么。尽管没有什么通用的方法,我们还是发现结合方法链接和静态工厂方法与imports的方式可以得到干净、简洁、易读易写的API。

总而言之,在使用Java来创建DSL的时候有利有弊。这都由我们——开发人员根据项目需求去决定它是否是正确的选择。

另外一点题外话,Java 7可能会包含帮助我们创建不那么冗长的DSL的新语言特性(比如闭包)。如果想得到更多关于建议中所提特性的全面的列表,请访问Alex Miller的blog

关于作者

Alex Ruiz是Oracle开发工具组织中的一名软件工程师。Alex喜欢阅读任何关于Java、测试、OOP 和AOP的信息,他最大的爱好就是编程。在加入Oracle之前,Alex曾是ThoughtWorks的咨询顾问。Alex的blog为 http://www.jroller.com/page/alexRuiz

Jeff Bay是纽约一家对冲基金的高级软件工程师。他曾多次建立高质量、迅速的XP团队工作于例如Onstar的计划注册系统、租赁软件、web服务器、建筑项目管理等各种系统。他对于消除重复和防止bug方面怀有极大的热情,以提高开发者的工作效率和减少在各种任务上所花费的时间。

相关资料

posted @ 2008-04-01 15:06 linugb118 阅读(5423) | 评论 (0)编辑 收藏

code管理模式

最近意外发现JunitFactory这个关键字,于是便去研究了一下,研究发现后得到更有意义的发现。

首先我们大概讲一下什么是JunitFactory. JunitFactory 其实就是Junit's Factory.如果曾经是java的开发人员
应该大家都知道Junit 就是java的单元测试。他的功能是什么呢?其实主要是检查一个方法输入相关参数后得到的
结果是否是自己期望的。而且在以前的应用中,往往是开放人员根据参数预先心中算出结果然后手工放入到Junit中,
接着运行这个junit 看看是否成功或失败。而JunitFactory则能预先输入相关参数包括边界参数,然后也能预先得
到与刚才相关参数相关的结果。然后自动生成对应的Junit。这个听上去好像有点牛了。因为你要知道方法是无法去
完全去分析的。那他是怎么去做的呢?比如说有这么一个方法:

public int plus(int i, int j)
{
  return i+j;
}

那么预先得到的junit是
int result = new MathDemo().plus(100, 1000);
assertEquals("result", 1100, result);

int result = new MathDemo().plus(0, 0);
assertEquals("result", 0, result);
两种情况。

如果你把 plus中的 i+j 改为 i+10+j,那么junit就会自动变成
int result = new MathDemo().plus(100, 1000);
assertEquals("result", 1110, result);

int result = new MathDemo().plus(0, 0);
assertEquals("result", 10, result);

同样如果改为string 那么他的junit也会相应的改掉。当然也许你要问如果我的方法很复杂,那么他怎么能自动分析产生
预期的结果?我的答案是肯定不能完全能产出所有结果。为什么?因为如果你的方法不是wellformat 或者说不符合寻常的思

路(我们称之为低质量代码,本来想说垃圾代码,后来想想不太文明)那么还需要自动分析吗?那就没这个自动分析的价值。

怎么自动知道这些代码是wellformat 还是unwellformat 的呢?其实这需要两种工作的集合,经验丰富的人工辨别和有规律

的机器辨别。值得注意的是,该JunitFactory的Eclipse pluign 就需要用户填写JunitFactory的website,并且保证运行

JunitFactory的时候,网络是通的,他能连接到她的服务器。他同时upload 当前需要junit的方法,并有相应的反馈。其实

这种两者合一的方法也解决了审核代码的问题,所以junitFactory 官方的解释就是With a full suite of

characterization tests generated by JUnit Factory you can bring your legacy code under control,
就是能合法地控制代码。

    上面是JunitFactory带给我们具体的东西,我现在想讨论的是软件公司的管理模式,特别是code的管理模式。我没有进

过500强的软件公司,所以没有能有幸接触他们的管理模式。但我认为如果能把JunitFactory的模式引入软件公司的话,这是

一件很好的事情。 这种code模式大致是这样的

流程:coder可以先根据需求去代码服务器询问某个通用的方法是否已经在代码服务器中存在,如果存在并已经被用过,那么

可以自己从代码服务器中获取该通用方法,如果没有那么就需要自己code该方法,coder 通过本地代码检查器开发完成一个

方法后可以上传给代码服务器,然后由代码管理员来审核并反馈。 审核通过并测试通过就可以进入代码服务器,并作相应的

功能描述版本控制什么的。

这个管理的模式的只是code开发管理模式,不包括需求分析模块,软件的需求分析等环节同样需要做。
这个模式的好处是:
1.能在coding的时候就能参与代码的管理,而不是coded之后再去参与代码的管理。这样可以节省很多走流程所造成的时间浪

费,coder可以在这个方法还没有审核后 可以写其他的方法。那么有的人就会说 我后面的方法是基于前面的,我岂不是要等

待审核的结果。那我就要问,难道你的这个模块都和这个方法耦合这么紧,如果真的是这样 那么设计有问题。

2.能充分实现reused 的软件思想。虽然reused 对于任何一个公司或开发人员讲,他们都会知道,但是很多真正的情况却不

是很理想,导致不可能充分利用reused的原因有很多,比如员工的沟通不够,已有的项目积累太多 以及写的方法是不是能

reused。这应该归咎于一个制度上的问题,如果用这种模式,coder 的代码必须经过审核,也就在源头上解决了这些问题。

3.解放新的职位,很多软件公司没有给coder 很好的职业规划,其实不是很多公司不想,只是没有合适的职位给他做。那么

新的代审核人员其实是需要开发经验很丰富的人员来承担,同时他只要read code 而不需要再去write code。那么这一新的

职位可以部分解决这个问题。

posted @ 2007-11-16 12:28 linugb118 阅读(1451) | 评论 (2)编辑 收藏

java内存泄漏

Don't we all remember the days when we programmed C or C++? You had to use new and delete to explicitly create and remove objects. Sometimes you even had to malloc() an amount of memory. With all these constructs you had to take special care that you cleaned up afterwards, else you were leaking memory.

Now however, in the days of Java, most people aren't that concerned with memory leaks anymore. The common line of thought is that the Java Garbage Collector will take care of cleaning up behind you. This is of course totally true in all normal cases. But sometimes, the Garbage Collector can't clean up, because you still have a reference, even though you didn't know that.

I stumbled across this small program while reading JavaPedia, which clearly shows that Java is also capable of inadvertent memory leaks.

public class TestGC {
  private String large = new String(new char[100000]);

 

  public String getSubString() {
    return this.large.substring(0,2);
  }

  public static void main(String[] args) {
    ArrayList<String> subStrings = new ArrayList<String>();
    for (int i = 0; i <1000000; i++) {
      TestGC testGC = new TestGC();
      subStrings.add(testGC.getSubString());
    }
  }
}

 

Now, if you run this, you'll see that it crashes with something like the following stacktrace:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.String.(String.java:174)
at TestGC.(TestGC.java:4)
at TestGC.main(TestGC.java:13)

Why does this happen? We should only be storing 1,000,000 Strings of length 2 right? That would amount to about 40Mb, which should fit in the PermGen space easily. So what happened here? Let's have a look at the substring method in the String class.

public class String {
  // Package private constructor which shares value array for speed.
  String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
  }

 

  public String substring(int beginIndex, int endIndex) {
    if (beginIndex <0) {
      throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex> count) {
      throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex> endIndex) {
      throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
      new String(offset + beginIndex, endIndex - beginIndex, value);
  }

 

We see that the substring call creates a new String using the given package protected constructor. And the one liner comment immediately shows what the problem is. The character array is shared with the large string. So instead of storing very small substrings, we were storing the large string every time, but with a different offset and length.

This problem extends to other operations, like String.split() and . The problem can be easily avoided by adapting the program as follows:

public class TestGC {
  private String large = new String(new char[100000]);

 

  public String getSubString() {
    return new String(this.large.substring(0,2)); // <-- fixes leak!
  }

  public static void main(String[] args) {
    ArrayList<String> subStrings = new ArrayList<String>();
    for (int i = 0; i <1000000; i++) {
      TestGC testGC = new TestGC();
      subStrings.add(testGC.getSubString());
    }
  }
}

 

I have many times heard, and also shared this opinion that the String copy constructor is useless and causes problems with not interning Strings. But in this case, it seems to have a right of existence, as it effectively trims the character array, and keeps us from keeping a reference to the very large String.

8 Responses to “Leaking Memory in Java”

  1. GadgetGadget.info - Gadgets on the web » Leaking Memory in Java Says:

    […] Devlib wrote an interesting post today!.Here’s a quick excerptNow however, in the days of Java, most people aren’t that concerned with memory leaks anymore. The common line of thought is that the Java Garbage Collector will take care of cleaning up behind you. This is of course totally true in all … […]

  2. Sherif Mansour Says:

    Hi There,
    Thanks for the insightful article! I found this quite useful - especially in understanding why Java OutOfMemory’s work…
    Sherif

  3. Jos Hirth Says:

    Well, that’s not a memory leak. See:
    http://en.wikipedia.org/wiki/Memory_leak

    The behavior is intentional - it trades memory for performance. As most things in the standard library (eg collections) it’s optimized for general usage and, well, generally it’s alright. But you certainly shouldn’t tokenize a really big string this way.

    The classic type of memory leaks doesn’t exist in managed languages. The only thing we can produce are so called reference leaks. That is… referencing stuff (and thus preventing em from being GCed) for longer as necessary (or for all eternity).

    Fortunately it’s easy to avoid - for the most part.

    The important things to know:

    Locally defined objects can be GCed as soon as there are no more no more references to it. Typically it’s the end of the block they are defined in (if you don’t store the reference anywhere). If you do store references, be sure to remove em if you don’t need em anymore.

    If you overwrite a reference with a new object, the object is first created and /then/ the reference is overwritten, which means the object can be only GCed /after/ the new object has been created.

    Usually this doesn’t matter. However, if you want to overwrite an object which is so big that it only fits once into the memory, you’ll need to null the reference before creating/assigning the new instance.

    Eg:
    //FatObject fits only once into memory
    FatObject fatty;
    fatty=new FatObject();
    fatty=new FatObject();

    Will bomb with OOME. Whereas…

    FatObject fatty;
    fatty=new FatObject();
    fatty=null;
    fatty=new FatObject();

    Will be fine, because the second creation of the FatObject will trigger a full GC and the GC will be able to clear enough memory (since the old reference has been nulled).

    Well, that rarely matters, but it’s good to know.

  4. Randomly Intermittent Thoughts » A Good Reasoning to Nullify an Object! Says:

    […] Jos Hirth wrote this in response to this post by Jeroen van Erp. […]

  5. links for 2007-10-06 - smalls blogger Says:

    […] Xebia Blog Leaking Memory in Java (tags: java memoryleak programming jvm) […]

  6. James McInosh Says:

    I don’t know which version of the JVM you are sunning but when it constructs a new string using this constructor:

    String(char value[], int offset, int count)

    It sets the value using this:

    this.value = Arrays.copyOfRange(value, offset, offset+count);

  7. creyle Says:

    To be more obvious, with the underlying big char array being referenced, all the TestGC objects created in the big for-loop could not be GCed. that’s the problem.

    Thanks

  8. Jeroen van Erp Says:

    James,

    True for String(char[] value, int offset, int count), but not for String(int offset, int count, char[] value). The constructor you mention is a public constructor. The constructor that is called from the substring method is a package private constructor.

posted @ 2007-11-15 13:02 linugb118 阅读(394) | 评论 (0)编辑 收藏

什么叫面向变量编程?

 

什么叫面向变量编程?

1.其实程序的传递就是变量的传递,变量是任何编程中不可缺少的因素。随着很多模式和编程方式的出现,应用设计可能层次比较清楚,更宜人理解。然而变量将变得很多,同样作用的变量在不同的层中可能不一样,而且随着不同开发人员在同一时间开发不同的层或者不同时间开发同一模块都可能根据自己的想法声明或者定义某个变量,而他们所定义的这个变量其实都是一样的, 理论上应该是一个变量,或者说至少命名应该一样。如果能归为同样命名的变量,那就能很大程度上减少维护和开发的工作。所以能否将同样功能的变量进行统一命名就很有必要。

2.了解变量其实就是了解业务,虽然很早就强调文档,强调业务知识的学习,然而我觉得看程序其实是了解业务的最好方法,但条件是程序比较清楚, 这里包括应用的构架清晰,具有相应的备注以及变量的含义清晰。

3.如果能充分认识面向变量的概念并根据面向变量思想来开发帮助工具,那么可以简化很多重复编程的工作,具体比如 有这么一个例子: 对于很多输入项的web页面,如果我们新增一个输入变量,那么我们可否通过面向变量的tool将相关的逻辑层 dao 数据库都添加这个同样命名的变量呢?

现在我们就很清楚了 我们现在要做的就是面向变量的tool,前面只是大体上说了什么是面向变量编程 已经面向变量编程的好处。那么如果我们需要开发一个面向变量的tool 那我们需要仔细分析变量的特点以及能用的共性

变量一个很重要的属性是她的scope 根据scope我们可以将用到的变量进行分类,帮助我们理解。下面我就web应用将变量scope进行分类(其实同样适用于其他应用)

我们把变量分为两大类, 一类是 面向变量所关心的,他具有跨越型传递性,他这里的跨越型传递性表示该变量的所附属的值会传递到下个地方,这个地方可以是下个页面, 可以是服务器 可以是数据库中 我们称该类变量为前锋型变量。另一类是 面向对象所不关心的, 他不具有跨越型传递性, 他只是作为临时中介,比如某个页面的中的临时变量,java 方法中的临时变量. 他们不是我们所关心的,我们称这里变量为候补型变量。对于面向对象的编程我们的原则是:前锋型变量命名要很清晰,要能够联动,能串成串,不要冗余;候补型变量命名要简洁,最好不要游离在方法外,在方法中产生,在方法中消亡。

 

对于候补型变量 我个人认为可能不能也没有必要开发什么相关的tool 来简化工作,但他提醒开发人员要不断的整理自己的方法,重构自己的方法,重用别人的通用方法。

而对于前锋型变量 我认为因为他承载着有效数据,他的理解往往反映了业务,并且它是全局性的,具有跨越型的传递,所以他的重要性会更大一点,那么针对他能有很多tool 可以做。

Tool 1:前锋变量联动机

功能描述:

在页面上定义一个前锋型变量,那么可以自动在数据库中添加该字段 DAO Business层添加该变量,同时可以实现修改某个页面变量 同时数据库以及各个层都能改变该变量。

或者 在数据库层添加某个字段 对应能产生页面,DAO Business的前锋型变量.

Tool2: 前锋变量检查工具

功能描述:

可以对某个页面,某个配置文件,数据库 查找所有的前锋型变量。将这些变量陈列出来给开发人员或者管理人员来分析。

是否还有其他idea 请帮忙想想。。。

posted @ 2007-11-09 15:14 linugb118 阅读(922) | 评论 (0)编辑 收藏

WhirlyCache

 

                                                                 WhirlyCache

借着最近项目用到cache,就顺便研究了一下cache 的设计,研究对象WhirlyCache 一个sun公司轻量的cache工具

作为一个cache的整体架构 他分这么几个模块

1.配置信息 包括config 文件和resource文件

Config文件 用来记录cacheManger 中需要的信息以及对每种cache 的属性记录,他的结构就是以cache 为基本单位,

<whirlyCache>

<cache>..</cache>

<cache>…</cache>

</whirlyCache>

而对于cache 我认为cache包括可以有一个或多个policycache策略)的定义,这些policy现在主要有FIFO,LRU等等,这些多个policys组合成当前该cachecache策略。除了主要策略 cache 还可以定义其他属性。

cacheConfig对象与config文件中的cache 声明可以认为是一一对应的

2.CacheManager cache管理器是cache功能中不可缺少的,和其他管理一样,通常cache管理器是singleton模式,他的功能如下:

A. load config文件, config文件load到内存,将每个cache信息set到对应的cacheconfig

B. create cache 根据前面load config,根据不同的cacheconfig创建不同的cache,其中包括policy

C.destroy 可以对manager中某个指定的cache进行destroy或者destroy 所有的cache

D.getCache 通过指定的name来获取缓存对象

E.shutdown Whirlycache 他实现的其实是 destroy all caches 的功能, 我认为shutdown 应该是destroy all data in cachemanger的功能,包括unload config file

CacheManager的数据结构:

主要有两个Map 用来存放数据

一个是configMap load config之后,存放多个cacheConfig

另一个是caches 用于存放不同的cache。、

3.Cache 接口

Cache 应该具有如下功能

A. store cache中存放需要存放的对象

B.retrieve cache中获取需要的对象

C.remove 清除cache中的某个对象

D.size 获取cachesize

对于数据结构来说, 最终cache应该是map这种数据结构

WhirlyCache cache中的key 有两种

一种就是map 中的object

另外一种就是他独有的对象 Cacheable object

4.Cacheable 只是whirlyCache中的一个接口,他下面有三个方法的声明

OnRemoveonRetreiveonStore。这个接口表示,当你将Cacheable object

作为cachekey的话,那么在执行cache中的remove,retrieve,store 三个操作

的时候,他同时调用cacheable中对于的onXXX方法。我们可以认为这个是Listener

当做Store的操作时候,如果put key cacheable 也就对于调用Cacheable onStore方法,那么实现onStore 就能在store 触发器事件。

5.CachePolicy

所谓CachePolicy 就是cache的策略,那么他与ManagerCache 有关,需要为该Policy指定ManagerCache,  所以就有SetManagedCache(), 而某个Policy的属性是在配置文件中指定的,所以就有了SetConfig(). 那么有混淆的就是Cache ManagedCache, 其实他们的区别在于一个是行为声明, 一个是具体的数据结构,后面会具体分析,而这里需要说明他们和Policy的关系不同的是,cache 中应该是可以有多个policy,他是面向用户的,用户通常说的“我为某个Cache 指定了这么几个策略组合” 那么这里的cache 就是指的Cache。而对于ManagedCache 他其实就是有个map实体,那么具体policy 的实现就是对该map的操作,比如FIFO 那么就要对这个map实行 FIFO的操作。

下面还有一个方法是Policy interface 必须声明的,那就是 performMaintenance(), 他就是具体执行该策略。Whirly 提供了下面几种CachePolicy:

a. FIFO   先进先出策略

b. LFU Least Frequently Used algorithm

c.LRU least recently used algorithm

Policy的机制:

首先需要一个Item对象, 他记录被引用的object最近被使用,最近用的,以及被使用的记录数等相关信息,在store的时候New 一个item 对象 一并putmanagedCache,在执行cacheremove store retrieve等操作的时候同时需要更新item中的信息。当执行到某个策略的时候,首先需要根据不同的policy 实现不同的Comparator( AddedComparatorCountComparator, UsedComparator) 而这些ComparatorObject比较原则就是将前面说的Item的记录信息进行比较,然后将Object排序。managedCache 根据相应的Comparator进行Sort。然后将managedCache中大于Cache设置大小的数据进行remove,这样整个过程就是performMaintenance

6. CacheDecorator

前面的cache只是接口,其实CacheDecrator才是cache的具体实现,前面的cache只是一个interface,他定义了面向用户接口的相关行为,具体他表示对于cache而言,提供给外部人员使用的行为接口。比如用户只要关心从cacheremove,retrieve object的这样的逻辑行为。而对于内部数据结构方面的具体操作,用户并不关心。为了清晰我们需要另外独立定义接口。这样我们定义了ManagedCache接口,他继承map  interface。同时可以在该接口中定义其他数据操作的方法。

AbstractMapBackedCache是实现ManagedCache的抽象类,到这里就需要引入具体的数据结构,所以AbstractMapBackedCache中有个 map field。而具体ManagedCache中的方法的实现,也是对该map 变量的操作。

下面我们讲讲题外话:

对于map type java世界中有很多种,有jdk里面的HashMap TreeMap 这些我们都比较熟悉,我们可以认为这些Map classic map而今天我们在WhirlyCache中看见了其他几个map 有的我也看到过 有的也是第一次。

ConcurrentHashMap

来源:

http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html

FastHashMapImpl

来源:

org.apache.commons.collections.FastHashMap

他是apache commons项目中collections的一个Map

SynchronizedHashMapImpl:

Collections.synchronizedMap( new HashMap())

他表示该HashMap synchronized,该Map保证了该对象任何时候的一致性

通过继承AbstractMapBackedCache,为他的map field设置不同的map,就实现不同的cache策略,这个策略有别与前面的policy,前面的是算法的策略,这里应该是数据结构的策略。

7.其他

a.实现对Hibernate Cache的控制 ,实现net.sf.hibernate.cache.Cache接口

b.添加关闭功能,当ServletContext reload的时候具体实现ServletContextListener

posted @ 2007-10-31 10:24 linugb118 阅读(1396) | 评论 (1)编辑 收藏

MaintainJ -- an Eclipse plug-in

MaintainJ, an Eclipse plug-in, generates sequence and class diagrams for a use case, helping the users to quickly understand a complex Java or J2EE application.

See: http://www.maintainj.com

posted @ 2007-01-24 17:37 linugb118 阅读(266) | 评论 (0)编辑 收藏

Fortress ----future language

Fortress is a new programming language designed for high-performance computing (HPC) with high programmability. In order to explore breakaway approaches to improving programmability, the Fortress design has not been tied to legacy language syntax or semantics; all aspects of HPC language design have been rethought from the ground up. As a result, we are able to support features in Fortress such as transactions, specification of locality, and implicit parallel computation, as integral features built into the core of the language. Features such as the Fortress component system and test framework facilitate program assembly and testing, and enable powerful compiler optimizations across library boundaries. Even the syntax and type system of Fortress are custom-tailored to modern HPC programming, supporting mathematical notation and static checking of properties such as physical units and dimensions, static type checking of multidimensional arrays and matrices, and definitions of domain-specific language syntax in libraries. Moreover, Fortress has been designed with the intent that it be a "growable" language, gracefully supporting the addition of future language features. In fact, much of the Fortress language itself (even the definition of arrays and other basic types) is encoded in libraries atop a relatively small core language

I think the Muti-core computer time is coming. So the parallel processing language will enter into the future. The Fortress it is!

See: http://fortress.sunsource.net/

posted @ 2007-01-24 15:58 linugb118 阅读(230) | 评论 (0)编辑 收藏

Object DataBase --DB4O之旅

Object DataBase --DB4O之旅

Object DataBase 出现有段时间了,最近对此有点兴趣,想探索一下,于是开始了我的db4o discovery 之旅。

db4o的jar 以及 api 在他document中都有写到, 其实他的 example 在document中也能找到,但是因为本人
也是在学习的过程,所以将用例也简单描述一下:

1.First Step:

define an object Class, this is Pilot object;

public class Pilot {  
  private String name;
  private int points;
 
  public Pilot(String name,int points) {
    this.name=name;
    this.points=points;
  }
   
  public int getPoints() {
    return points;
  }
 
  public void addPoints(int points) {
    this.points+=points;
  }
 
  public String getName() {
    return name;
  }
 
  public String toString() {
    return name+"/"+points;
  }
}

the next target is how to store, retrieve , update, delete the instance of the Pilot Class.

2.open database:

[accessDb4o]
ObjectContainer db=Db4o.openFile(Util.YAPFILENAME);
try {
// do something with db4o
}
finally {
db.close();
}

db4o 中通过创建一个file 来建立database,这是一个二进制文件。这里就是通过 Db4o.openFile(..)来得到一个 ObjectContainer instance.
ObjectContainer 就表示database。 实现对数据库的操作后, 可以通过 close,来release all resource。

3.store object

[storeFirstPilot]
Pilot pilot1=new Pilot("Michael Schumacher",100);
db.set(pilot1);
System.out.println("Stored "+pilot1);

只需要简单的调用set method 就可以了。

4.retrieve object

db4o 提供了三种 quering systems.
Query by Example (QBE)
Native Queries (NQ)
SODA Query API (SODA)

这里先介绍QBE:
首先需要构造你需要query 的对象的 prototype

To retrieve all pilots from our database, we provide an 'empty' prototype:
[retrieveAllPilots]
Pilot proto=new Pilot(null,0);
ObjectSet result=db.get(proto);
listResult(result);

public static void listResult(ObjectSet result) {
System.out.println(result.size());
while(result.hasNext()) {
System.out.println(result.next());
}

在 jdk 1.5中 可以这样写
List <Pilot> pilots = db.query(Pilot.class);

如果想查询 pilot name is Bobbi, 可以构造这样的prototype:
Pilot proto=new Pilot("Bobbi",0);

同样如果想查询 Point 是100的 可以这样 construct prototype:
Pilot proto=new Pilot(null,100);

5.update object

updating objects is just as easy as storing them,In fact, you can use retrieve a object and modify it, set it to database again.

[updatePilot]
ObjectSet result=db.get(new Pilot("Michael Schumacher",0));
Pilot found=(Pilot)result.next();
found.addPoints(11);
db.set(found);
System.out.println("Added 11 points for "+found);
retrieveAllPilots(db);

6. Delete objects
Objects are removed from the database using the delete() method.
[deleteFirstPilotByName]
ObjectSet result=db.get(new Pilot("Michael Schumacher",0));
Pilot found=(Pilot)result.next();
PDF by iText, generated by Doctor, courtesy of db4objects Inc.
db.delete(found);
System.out.println("Deleted "+found);

-------------------------------------------
Query:

对应数据库来说,Query是很复杂 很重要的。
刚才我们只是简单的 做了一个QBE的 introduce。现在我们来进一步学习db4o的Query

db4o 提供了三种 quering systems.
Query by Example (QBE) 表示简单的查询,只做简单的CRUD(Create Read Update Delete)
Native Queries (NQ) 接口层的调用,是db4o query的主要用法。
SODA Query API (SODA) 内部的接口层调用, 对db4o 更进一步的深入了解,可以做出更复杂的query,可以动态生成query

其实对应一般的数据库操作, 第二种 query (NQ) 才是最常用的。
NQ:
http://www.cs.utexas.edu/users/wcook/papers/NativeQueries/NativeQueries8-23-05.pdf
http://www.cs.utexas.edu/users/wcook/papers/SafeQuery/SafeQueryFinal.pdf
(所谓navtive queries 从字面上的意思,我们的查询可以通过native language 来query 数据,而不需要象传统的做法,需要develpment language
和 query language 两种语言来实现query。)
NQ 看上去也 传统的O/R mapping 看来,他更能进行对象直接的操作,但NQ 也有一个弊端,当我们需要检查一段很复杂的logic,如果我们是用的传统
的JDBC 我们可以找出query string 直接通过db UI Tool 去check logic 的正确性,但是如果是NQ 那就不行了, 我们需要运行这段code, 那当然需
要初始化涉及到的persistence Object, 这可能也是NQ 的很大的一个drawback。

SODA Query API:

The SODA query API is low level quering api, allowing direct access to nodes of query graphs. It can generate drynamic queries.

[retrieveAllPilots]
Query query=db.query();
query.constrain(Pilot.class);
ObjectSet result=query.execute();
listResult(result);

通过query method 来创建Query instance,然后通过 constrain instances 来 execute出 相应的result。
a query graph made up of query nodes and constraints. A query node is a placeholder for a
candidate object, a constraint decides whether to add or exclude candidates from the result.

////////////////////////////////
//Three query method example
///////////////////////////////

//QBE
just do CRUD simple operation, The code show above.

//NQ
public static void retrieveComplexNQ(ObjectContainer db) {
ObjectSet result=db.query(new Predicate() {
public boolean match(Pilot pilot) {
return pilot.getPoints()>99
&& pilot.getPoints()<199
|| pilot.getName().equals("Rubens Barrichello");
}
});
listResult(result);
}

//SODA
public static void retrieveComplexSODA(ObjectContainer db) {
Query query=db.query();
query.constrain(Pilot.class);
Query pointQuery=query.descend("points");
query.descend("name").constrain("Rubens Barrichello")
.or(pointQuery.constrain(new Integer(99)).greater()
.and(pointQuery.constrain(new
Integer(199)).smaller()));
ObjectSet result=query.execute();
listResult(result);
}
4.Structed Object

如果一个对象中不仅仅有常见的java类型还包含其他的对象,那么在modify 被包含的对象的时候,就会出现一个问题。如果我更新了这些被包含的对象,那么在update delete 的时候,他们会不会被操作到?db4o中 提供了一个 depth的概念。所有object 的默认的update depth为1,这就意味着该object 中的primitive 和 string members 能被update,其他类型的对象将不被upda
te。 同样对应delete 如果想实现 对象中的递归删除, 那同样需要利用db4o中的 delete depth


这是update depth:
[updatePilotSeparateSessionsImprovedPart1]
Db4o.configure().objectClass("com.db4o.f1.chapter2.Car")
.cascadeOnUpdate(true);

这是delete depth:
[deleteDeepPart1]
Db4o.configure().objectClass("com.db4o.f1.chapter2.Car")
.cascadeOnDelete(true);

Again: Note that all configuration must take place before the ObjectContainer is opened.

其实这里还没有结束,对于delete 会出现这么一个问题,当我通过 delet depth 将一个instance 删除了,他里面包含的某个其他类型的object instance 也被删除了,但是这个
对象还在被其他对象引用,那这个问题怎么办? 现在db4o 还没有解决方法。我们现在只能
小心操作delete。



5. Transactions
对应数据库来说,transaction 是必须的,下面我们来看看object dataBase--db4o是怎么
来处理transcation的。

当open a container的时候,一个transaction 就隐型地开始了,而当close 这个container,
那么当前的transaction就隐型地提交了。

那么如果显型地commit 和 rollback呢? 下面有这么两个例子:

public static void storeCarCommit(ObjectContainer db) {
Pilot pilot=new Pilot("Rubens Barrichello",99);
Car car=new Car("BMW");
car.setPilot(pilot);
db.set(car);
db.commit();
}


public static void storeCarRollback(ObjectContainer db) {
Pilot pilot=new Pilot("Michael Schumacher",100);
Car car=new Car("Ferrari");
car.setPilot(pilot);
db.set(car);
db.rollback();
}

与JDBC相比,好像有一个区别就是,db4o不需要来设置commit的模式, setAutoCommit()

她就是如果db.close(),那么就autoCommit了。

对于Object Database,因为他们都是用object 来存取,那么当object 被set 相应的值后,但没有save到database,也就说
current transaction 被rollback,那么为了保持数据的一致性,需要refresh live object.那么这个怎么来实现呢?

public static void carSnapshotRollbackRefresh(ObjectContainer db)
{
ObjectSet result=db.get(new Car("BMW"));
Car car=(Car)result.next();
car.snapshot();
db.set(car);
db.rollback();
PDF by iText, generated by Doctor, courtesy of db4objects Inc.
db.ext().refresh(car,Integer.MAX_VALUE);
System.out.println(car);
}

6.Embedded server
对应 Embedded server,open一个port为0的server。

[accessLocalServer]
ObjectServer server=Db4o.openServer(Util.YAPFILENAME,0);
try {
PDF by iText, generated by Doctor, courtesy of db4objects Inc.
ObjectContainer client=server.openClient();
// Do something with this client, or open more clients
client.close();
}
finally {
server.close();
}

7.Networking

[accessRemoteServer]
ObjectServer server=Db4o.openServer(Util.YAPFILENAME,PORT);
server.grantAccess(USER,PASSWORD);
try {
ObjectContainer
client=Db4o.openClient("localhost",PORT,USER,PASSWORD);
// Do something with this client, or open more clients
client.close();
}
finally {
server.close();
}

posted @ 2006-11-29 13:21 linugb118 阅读(1350) | 评论 (0)编辑 收藏

如何让Object 变得有序

如何让Object 变得有序

如何让Object 变得有序:
1,方法一:继承Comparable,实现CompareTo方法, CompareTo 只有一个参数,
但它有四个限制条件,具体我也没有研究,可查看相关信息。如果该类实现这个方法
它就具有比较规则定义,那么以后放在Collection中就能实现直接排序,或者与另一个同
类的对象进行比较。如果该类没有实现该方法,那它就会没有比较规则定义,那么就会throw
ClassCastExceptioin
2,方法二:如果类没有实现或者说定义比较规则,那么可以用比较器来定义Collection中的比较
策略,继承Comparator类并实现它中的Compare方法,Compare方法有两个参数,Compare方法同样有四个限制条件
其实比较器还是不很好用,因为在不同的方法中,比较器在方法中的用途或者说判断条件的地方有所不同,如果要想
很深入了解每个将Comparator作为参数的方法,那需要研究各个jdk中相干的源码。比如Arrays.sort
方法中就用到Comparator。

虽然Comparator 的具体实现不是很好理解,但它的理念还是和Comparable很清楚的。
方法一表示,上帝制造万物的时候,一开始就给每个物种添加了条件属性,使他们可以按有序排列
方法一表示,上帝制造万物的时候,但一时粗心忘了给每个物种添加了条件属性,他们就很混乱,女娲知道后就
造了一把尺子,在需要有序的地方让他们相互对着尺子比较。

posted @ 2006-11-29 13:19 linugb118 阅读(973) | 评论 (0)编辑 收藏

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

My Links

Blog Stats

常用链接

留言簿(1)

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜