Здесь описано два шаблона, которые используются исключительно для контроля количество объектов.

О Синглетоне (Singleton) можно было бы думать, как об особым случаем Пула Объектов (Object Pool), но приложения с Пулом Объектов достаточно уникальны по сравнению с Синглетоном, поэтому трактуются как два отдельных случая.

Синглетон (Singleton)

Возможно, синглетон является простейшим дизайном проектирования, который является способом обеспечения одного и только одного объекта определенного типа. Важнейший аспект Синглетона заключается в том, что вы предоставляете глобальную точку доступа, так что синглетон часто является решением, которое вы получали при использовании глобальных переменных в C. В дополнение, синглетон часто имеет характер реестра или поискового сервиса - это место, в котором вы ищите ссылки на другие объекты.

Синглетоны могут быть найдены и библиотеках Java, но здесь приведен более направленный пример:

//: синглетон:SingletonPattern.java
// Шаблон дизайна Синглетон: вы никогда
// не сможете создать более одного экземпляра.
package singleton;

import junit.framework.*;

// Так как здесь нет наследования от базового
// класса Cloneable, то размножаемость (клонируемость) не добавлена,
// модификатор final предотвращает добавление размножения
// посредством наследования:
final class Singleton {
  
private static Singleton s = new Singleton(47);
  
private int i;
  
  
private Singleton(int x) {
     
i = x;
  
}
  
  
public static Singleton getReference() {
     
return s;
  
}
  
  
public int getValue() {
     
return i;
  
}
  
  
public void setValue(int x) {
     
i = x;
  
}
}

public class SingletonPattern extends TestCase {
  
public void test() {
     
Singleton s = Singleton.getReference();
      String result =
"" + s.getValue();
      System.out.println
(result);
      assertEquals
(result, "47");
      Singleton s2 = Singleton.getReference
();
      s2.setValue
(9);
      result =
"" + s.getValue();
      System.out.println
(result);
      assertEquals
(result, "9");
     
try {
        
// Не могу сделать это: ошибка времени компиляции.
         // Singleton s3 = (Singleton)s2.clone();
     
}
     
catch (Exception e) {
        
throw new RuntimeException(e);
     
}
   }
  
  
public static void main(String[] args) {
     
junit.textui.TestRunner.run(SingletonPattern.class);
  
}
}
// /:~

Основной смысл при создании синглетона в том, чтобы у клиентского программиста не было возможности создать объект за исключением той, которую вы ему предоставили. Вы должны сделать все конструкторы частными (private) и вы должны создать хотя бы один конструктор, чтобы запретить компилятору синтезировать конструктор по умолчанию за вас (который будет создан, используя пакетный доступ).

В этом месте вы решаете, как вам создавать объект. В данном случае он создан статически, но вы можете также подождать, пока клиентский программист запросит объект, и создадите его по запросу. В любом случае, объект должен храниться недоступным образом. Вы обеспечиваете доступ через публичные методы. В данном случае getReference() предоставляет ссылку на объект Singleton. Остальная часть интерфейса (getValue() и setValue()) - это обычный интерфейс класса.

Java также позволяет создавать объекты с помощью клонирования. В этом примере модификатор final предотвращает клонирование. Так как Singleton наследуется напрямую от Object, метод clone() остается в разделе protected, так что он не может быть использован (его использование вызывает ошибку времени компиляции). Однако, если вы наследуете от иерархического класса, который уже перегрузил метод clone(), сделав его публичным через реализацию Clonable, есть способ предотвратить клонирование с помощью перегрузки метода clone(), который будет выбрасывать CloneNotSupportedException, как это описано в приложении А Thinking in Java, 2nd edition. (Вы можете также перегрузить метод clone() и просто возвращать this, но это может быть обманчивым, так как клиентский программист будет думать, что он клонировал объект, но вместо этого будет иметь дело с оригиналом этого объекта.) На самом деле, это не абсолютная правда, потому что даже в приведенной выше ситуации кто-либо может использовать рефлексию, чтобы вызвать clone() (не правда ли? clone() все еще является защищенным (protected), так что я не совсем уверен. Если это так, вы должны выбрасывать CloneNotSupportedException, как единственный способ гарантии не клонируемости.)

Упражнения

  1. SingletonPattern.java всегда создает объект, даже если он никогда не будет использован. Измените эту программу, использующую ленивую инициализацию, таким образом, чтобы объект синглетона создавался только при первой необходимости.
  2. Создайте службу реестра/поиска, которая принимает интерфейс Java и производит ссылку на объект, который реализует этот интерфейс.

Пул объектов (Object pool)

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

Как пример, предлагаю базу данных. Коммерческие базы данных часто ограничивают число соединений, которые вы можете использовать одновременно. Ниже приведена реализация, которая использует пул объектов, для управления соединениями. Прежде всего, основная концепция управления пулом объектов реализована в виде отдельного класса:

//: :PoolManager.java
package singleton;

import java.util.*;

public class PoolManager {
  
private static class PoolItem {
     
boolean inUse = false;
      Object item;
     
      PoolItem
(Object item) {
        
this.item = item;
     
}
   }
  
  
private ArrayList items = new ArrayList();
  
  
public void add(Object item) {
     
items.add(new PoolItem(item));
  
}
  
  
static class EmptyPoolException extends Exception {
   }
  
  
public Object get() throws EmptyPoolException {
     
for (int i = 0; i < items.size(); i++) {
        
PoolItem pitem = (PoolItem) items.get(i);
        
if (pitem.inUse == false) {
           
pitem.inUse = true;
           
return pitem.item;
        
}
      }
     
// :
     
throw new EmptyPoolException();
     
// return null; //
  
}
  
  
public void release(Object item) {
     
for (int i = 0; i < items.size(); i++) {
        
PoolItem pitem = (PoolItem) items.get(i);
        
if (item == pitem.item) {
           
pitem.inUse = false;
           
return;
        
}
      }
     
throw new RuntimeException(item + " not found");
  
}
}
// /:~

//: singleton:ConnectionPoolDemo.java
package singleton;

import junit.framework.*;

interface Connection {
  
Object get();
  
  
void set(Object x);
}

class ConnectionImplementation implements Connection {
  
public Object get() {
     
return null;
  
}
  
  
public void set(Object s) {
   }
}

class ConnectionPool { // Синглетон
  
private static PoolManager pool = new PoolManager();
  
  
public static void addConnections(int number) {
     
for (int i = 0; i < number; i++)
        
pool.add(new ConnectionImplementation());
  
}
  
  
public static Connection getConnection()
        
throws PoolManager.EmptyPoolException {
     
return (Connection) pool.get();
  
}
  
  
public static void releaseConnection(Connection c) {
     
pool.release(c);
  
}
}

public class ConnectionPoolDemo extends TestCase {
  
static {
     
ConnectionPool.addConnections(5);
  
}
  
  
public void test() {
     
Connection c = null;
     
try {
        
c = ConnectionPool.getConnection();
     
}
     
catch (PoolManager.EmptyPoolException e) {
        
throw new RuntimeException(e);
     
}
     
c.set(new Object());
      c.get
();
      ConnectionPool.releaseConnection
(c);
  
}
  
  
public void test2() {
     
Connection c = null;
     
try {
        
c = ConnectionPool.getConnection();
     
}
     
catch (PoolManager.EmptyPoolException e) {
        
throw new RuntimeException(e);
     
}
     
c.set(new Object());
      c.get
();
      ConnectionPool.releaseConnection
(c);
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(ConnectionPoolDemo.class);
  
}
}
// /:~

Упражнения

  1. Добавьте тестирование модуля ConnectionPoolDemo.java, чтобы продемонстрировать проблему, когда клиент может не освободить соединение, а продолжать использовать его.