Перейти к основному содержимому

Статические классы против шаблона Singleton в Java

· 5 мин. чтения

1. Введение

В этом кратком руководстве мы обсудим некоторые существенные различия между программированием по шаблону проектирования Singleton и использованием статических классов в Java. Мы рассмотрим обе методологии кодирования и сравним их применительно к различным аспектам программирования.

К концу этой статьи мы сможем принять правильное решение при выборе между двумя вариантами.

2. Основы

Давайте ударим по эпицентру. Singleton — это шаблон проектирования, который гарантирует наличие одного экземпляра класса на протяжении всего жизненного цикла приложения.

Он также обеспечивает глобальную точку доступа к этому экземпляру.

static — зарезервированное ключевое слово — это модификатор, который делает переменные экземпляра переменными класса. Следовательно, эти переменные связываются с классом (с любым объектом). При использовании с методами он делает их доступными только по имени класса. Наконец, мы также можем создавать статические вложенные внутренние классы .

В этом контексте статический класс содержит статические методы и статические переменные .

3. Синглтон против статических служебных классов

Теперь давайте спустимся в кроличью нору и поймем некоторые заметные различия между двумя гигантами. Мы начинаем наш поиск с некоторых объектно-ориентированных концепций.

3.1. Полиморфизм времени выполнения

Статические методы в Java разрешаются во время компиляции и не могут быть переопределены во время выполнения. Следовательно, статический класс не может по-настоящему извлечь выгоду из полиморфизма времени выполнения:

public class SuperUtility {

public static String echoIt(String data) {
return "SUPER";
}
}

public class SubUtility extends SuperUtility {

public static String echoIt(String data) {
return data;
}
}

@Test
public void whenStaticUtilClassInheritance_thenOverridingFails() {
SuperUtility superUtility = new SubUtility();
Assert.assertNotEquals("ECHO", superUtility.echoIt("ECHO"));
Assert.assertEquals("SUPER", superUtility.echoIt("ECHO"));
}

Напротив, синглтоны могут использовать полиморфизм времени выполнения, как и любой другой класс, производный от базового класса :

public class MyLock {

protected String takeLock(int locks) {
return "Taken Specific Lock";
}
}

public class SingletonLock extends MyLock {

// private constructor and getInstance method

@Override
public String takeLock(int locks) {
return "Taken Singleton Lock";
}
}

@Test
public void whenSingletonDerivesBaseClass_thenRuntimePolymorphism() {
MyLock myLock = new MyLock();
Assert.assertEquals("Taken Specific Lock", myLock.takeLock(10));
myLock = SingletonLock.getInstance();
Assert.assertEquals("Taken Singleton Lock", myLock.takeLock(10));
}

Кроме того, синглтоны также могут реализовывать интерфейсы , что дает им преимущество перед статическими классами:

public class FileSystemSingleton implements SingletonInterface {

// private constructor and getInstance method

@Override
public String describeMe() {
return "File System Responsibilities";
}
}

public class CachingSingleton implements SingletonInterface {

// private constructor and getInstance method

@Override
public String describeMe() {
return "Caching Responsibilities";
}
}

@Test
public void whenSingletonImplementsInterface_thenRuntimePolymorphism() {
SingletonInterface singleton = FileSystemSingleton.getInstance();
Assert.assertEquals("File System Responsibilities", singleton.describeMe());
singleton = CachingSingleton.getInstance();
Assert.assertEquals("Caching Responsibilities", singleton.describeMe());
}

Спринг-бины Spring Bean с областью действия Singleton, реализующие интерфейс, являются прекрасными примерами этой парадигмы.

3.2. Параметры метода

Поскольку это по сути объект, мы можем легко передать синглтон другим методам в качестве аргумента:

@Test
public void whenSingleton_thenPassAsArguments() {
SingletonInterface singleton = FileSystemSingleton.getInstance();
Assert.assertEquals("Taken Singleton Lock", singleton.passOnLocks(SingletonLock.getInstance()));
}

Однако создание статического объекта служебного класса и передача его в методах бесполезна и является плохой идеей.

3.3. Состояние объекта, сериализация и возможность клонирования

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

@Test
public void whenSingleton_thenAllowState() {
SingletonInterface singleton = FileSystemSingleton.getInstance();
IntStream.range(0, 5)
.forEach(i -> singleton.increment());
Assert.assertEquals(5, ((FileSystemSingleton) singleton).getFilesWritten());
}

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

new ObjectOutputStream(baos).writeObject(singleton);
SerializableSingleton singletonNew = (SerializableSingleton) new ObjectInputStream
(new ByteArrayInputStream(baos.toByteArray())).readObject();

Наконец, существование экземпляра также создает возможность его клонирования с использованием метода clone объекта :

@Test
public void whenSingleton_thenAllowCloneable() {
Assert.assertEquals(2, ((SerializableCloneableSingleton) singleton.cloneObject()).getState());
}

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

3.4. Механизм загрузки и распределение памяти

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

С другой стороны, статический класс включает в себя статические методы и статически связанные переменные во время компиляции и размещается в стеке.

Поэтому статические классы всегда жадно загружаются во время загрузки классов в JVM.

3.5. Эффективность и производительность

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

Кроме того, благодаря статической привязке во время компиляции они более эффективны, чем синглтоны, и, как правило, быстрее.

Мы должны выбирать синглтоны только по соображениям дизайна, а не как решение с одним экземпляром для повышения эффективности или производительности.

3.6. Другие незначительные отличия

Программирование для синглтона, а не для статического класса, также может принести пользу в плане необходимого рефакторинга.

Бесспорно, синглтон — это объект класса. Поэтому мы можем легко уйти от него к многоэкземплярному миру класса.

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

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

4. Сделать правильный выбор

Выбирайте синглтон, если мы:

  • Требуется полное объектно-ориентированное решение для приложения
  • Нужен только один экземпляр класса в любой момент времени и для поддержания состояния
  • Хотите лениво загружаемое решение для класса, чтобы оно загружалось только при необходимости

Используйте статические классы, когда мы:

  • Просто нужно хранить много статических служебных методов, которые работают только с входными параметрами и не изменяют какое-либо внутреннее состояние.
  • Не нужен полиморфизм во время выполнения или объектно-ориентированное решение

5. Вывод

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

Как всегда, мы можем найти полный код на GitHub .