Прототип - Паттерн разработки

 

Определение

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

 

В Java клонирование объекта производится с помощью метода clone(), возвращающим всегда объект типа Object, поэтому требуется приводить результат к требуемому типу:

 

Jobj cloneObj = (Jobj) originalObj.clone();

 

Следует помнить, что клонируются только те объекты, которые реализуют интерфейс Cloneable, а объекты, которые клонированы быть не могут, при попытке клонирования выбрасывают CloneNotSupported Exception. Кроме того, это protected метод, поэтому вызывать его следует внутри того класса, который содержит данный.

 

Примеры
 

Пример 1

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

 

В пакете org.test.technerium.patterns.prototype.shallowclone1 создаем класс Book, реализующий интерфейс Cloneable, в котором определяем метод clone(), возвращающий объект типа Book, а также описываем считывание файла в контейнер:

 

package org.test.technerium.patterns.prototype.shallowclone1;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Vector;

public class Book implements Cloneable{
    private Vector<String> content;// Контейнер содержимого книги
    public Book(String bookName){

        content = new Vector<String>(); // Инициализация контейнера
        
        try {
            //Считываем файл bookName в контейнер
            BufferedReader file = new BufferedReader(new FileReader(bookName));
            String line;

            while ((line = file.readLine()) != null) {
                content.addElement(line);
            }
            file.close();
        } catch (IOException e) {
            System.out.println("An error while parsing book");
        }
    }
    //Определяем метод clone
    public Book clone() {
        Book clone = null;
        try {
            clone = (Book) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("An error cloning book obj:");
            e.printStackTrace();
        }
        return clone;
    }
    
    // Очистка книги
    public void empty(){
        content.clear();
    }
    
    // Возвращаем содержимое книги
    public Vector<String> getContent(){
        return content;
    }
}

Здесь мы определили метод empty(), который будем вызывать в качестве примера операции с объектом книги.

Далее оказывается удобным написать класс кэша BookCache, который будет инициализировать объект-прототип при помощи метода loadCache(). Когда клиентской части потребуется копия книги, получить ее можно будет  при помощи метода loadCache:

package org.test.technerium.patterns.prototype.shallowclone1;

import java.util.HashMap;
import java.util.Map;

public class BookCache {
    private static Map<String, Book> cache;
    
    public static Book getBook(String name){
        Book book = null;
        if(cache.containsKey(name) && cache.get(name)!=null)
            book = cache.get(name).clone(); //Извлекаем прототип из хранилиша и клонируем его
        
        return book;
    }
    
    public static void loadCache(){
        Book book = new Book("book.txt"); //Инициализируем прототип
        cache = new HashMap<String, Book>(); 
        cache.put("book.txt", book); // Сохраняем прототип в хранилище
    }
}

 

Далее пишем класс для запуска теста:

 

package org.test.technerium.patterns.prototype.shallowclone1;

import java.util.Vector;

public class RunTestPrototype {
    public static void main(String[] args){
        BookCache.loadCache();
        
        // Создаем два экземпляра книги и получим их содержимое
        Book book1 = BookCache.getBook("book.txt");
        Book book2 = BookCache.getBook("book.txt");
        Vector<String> content1 = book1.getContent();
        Vector<String> content2 = book2.getContent();
        // Выведем содержимое в консоль
        log("Book 1: ");
        for(String line : content1){
            log(line);
        }
        log("Book 2: ");
        for(String line : content2){
            log(line);
        }
        
        // Теперь опустошим второй экзепляр книги, получим содержимое обеих книг еще раз
        log("Empty-->");
        book2.empty();
        content1 = book1.getContent();
        content2 = book2.getContent();
        // Выведем содержимое в консоль
        log("Book 1: ");
        for(String line : content1){
            log(line);
        }
        log("Book 2: ");
        for(String line : content2){
            log(line);
        }
    }
    
    private static void log(String msg){
        System.out.println(msg);
    }
}

 

В файл book.txt пишем пример текста:

 

Lorem ipsum dolor sit amet,

consectetur adipisicing elit,

sed do eiusmod tempor

incididunt ut labore et dolore

magna aliqua. 

 

Запускаем тест и в консоли видим:

 

Book 1:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Book 2:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Empty-->
Book 1:
Book 2:

Здесь становится видно, что, хотя мы очищали содержимое второй книги, содержимое первой книги также очищено. Происходит это поскольку clone() создает неполную копию объекта книги: мы получаем копию ссылки на реальный объект  прототипа в памяти, а не ссылку на копию объекта. Поэтому любое изменение одной такой неполной копии отражается на других копиях. Заметьте, что в классе Book в методе empty мы изменяем объект вектора, одинаковый для обеих копий:

    public void empty(){
        content.clear();
    }

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

    public void empty(){
        content = new Vector<String>();
    }

Для  иллюстрации этого примера скопируем наш пакет  со всеми классами в новый org.test.technerium.patterns.prototype.shallowclone2 и в классе Book заменим empty() на приведенный выше. Запустим пример и в консоли увидим:

 

Book 1:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Book 2:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Empty-->
Book 1:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Book 2:

Что и требовалось показать: вторая книга не очищается.


Пример 2

Если требуется создавать полные копии прототипа, то следует использовать возможности интерфейса Serializable: класс сериализуем, то мы можем записать его как поток байтов и восстановить класс из потока байтов обратно. Снова копируем файлы пакета org.test.technerium.patterns.prototype.shallowclone1 в новый org.test.technerium.patterns.prototype.deepclone и в классе Book определим метод deepClone(), в котором выведем объект в поток вывода, а затем считываем байты обратно. Метод clone() удаляем за ненадобностью. Естественно, добавим указатель на интерфейс Serializable.

 

package org.test.technerium.patterns.prototype.deepclone;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Vector;
//делаем класс сериализуемым
public class Book implements Cloneable, Serializable{
    private Vector<String> content;
    public Book(String bookName){

        content = new Vector<String>();
        
        try {
            BufferedReader file = new BufferedReader(new FileReader(bookName));
            String line;

            while ((line = file.readLine()) != null) {
                content.addElement(line);
            }
            file.close();
        } catch (IOException e) {
            System.out.println("An error while parsing book");
        }
    }
    //полное копирование
    public Object deepClone()
    {
        try{
            // Операции с потоками
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(b);
            out.writeObject(this);
            ByteArrayInputStream bIn = new ByteArrayInputStream(b.toByteArray());
            ObjectInputStream oi = new ObjectInputStream(bIn);
            return (oi.readObject());
        } catch (Exception e){
            System.out.println("exception:"+e.getMessage());
            return null;
        }
    }
    // Очищаем содержимое
    public void empty(){
        content.clear();
    }
    
    public Vector<String> getContent(){
        return content;
    }
}

 

Соответственно поменяем создание копии в BookCache:

 

package org.test.technerium.patterns.prototype.deepclone;

import java.util.HashMap;
import java.util.Map;

public class BookCache {
    private static Map<String, Book> cache;
    
    public static Book getBook(String name){
        Book book = null;
        if(cache.containsKey(name) && cache.get(name)!=null)
            book = (Book) cache.get(name).deepClone(); //Возвращаем полную копию
        
        return book;
    }
    
    public static void loadCache(){
        Book book = new Book("book.txt");
        cache = new HashMap<String, Book>();
        cache.put("book.txt", book);
    }
}

 

Запускаем тест опять и видим, что теперь наши копии совершенно независимы:

 

Book 1:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Book 2:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Empty-->
Book 1:
Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et dolore
magna aliqua.
Book 2:

 


Надеюсь, теперь вы получили представление о паттерне Прототип на примере Java. Все примеры в архиве проекта для Eclipse можно скачать по ссылке.

Возможно, вам будут интересны и другие шаблоны, представленные в обзорной статье

 

 

Категория: 
Была ли статья полезна: 

Добавить комментарий

HTML

  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre> <p>
  • Строки и параграфы переносятся автоматически.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Поместите примеры вашего исходного кода в теги <code>...</code> or <source>...</source> и он будет красиво отформатирован.

Plain text

  • Поместите примеры вашего исходного кода в теги <code>...</code> or <source>...</source> и он будет красиво отформатирован.
  • Строки и параграфы переносятся автоматически.
  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <code> <source>
CAPTCHA
Пожалуйста, подтвердите, что вы человек.