<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    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 閱讀(5427) | 評論 (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 閱讀(1457) | 評論 (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 閱讀(395) | 評論 (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 閱讀(924) | 評論 (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 閱讀(1400) | 評論 (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 閱讀(268) | 評論 (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 閱讀(231) | 評論 (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 閱讀(1351) | 評論 (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 閱讀(976) | 評論 (0)編輯 收藏

    僅列出標題
    共5頁: 上一頁 1 2 3 4 5 

    My Links

    Blog Stats

    常用鏈接

    留言簿(1)

    隨筆檔案

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 国产亚洲大尺度无码无码专线| 亚洲第一成年免费网站| 亚洲区日韩区无码区| 无码成A毛片免费| 永久亚洲成a人片777777| 一级毛片免费毛片毛片| 亚洲成A∨人片天堂网无码| 亚洲欧美日韩国产精品一区| 国产美女在线精品免费观看| 亚洲码一区二区三区| 亚洲成a人无码亚洲成av无码 | 亚洲av成人一区二区三区在线播放| 成人免费看黄20分钟| 色天使色婷婷在线影院亚洲| 四虎影视永久免费观看| 亚洲天堂免费在线视频| 亚洲精品无码成人AAA片| 日韩中文字幕免费视频| 亚洲男人的天堂网站| 亚洲中文字幕无码永久在线| 99久久免费中文字幕精品| 久久综合亚洲色hezyo| mm1313亚洲国产精品美女| 亚洲伊人久久大香线蕉| 四虎影视精品永久免费| 一级特黄录像免费播放肥| 久久久久久亚洲Av无码精品专口| 蜜臀91精品国产免费观看| 国产精品黄页免费高清在线观看| 亚洲福利视频导航| 日韩一区二区免费视频| 久艹视频在线免费观看| 男人免费视频一区二区在线观看 | 亚洲国产精品无码专区影院 | 精品国产免费人成电影在线观看| 色屁屁在线观看视频免费| 亚洲国产日韩女人aaaaaa毛片在线 | 久久水蜜桃亚洲av无码精品麻豆| 国产精品视频免费一区二区| 中文字幕日本人妻久久久免费| 久久久久亚洲精品无码网址色欲 |