Понимание Java-пакетов и фабрик - PullRequest
3 голосов
/ 09 ноября 2010

Я читаю Мышление в Java * Брюса Экеля , и есть упражнение, которое я просто не получаю:

Pg. 161: Упражнение 8: (4) форма примера Lunch.java, создать класс под названием ConnectionManager, который управляет фиксированным массив объектов Connection. The клиентский программист не должен быть в состоянии явно создать объекты подключения , но может получить их только через статический метод в 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 через статическую фабрику 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();
  }
} ///:~

Ответы [ 7 ]

4 голосов
/ 09 ноября 2010

Каждый класс имеет область.В вашем коде класс Connection имеет область действия package (т. Е. К нему могут обращаться только классы в этом же пакете).Это означает, что перемещение класса ConnectionManager в другой пакет выводит его за пределы области видимости класса Connection;это провал, который вы видите.Эти соединения должны располагаться вместе с их фабрикой.

На самом деле, то, что вы бы на самом деле сделали бы, это создаете открытый интерфейс, который определяет, какие операции на Connection есть, ичастная реализация этого интерфейса внутри ConnectionManager;Затем метод newConnection может просто объявить, что он возвращает экземпляр интерфейса, и механизм защиты пакетов не дает кому-либо помешать этому интерфейсу (ну, не без использования более забавных частей отражения).

3 голосов
/ 09 ноября 2010

Хитрость: соединение должно быть не конкретным классом, а интерфейсом. Второй класс (ConnectionImpl) обеспечивает реализацию этого интерфейса.

Только ConnectionManager может создавать экземпляры этого конкретного класса и возвращает интерфейс .

Пример (уменьшено для отображения модификаторов доступа):

public interface Connection() {
}

public class ConnectionManager {
  private static class ConnectionImpl implement Connection {
    private ConnectionImpl() {
    }
  }
  public static Connection createConnection() {
    return new ConnectionImpl();
  }
}
2 голосов
/ 09 ноября 2010

Другой способ - сделать Connection открытым вложенным классом внутри ConnectionManager.Но вы можете этого не хотеть.Предложения Андреаса и Донала действительно лучше и практичнее.

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();
    }
}
1 голос
/ 09 ноября 2010

Это следующее 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 упаковке .

0 голосов
/ 09 ноября 2010

Метод makeConnection() имеет видимость по умолчанию, что означает, что он не может напрямую использоваться кодом в разных пакетах. (На самом деле то же самое относится и к самому классу Connection, который, я думаю, вам не нужен).

Обратите внимание, что клиентские программисты могут обойти это, поместив свой код в один пакет; но видимость кода, как правило, должна рассматриваться как помощь, чтобы помочь клиентским программистам увидеть API, а не детали реализации, а не как способ предотвратить преднамеренную глупость или злобу Хотя, если вам действительно нужно запустить ненадежный код, внедрение кода в ваши пакеты можно предотвратить, подписав ваши JAR-файлы.

0 голосов
/ 09 ноября 2010

Цель получения соединения через ConnectionManager - поместить связанные с ресурсом вещи в отдельный класс. В этом случае вы можете смоделировать соединение при написании модульного теста.

0 голосов
/ 09 ноября 2010

Вы сказали

Дело в том, что клиентская программа может напрямую создавать объекты Connection через статическую фабрику Connection.makeConnection

Я думаю, что если это статический метод, это не напрямую.

Также оглядываясь на задание

Клиентский программист не должен иметь возможности явно создавать объекты соединения

Ваше решение скрывает тот факт, что создаются новые подключения. Вы можете подчеркнуть это, переименовав «newConnection» в «getConnection» или что-то в этом роде.

Возможно, ожидаемое решение должно повторно использовать объекты соединения, но в упражнении этого не говорится. (И это не имеет особого смысла для соединений).

...