了解Java包和工厂

时间:2010-11-09 09:21:41

标签: java packages factory-pattern

我正在读布鲁斯·埃克尔的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();
  }
} ///:~

7 个答案:

答案 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;
    }
}

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中。

答案 4 :(得分:0)

你说

  

问题是客户端程序可以直接创建连接对象通过静态Connection.makeConnection工厂

我认为如果是通过静态方法,则不是直接的。

另外,回顾一下任务

  

客户端程序员必须无法显式创建Connection对象

您的解决方案隐藏了创建新连接的事实。您可以通过将“newConnection”重命名为“getConnection”或其他内容来强调这一点。

也许预期的解决方案应该重用连接对象,但是在练习中并没有这么说。 (它对连接没有多大意义)。

答案 5 :(得分:0)

通过ConnectionManager获取连接的目的是将资源相关的东西放在一个单独的类中。在这种情况下,您可以在编写单元测试时模拟连接。

答案 6 :(得分:0)

makeConnection()方法具有默认可见性,这意味着它不能由不同包中的代码直接使用。 (实际上Connection类本身也是如此,我认为你不想这样做。

请注意,客户端程序员可以通过将代码放在同一个pacakge中来解决这个问题。但是代码可见性通常应被视为帮助客户程序员查看API而不是实现细节,而不是防止故意愚蠢或恶意的方法。虽然如果您确实需要运行不受信任的代码,可以通过签署JAR来防止将代码注入到包中。