了解 Java 包和工厂

发布于 2024-09-30 23:20:55 字数 2673 浏览 1 评论 0原文

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

页码。 161:练习 8:(4) 跟随 示例Lunch.java的形式, 创建一个名为 ConnectionManager 管理固定的 Connection 对象的数组。 的 客户端程序员一定不能 显式创建 Connection 对象, 但只能通过静态获取它们 连接管理器中的方法。当 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 工厂,这似乎违反了演习目标。然而,如果我将 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();
  }
} ///:~

I'm reading Bruce Eckel's Thinking in Java and there's an exercise I'm just not getting:

Pg. 161: Exercise 8: (4) Following
the form of the example Lunch.java,
create a class called
ConnectionManager that manages a fixed
array of Connection objects. The
client programmer must not be able to
explicitly create Connection objects
,
but can only get them via a static
method in ConnectionManager. When the
ConnectionManager runs out of objects,
it returns a null reference. Test the
classes in main( ).

I came up with the following solution:

// 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 );
            }
        }
    }
}

And a second file in the same directory meaning everything ends up in the same default package:

// 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;
    }
}

The thing is that the client program can directly create Connection objects via the static Connection.makeConnection factory, which seems to violate the exercises goals. Yet if I make the ConnectionManager.java a separate package then import it complains that it can't find a definition for Connection.

I feel like something is going over my head here, but I'm not sure what.

Here is the code for Lunch.java which is referenced in the question:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

谁的新欢旧爱 2024-10-07 23:20:55

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

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

Each class has a scope. In your code, the Connection class has package scope (i.e., it can only be accessed by classes within that same package). This means that moving the ConnectionManager class to another package moves it outside the scope that can see the Connection class; that's the failure you see. Those connections have to be co-located with their factory.

In reality, what you would actually do is you create a public interface that defines what operations on the Connection are, and a private implementation of that interface inside the ConnectionManager; the newConnection method can then just declare that it returns an instance of the interface, and the package protection mechanism stops anyone from prying behind that interface (well, not without using the funkier parts of reflection).

国产ˉ祖宗 2024-10-07 23:20:55

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

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

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

public interface Connection() {
}

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

The trick: Connection shouldn't be a concrete class but an interface. A second class (ConnectionImpl) provides an implementation of this interface.

Only the ConnectionManager can instantiate instances of this concrete class and returns the interface.

Example (reduced to show access modifiers):

public interface Connection() {
}

public class ConnectionManager {
  private static class ConnectionImpl implement Connection {
    private ConnectionImpl() {
    }
  }
  public static Connection createConnection() {
    return new ConnectionImpl();
  }
}
清泪尽 2024-10-07 23:20:55

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

Another way is to make Connection a public nested class inside the ConnectionManager. But you might not want that. Andreas_D's and Donal's suggestions are better and more practical, indeed.

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() {}
    }

}

Edited to show how the TestConnections will look like,

public class TestConnections {

    public static void main(String[] args) {
        Connection conn = ConnectionManager.newConnectiton();
    }
}
你好,陌生人 2024-10-07 23:20:55

从某种意义上说,这是通过将某些类放置在具有适当访问修饰符的不同类下来完成的。假设本书的学生/读者尚未了解界面

// 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位于不同中。

This is following Lunch.java more in a sense that its done by placing some classes under a different with appropriate access modifiers. Assuming the student/reader of the book isn't introduced to interfaces, yet.

// 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;
    }
}

Both, Connection and ConnectionManager are in the same package cm.

// File different.TestConnections.java
package different;

import cm.*;

public class TestConnections {

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

Notice, TestConnections is in the different package.

人海汹涌 2024-10-07 23:20:55

你说

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

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

另外,回顾一下任务

客户端程序员不得显式创建 Connection 对象

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

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

You said

The thing is that the client program can directly create Connection objects via the static Connection.makeConnection factory

I think if it is via a static method, it is not directly.

Also, looking back at the task

The client programmer must not be able to explicitly create Connection objects

You solution hides the fact that new connections are created. You can emphasize this by renaming "newConnection" to "getConnection" or something.

Maybe the expected solution should reuse connection objects, but it does not say so in the excercise. (And it does not make much sense for connections).

笑红尘 2024-10-07 23:20:55

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

The purpose of getting the connection via ConnectionManager is to have the resource related things in a separated class. In this case you can mock the connection when writing a unit test.

薔薇婲 2024-10-07 23:20:55

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

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

The makeConnection() method has default visibility, which means it cannot be used directly by code in different packages. (Actually the same is true for the Connection class itself, which I don't think you want).

Note that client programmers can get around this by placing their code in the same pacakge; but code visibility should generally be seen as an aid to help client programmers see the API rather than the implementation details, not as a way to prevent willful stupidity or malice. Though if you really need to run untrusted code, injecting code into your packages can be prevented by signing your JARs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文