【问题标题】:Understanding Java Packages and Factories了解 Java 包和工厂
【发布时间】:2011-05-07 03:13:56
【问题描述】:

我正在阅读 Bruce Eckel 的 Thinking in Java 并且有一个练习我没有得到:

页。 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();
  }
} ///:~

【问题讨论】:

  • 好书,但布鲁斯在这本书上比要求的更令人困惑。他对变量和方法的命名也可以对这个例子做一点改进。

标签: java packages factory-pattern


【解决方案1】:

每个类都有一个作用域。在您的代码中,Connection 类具有 package 范围(即,它只能由同一包中的类访问)。这意味着将ConnectionManager 类移动到另一个包会将其移到可以看到Connection 类的范围之外;这就是你看到的失败。这些连接必须与他们的工厂位于同一地点。

实际上,您实际上要做的是创建一个公共接口,该接口定义Connection 上的操作是什么,以及该接口在ConnectionManager 内的私有实现;然后newConnection 方法可以声明它返回接口的一个实例,并且包保护机制阻止任何人窥探该接口(嗯,不是不使用反射的更有趣的部分)。

【讨论】:

  • 我没有注意到您提出的建议与 Andreas_D 的建议相同。 +1
  • 顺便说一句,真正需要工厂类的整个复杂性的唯一原因是因为new 运算符具有非常精确的语义,无法修改。
  • 好点,但我认为它们与这个特定的练习无关,因为他在本书的这一点上甚至没有涉及接口。在我看来,他在努力争取别的东西,我只是不知道是什么。
  • @Robert:然后重点看我回答的第一段,记住工厂可以变得非常复杂。 (最终,它们变成了 Spring,但那是另一回事了……)
【解决方案2】:

诀窍:连接不应该是一个具体的类,而是一个接口。第二个类 (ConnectionImpl) 提供了此接口的实现。

只有 ConnectionManager 可以实例化这个具体类的实例并返回接口

示例(简化为显示访问修饰符):

public interface Connection() {
}

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

【讨论】:

  • 这就是我要做的。但是在加载您的答案后,我决定提供另一种选择。但这是原始且更好的解决方案。 +1
  • 谢谢,但他在本书的这一点上还没有涉及接口,看来他正在寻找一些基于包的解决方案。
  • +1 是一个很好的解决方案,即使它不是我想要的解决方案。
【解决方案3】:

另一种方法是使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();
    }
}

【讨论】:

  • 但是 Connection 类不是只在 ConnectionManager 中可见吗?如何在 ConnectionManager 之外创建对 Connection 对象的引用?
  • @Robert S.:不,任何imports 它的类都可以看到它,因为修饰符是public。请记住,您想使用ConnectionManager 创建Connection 的实例,但不是直接创建。所以,这样你就可以了。但我希望你在这里看到我的另一个答案。这与练习有关。这是链接,stackoverflow.com/questions/4132084/…
  • +1 - 非常酷。在 TestConnections.java 中,我还必须使用 ConnectionManager. 限定 Connection conn ...。你能更深入地解释一下这是如何工作的吗?当我从 Connection 的声明中删除 static 关键字时,我收到错误 ConnectionManager.java:9 non-static variable this cannot be referenced from a static context
  • @Robert S.:有关解释,请查看我在此线程中的答案,stackoverflow.com/questions/1845731/…。干杯。
【解决方案4】:

在某种意义上,这更多地遵循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();
    }
}

注意,TestConnectionsdifferent中。

【讨论】:

  • 但我认为每个包不能有多个公共类?此外,练习的重点是禁止客户端直接创建 Connection 对象。如果 Connection 是具有公共构造函数的公共连接,则客户端将能够直接实例化它。
  • @Robert S.:不,不是这样,实际上每个文件不能有超过 1 个 public class。请睁大眼睛注意,构造函数不是public,而是类。此外,您为什么不尝试我的整个示例——完全按照我的指示——并尝试直接创建一个Connection 实例。告诉我你能不能。
  • +1:太好了,使 Connection 的构造函数具有包访问权限而不是私有访问权限,解决了问题并满足了练习的要求。只是为了其他人的利益 Connection 和 ConnectionManager 必须在同一个包中的不同文件中才能工作。起初我并不清楚。例如,我创建了一个名为 CM 的子目录/包,并将这些类的两个文件放在其中,并在每个文件的开头加上 package CM; 指令。最初我将它们放在同一个文件中并出现编译错误。
  • @Robert S.:谢谢哥们。我又修改了一些,希望不会破坏清晰度。
【解决方案5】:

你说

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

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

还有,回头看看任务

客户端程序员不能显式创建连接对象

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

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

【讨论】:

  • 所以您认为我在练习中阅读了一些并非真正需要的内容,即完全禁止客户端程序能够以任何方式创建 Connection 对象,除非通过 ConnectionManager 工厂?跨度>
【解决方案6】:

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

【讨论】:

    【解决方案7】:

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

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

    【讨论】:

    • 我认为这种默认可见性的东西与这本书将所有内容都塞进一个文件这一事实有关。
    • 对了,问题的关键是如果我把ConnectionManager和Connection放在一个单独的包里,那么客户端代码根本看不到Connection类的定义,也就没有办法了从 ConnectionManager 获取 Connection 对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多