2010-11-09 46 views
3

我閱讀布魯斯Eckel的Thinking in Java並有一個鍛鍊,我只是沒有得到:瞭解Java包和工廠

PG。 161:練習8:(4)以下 示例Lunch.java的形式, 創建一個名爲 的ConnectionManager,它管理Connection對象的固定 數組。 客戶端編程人員 必須無法顯式創建連接對象, ,但只能通過ConnectionManager中的靜態 方法獲取它們。當ConnectionManager用完對象 時,它將返回一個空引用。在main()中測試 類。

我想出了以下解決方案:

// TestConnection.java 
import java.util.*; 

public class TestConnections { 
    public static void main(String[] args) { 
     Connection cn = Connection.makeConnection(); 

     for (int i = 0; i != 6; ++i) { 
      Connection tmp = ConnectionManager.newConnectiton(); 
      if (tmp == null) 
       System.out.println("Out of Connection objects"); 
      else { 
       System.out.println("Got object: " + tmp); 
      } 
     } 
    } 
} 

和第二文件在同一目錄意味着一切都在相同的默認包結束:

// ConnectionManager.java 
class Connection { 
    private Connection() {} 

    static Connection makeConnection() { 
     return new Connection(); 
    } 
} 

public class ConnectionManager { 
    static private Connection[] _connections = new Connection[5]; 

    private ConnectionManager() {} 

    static public Connection newConnectiton() { 
     for (int i = 0; i != _connections.length; ++i) { 
      if (_connections[i] == null) { 
       _connections[i] = Connection.makeConnection(); 
       return _connections[i]; 
      } 
     } 
     return null; 
    } 
} 

的事情是,客戶端程序可以通過靜態Connection.makeConnection工廠直接創建Connection對象,這似乎違反了練習目標。然而,如果我使ConnectionManager.java成爲一個單獨的包,那麼導入它會抱怨它找不到Connection的定義。

我覺得這裏有些事情要過去,但我不確定是什麼。

下面是其在問題爲引用Lunch.java的代碼:

//: access/Lunch.java 
// Demonstrates class access specifiers. Make a class 
// effectively private with private constructors: 

class Soup1 { 
    private Soup1() {} 
    // (1) Allow creation via static method: 
    public static Soup1 makeSoup() { 
    return new Soup1(); 
    } 
} 

class Soup2 { 
    private Soup2() {} 
    // (2) Create a static object and return a reference 
    // upon request.(The "Singleton" pattern): 
    private static Soup2 ps1 = new Soup2(); 
    public static Soup2 access() { 
    return ps1; 
    } 
    public void f() {} 
} 

// Only one public class allowed per file: 
public class Lunch { 
    void testPrivate() { 
    // Can't do this! Private constructor: 
    //! Soup1 soup = new Soup1(); 
    } 
    void testStatic() { 
    Soup1 soup = Soup1.makeSoup(); 
    } 
    void testSingleton() { 
    Soup2.access().f(); 
    } 
} ///:~ 
+0

偉大的書,但在這一個布魯斯使它比需要更混亂一點。他的變量和方法的命名也可以在這個例子中稍微改進一點。 – 2010-11-09 09:44:15

回答

1

這也是繼Lunch.java更在某種意義上,它通過將一些類根據不同的適當訪問修飾符來完成。假設該書的學生/讀者沒有被引入interfaces

// File cm.Connection.java 
package cm; 

public class Connection { 
    // The constructor has package access and so is available to 
    // ConnectionManager, but not to any class outside package cm 
    Connection() { } 
} 

// File cm.ConnectionManager.java 
package cm; 

public class ConnectionManager { 
    static private Connection[] _connections = new Connection[5]; 

    private ConnectionManager() {} 

    static public Connection newConnectiton() { 
     for (int i = 0; i != _connections.length; ++i) { 
      if (_connections[i] == null) { 
       _connections[i] = new Connection(); 
       return _connections[i]; 
      } 
     } 
     return null; 
    } 
} 

兩者,ConnectionConnectionManager同一封裝cm

// File different.TestConnections.java 
package different; 

import cm.*; 

public class TestConnections { 

    public static void main(String[] args) { 
     Connection conn = ConnectionManager.newConnectiton(); 
    } 
} 

通知,TestConnections是在different

+0

但我認爲每個包裝不能有多個公共課程?此外,練習的重點是禁止客戶端直接創建Connection對象。如果Connection是公共構造函數,則客戶端將能夠直接實例化它。 – 2010-11-09 10:54:43

+0

@Robert S:不,不是,實際上每個文件不能超過1個'public class'。請注意,睜大眼睛,構造函數不是「公共」的,但類是。此外,爲什麼不嘗試我的整個示例 - 完全按照我的指示 - 並嘗試直接創建一個「Connection」實例。並告訴我你是否可以。 – 2010-11-09 11:01:04

+0

+1:很好,使Connection的構造函數具有包訪問而不是私人訪問,可以解決問題並滿足練習的要求。爲了其他的好處,Connection和ConnectionManager必須位於同一個包中的單獨文件中才能工作。起初我不清楚這一點。例如,我創建了一個名爲CM的子目錄/包,並將這兩個類的兩個文件與每個文件開頭的'package CM;'指令放在那裏。最初我讓他們在同一個文件中,並得到編譯錯誤。 – 2010-11-09 12:55:10

4

每個類都有一個範圍。在您的代碼中,Connection類具有範圍(即,它只能由相同包中的類訪問)。這意味着將ConnectionManager類移到另一個包會將其移到範圍之外,該範圍可以看到Connection類;那是你看到的失敗。這些連接必須與工廠同處一處。

在現實中,你會實際上做的就是你所創造的一個公共接口,它定義了在Connection操作,和私人實施ConnectionManager內部的接口;然後newConnection方法可以聲明它返回接口的一個實例,並且包保護機制可以阻止任何人在該接口後面窺探(當然,不是沒有使用反射的更好的部分)。

+0

我沒有注意到你提出了和Andreas_D建議的一樣的東西。 +1 – 2010-11-09 09:44:06

+0

順便說一句,真正需要工廠類的整個複雜性的唯一原因是因爲'new'運算符具有非常精確的語義,不能修改。 – 2010-11-09 10:16:15

+0

偉大的觀點,但我不認爲他們與這個特定的練習有關,因爲他在書中此時甚至沒有涉及界面。在我看來,他正在努力尋找其他的東西,我只是不知道該怎麼做。 – 2010-11-09 10:45:40

0

你說

的事情是,客戶端程序可以直接創建連接對象通過靜態Connection.makeConnection工廠

我想,如果它是通過一個靜態方法,它不是直接的。

此外,回首任務

客戶端程序員必須不能明確創建連接對象

您的解決方案隱藏了新的連接創建的事實。您可以通過將「newConnection」重命名爲「getConnection」等來強調這一點。

也許預期的解決方案應該重用連接對象,但它不會在鍛鍊; Tibial這麼說。 (而且連接沒有多大意義)。

+0

所以你認爲我正在閱讀一些並非真正需要的東西,即完全禁止客戶端程序以任何方式創建Connection對象通過ConnectionManager工廠? – 2010-11-09 10:56:53

0

通過ConnectionManager獲取連接的目的是讓資源在分離的類中相關。在這種情況下,您可以在編寫單元測試時模擬連接。

0

makeConnection()方法具有默認的可見性,這意味着它不能直接通過代碼在不同的封裝中。 (實際上Connection課程本身也是如此,我認爲你不需要)。

請注意,客戶端程序員可以通過將其代碼置於相同的位置來解決此問題;但代碼可見性通常應該被看作是幫助客戶程序員看到API而不是實現細節的一種輔助手段,而不是用來防止故意的愚蠢或惡意的方式。儘管如果你真的需要運行不可信的代碼,通過簽名你的JAR可以防止你的包中注入代碼。

+0

我認爲這個默認可見的東西與這本書將所有東西都塞進單個文件的事實有關。 – Cephalopod 2010-11-09 09:43:00

+0

對,問題的癥結在於,如果我將ConnectionManager和Connection放在一個單獨的包中,那麼客戶端代碼根本看不到Connection類的定義,因此無法從ConnectionManager獲取Connection對象。 – 2010-11-09 11:01:41

3

訣竅:連接不應該是一個具體的類,而是一個接口。第二類(ConnectionImpl)提供了此接口的實現。

只有ConnectionManager可以實例化此具體類的實例並返回接口

(縮小以顯示訪問修飾符):

public interface Connection() { 
} 

public class ConnectionManager { 
    private static class ConnectionImpl implement Connection { 
    private ConnectionImpl() { 
    } 
    } 
    public static Connection createConnection() { 
    return new ConnectionImpl(); 
    } 
} 
+0

這就是我所要做的。但在加載你的答案後,我決定給出另一種選擇。但這是原來的更好的解決方案。 +1 – 2010-11-09 09:39:52

+0

謝謝,但他沒有在書中的這一點介紹過接口,他似乎正在尋找一些基於包的解決方案。 – 2010-11-09 10:48:08

+0

+1是一個很好的解決方案,即使它不是我正在尋找的那個。 – 2010-11-09 14:03:07

2

的另一種方法是使所述Connection內部ConnectionManager一個公共嵌套類。但你可能不想要那樣。確實,Andreas_D和Donal的建議更好,更實用。

public class ConnectionManager { 
    private static Connection[] _connections = new Connection[5]; 

    private ConnectionManager() {} 

    public static Connection newConnectiton() { 
     for (int i = 0; i != _connections.length; ++i) { 
      if (_connections[i] == null) { 
       _connections[i] = new Connection(); 
       return _connections[i]; 
      } 
     } 
     return null; 
    } 

    public static class Connection { 
     private Connection() {} 
    } 

} 

編輯,以顯示如何TestConnections的樣子,

public class TestConnections { 

    public static void main(String[] args) { 
     Connection conn = ConnectionManager.newConnectiton(); 
    } 
} 
+0

但Connection類是否僅在ConnectionManager中可見?我怎樣才能創建一個連接對象以外的ConnectionManager的ref? – 2010-11-09 10:50:54

+0

@Robert S:不,它可以在那裏看到任何「輸入」它的類,因爲修飾符是「public」。請記住,您想使用'ConnectionManager'創建'Connection'的實例,但不是直接創建。所以,你可以這樣。但我希望你能在這裏看到我的其他答案。這很大程度上取決於練習。這裏是鏈接,http://stackoverflow.com/questions/4132084/understanding-java-packages-and-factories/4132326#4132326 – 2010-11-09 10:58:24

+0

+1 - 非常酷。在TestConnections.java中,我必須使用'ConnectionManager.'來限定'Connection conn ...'。你能不能更深入地解釋這是如何工作的?當我從Connection的聲明中刪除'static'關鍵字時,我得到錯誤'ConnectionManager.java:9非靜態變量,這不能從靜態上下文中引用' – 2010-11-09 12:15:04