我正在读布鲁斯·埃克尔的Thinking in Java而且还有一个我没有得到的练习:
PG。 161:练习8:(4)以下 Lunch.java示例的形式, 创建一个名为的类 管理固定的ConnectionManager Connection对象的数组。的的 客户端程序员一定不能 显式创建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 :(得分:4)
每个班级都有一个范围。在您的代码中,Connection
类具有包范围(即,它只能由同一包中的类访问)。这意味着将ConnectionManager
类移动到另一个包会将其移到可以看到Connection
类的范围之外;那是你看到的失败。这些连接必须与他们的工厂位于同一地点。
实际上,你实际所做的是你创建一个公共接口来定义Connection
上的操作,以及{{1}内该接口的私有实现}; ConnectionManager
方法然后可以声明它返回一个接口的实例,并且包保护机制阻止任何人在该接口后面撬开(好吧,不是没有使用反射的更有趣的部分)。
答案 1 :(得分:3)
技巧:连接不应该是具体的类而是接口。第二个类(ConnectionImpl
)提供了此接口的实现。
只有ConnectionManager可以实例化此具体类的实例并返回接口。
示例(简化为显示访问修饰符):
public interface Connection() {
}
public class ConnectionManager {
private static class ConnectionImpl implement Connection {
private ConnectionImpl() {
}
}
public static Connection createConnection() {
return new ConnectionImpl();
}
}
答案 2 :(得分: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();
}
}
答案 3 :(得分: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;
}
}
Connection
和ConnectionManager
都在相同的包cm
中。
// File different.TestConnections.java
package different;
import cm.*;
public class TestConnections {
public static void main(String[] args) {
Connection conn = ConnectionManager.newConnectiton();
}
}
请注意,TestConnections
位于 different
包中。
答案 4 :(得分:0)
你说
问题是客户端程序可以直接创建连接对象通过静态Connection.makeConnection工厂
我认为如果是通过静态方法,则不是直接的。
另外,回顾一下任务
客户端程序员必须无法显式创建Connection对象
您的解决方案隐藏了创建新连接的事实。您可以通过将“newConnection”重命名为“getConnection”或其他内容来强调这一点。
也许预期的解决方案应该重用连接对象,但是在练习中并没有这么说。 (它对连接没有多大意义)。
答案 5 :(得分:0)
通过ConnectionManager获取连接的目的是将资源相关的东西放在一个单独的类中。在这种情况下,您可以在编写单元测试时模拟连接。
答案 6 :(得分:0)
makeConnection()
方法具有默认可见性,这意味着它不能由不同包中的代码直接使用。 (实际上Connection
类本身也是如此,我认为你不想这样做。
请注意,客户端程序员可以通过将代码放在同一个pacakge中来解决这个问题。但是代码可见性通常应被视为帮助客户程序员查看API而不是实现细节,而不是防止故意愚蠢或恶意的方法。虽然如果您确实需要运行不受信任的代码,可以通过签署JAR来防止将代码注入到包中。