2010-11-09 2 views
3

Я читаю Брюс Eckel-х Thinking in Java и есть упражнение, я просто не получаю:Понимание пакетов Java и заводы

Pg. 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 через статический завод 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(); 
    } 
} ///:~ 
+0

Отличная книга, но на этом один Брюс сделал это немного более запутанным, чем требовалось. Его наименование переменных и методов также могло бы немного улучшить этот пример. –

ответ

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 пакет.

+0

Но я думал, что у вас не может быть более одного публичного класса на пакет? Кроме того, весь смысл упражнения - запретить клиенту возможность напрямую создавать объекты Connection. Если Connection является общедоступным с помощью открытого конструктора, то клиент сможет его напрямую создать. –

+0

@ Robert S .: Нет, это не так, на самом деле у вас не может быть более 1 'public class' за файл. И обратите внимание, что с широко открытыми глазами, что конструктор не 'public', но класс. Кроме того, почему бы вам не попробовать весь мой пример - точно так же, как я дал указание - и попытаться создать экземпляр «Connection» напрямую. И скажите, можете ли вы или не можете. –

+0

+1: Отлично, что делает конструктор для подключения доступ к пакету вместо частного доступа решает проблему и выполняет требования упражнения. Для других преимуществ Connection и ConnectionManager должны быть в отдельных файлах в одном пакете, чтобы это работало. Сначала это было непонятно. Например, я создал подкаталог/пакет с именем CM и разместил там два файла для этих классов с директивой 'package CM;' в начале каждого файла.Первоначально я имел их в одном файле и получил ошибки компиляции. –

4

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

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

+0

Я не заметил, что вы предложили то же, что предложил Andreas_D. +1 –

+0

BTW, единственная причина, по которой вся эта сложность с заводскими классами действительно нужна, - это потому, что оператор 'new' имеет очень точную семантику, которая не может быть изменена. –

+0

Отличные моменты, но я не думаю, что они имеют отношение к этому конкретному упражнению, поскольку он даже не охватывал интерфейсы в этой точке книги. Мне кажется, он стремится к чему-то еще, я просто не знаю, что. –

0

Вы сказали

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

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

Кроме того, оглядываясь назад на задачу

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

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

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

+0

Итак, вы думаете, что я что-то читаю в упражнении, которое действительно не требуется, а именно полностью запрещает клиентской программе создавать объект соединения любым способом, кроме через фабрику ConnectionManager? –

0

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

0

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

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

+0

Я думаю, что это свойство видимости по умолчанию связано с тем, что эта книга заедает все в один файл. – Cephalopod

+0

Правильно, проблема заключается в том, что если я свяжу ConnectionManager и Connection вместе в отдельном пакете, то клиентский код вообще не сможет увидеть определение класса Connection и, следовательно, не сможет получить объекты Connection из ConnectionManager. –

3

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

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

Пример (уменьшенный, чтобы показать модификаторы доступа):

public interface Connection() { 
} 

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

Это то, чем я был. Но после загрузки вашего ответа я решил дать еще один вариант. Но это оригинальное и лучшее решение. +1 –

+0

Спасибо, но он не накрыл интерфейсы на данный момент в книге, и кажется, что он ищет какое-то решение на основе пакетов. –

+0

+1 для хорошего решения, даже если это не тот, который я искал. –

2

Другой способ, чтобы сделать 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(); 
    } 
} 
+0

Но разве класс Connection не отображается только в ConnectionManager? Как создать ссылку на объект Connection за пределами ConnectionManager? –

+0

@ Robert S .: Нет, там будет видно, какой класс будет «импортировать», так как модификатор «public». Помните, что вы хотите создать экземпляр 'Connection', используя' ConnectionManager', но не напрямую. Итак, так вы можете. Но я хотел бы, чтобы вы увидели мой другой ответ. Это в значительной степени соответствует упражнению. Вот ссылка, http://stackoverflow.com/questions/4132084/understanding-java-packages-and-factories/4132326#4132326 –

+0

+1 - Очень круто. В TestConnections.java мне пришлось квалифицировать «Connection conn ...» с 'ConnectionManager.'. Не могли бы вы объяснить более подробно, как это работает? Когда я удаляю ключевое слово 'static' из объявления Connection, я получаю ошибку« Нестационарная переменная ConnectionManager.java:9, на которую нельзя ссылаться из статического контекста » –

Смежные вопросы