Javenue logo

Javenue

Программирование на Java

Информационные технологии

Паттерн Prototype в Java - Прототип

В написанных ранее статьях мы уже рассматривали паттерны создания объектов (Creational Design Patterns).

Так как с необходимостью создания объектов програмисты встречаются каждый день, рассмотрим еще один паттерн - Prototype (Прототип).

Шаблон Prototype позволяет создавать новые объекты на основе некоторого объекта-прототипа при этом совсем не обязательно знать как необходимый объект устроен.

Вот некоторые ситуации когда может помочь этот паттерн проектирования:

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

В Java уже заложена функциональность для имплементации паттерна Прототип - интерфейс Cloneable.

Для этого достаточно объявить класс (который и будет прототипом), как реализующий Cloneable. Далее, вместо создания объектов нашего класса через оператор new, воспользуемся методом clone у ранее созданного объекта (прототипа).

Но рассмотрим отличный от клонирования вариант:

public interface Copyable {
  Copyable copy();
}

public class ComplicatedObject implements Copyable {
  public enum Type {
    ONE, TWO
  }

  public ComplicatedObject copy() {
    return new ComplicatedObject();
  }

  public void setType(Type type) {
    this.type = type;
  }
}

public class Test {
  public static void main(String[] args) {
    ComplicatedObject prototype = new ComplicatedObject();

    ComplicatedObject clone = prototype.copy();
    
    clone.setType(ComplicatedObject.Type.ONE);
  }
}

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

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

Альтернативой паттерну Прототип могут быть шаблоны Abstract Factory или Factory Method.

Жду ваших комментариев и вопросов.



Комментариев: 9

  Выйти

  * для публикации комментариев нужно  

svetliy:

Как-то не очень раскрыта суть паттерна.

1)если создание объектов (через оператор new) занимает длительный промежуток времени или требовательно к памяти;

как это понять если все равно в clone создается новый объект (вызов return new ComplicatedObject();)

2) Но рассмотрим отличный от клонирования вариант: и в чем суть этого отличного от клонирования варианта

Я понимаю так суть паттерна прототип: если нам необходимо получить клон(полную независимую копию экземпляра класса, где значения полей (атрибутов)совпадают с исходным, но имеют разные ссылки) объекта, то необходимо реализовать интерфейс Clonable. Метод clone класса Object просто производит копирование.

c0nst:

Спасибо за комментарий. Постараюсь ответить на ваши вопросы.

1) Прототип может использоваться и в том случае, если создание нового объекта нетривиально для пользователя. Например, в конструкторе много параметров. Или автор класса не хочет, чтобы пользователь создавал класс через new, но конструктор нельзя делать приватным для поддержки reflection. Всякое бывает…

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

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

Yuri:

Автор просто не показал клонирование данных объекта в методе

public ComplicatedObject copy() { return new ComplicatedObject(); }

более наглядно было бы:

public ComplicatedObject copy() {
  // клонируем тело
  ComplicatedObject res = new ComplicatedObject();
  // а теперь клонируем внутренности
  …
  return res;
}

А вообще, утверждение “если создание объектов (через оператор new) занимает длительный промежуток времени или требовательно к памяти” весьма сомнительно, кто-нибудь знает, как создать новый объект без new? Может reflection, скажет кто-то? А Class.newInstance(), что, не использует пустой конструктор?

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

black zorro:

А самое необычное для приходящих с c|c++ то, что в java у enum-ов могут быть методы. На уровне компилятора в c++ enum-ы преобразовывались в целые числа фактически можно было делать вещи вроде (надеюсь не путаю)

FOO === BAR + 10

В java когда объвляется тип enum, то это аналогично наследованию от класса.

enum HUMANSTATE {
  GOOD, BAD, DRUNK
}

public class HackAppProc {
  public static void main(String[] args) {
    HUMANSTATE s = HUMANSTATE.GOOD;
    System.out.println (s.ordinal());
    System.out.println (s.name());
    System.out.println (s.compareTo(HUMANSTATE.BAD));
    for (HUMANSTATE state : HUMANSTATE.values()) {
      System.out.println(state);
    }
  }
}

Интересно было бы почитать как enum-ы реализованы на уровне виртуальной машины в java и есть ли там хитрые хаки оптимизации.

А еще было бы неплохо увидеть статейку про SET-ы они с появлением varargs-синтаксиса и enum-ов стали как конфетки.

Ну а вообще официальный док: http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html

black zorro:

Недавно узнал что в java enum-ах можно делать так:

enum Sex {
    MALE(1) {
        public void foo() {
            System.out.println("MALE::foo");
        }
    },
    FEMALE(0) {
        public void foo() {
            System.out.println("FEMALE::foo");
        }
    };
    private int sex;
    private Sex(int sex) {
        this.sex = sex;
    }
    public int getSize() {
        return this.sex;
    }
    public void foo() {
        System.out.println("?");
    }
}

и пример использования:

public class TestMalesFemales {
    public static Sex getSex() {
        return Math.random() < 0.5 ? Sex.MALE : Sex.FEMALE;
    }
    public static void main(String[] args) {
        getSex().foo();
    }
}

Интересненько как в одном классе существуют три версии одной функции да еще с динамическим выбором нужного метода?

Не является ли каждый элемент enum-а фактически отдельным подклассом самого enum-а.

c0nst:

Почти все правильно говоришь. Запись “public enum Modifiers” равноценна “public final class Modifiers extends java.lang.Enum”. Это специальная лингвистическая конструкция для упрощения синтаксиса. Посмотри класс Enum из пакета java.lang.

black zorro:

public final class Modifiers extends java.lang.Enum

это как раз без вопросов, мое представление в том, что класс генерится для каждого из элементов енум-а,

да … вот только сейчас додумался глянуть результат компиляции (итак, класс Test0, с enum-ом Sex, с двумя элементами):

Test0$Sex.class
Test0$Sex$1.class
Test0$Sex$2.class

или вот еще экспериментик:

System.out.println(Sex.FEMALE.getClass().getSuperclass());
System.out.println(Sex.MALE.getClass().getSuperclass());

на выходе получил, что у обоих элементов enum-а один папа - сам enum:

class testi.Test0$Sex
class testi.Test0$Sex

Станислав Полозов:

Да вообще не раскрыта тема, начато за здравие, а продолжение никакое. Если начали говорить о Cloneable, то и в примере надо его приводить. public class ComplicatedObject implements Cloneable {...} ... ComplicatedObject test; try { ComplicatedObject userTest = test.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace();//объект не поддерживает клонирование }

Mykyta Zavada:

Сеттер для Type не прокатывает, потому что this ссылается в пустоту. А вообще интересно, чтобы компилятор показал что-то, кроме пустоты.