Шаблоны проектирования | Легковесные шаблоны и типичные приложения

Java задняя часть JVM Шаблоны проектирования

предисловие

Основное содержание этой статьи:

  • Знакомство с паттерном наилегчайшего веса
  • Пример — облачный диск
  • Суммировать
  • Типичное применение режима легковеса для анализа исходного кода
    • Шаблон легковеса в String
    • Шаблон облегченного веса в Integer
    • Шаблон наилегчайшего веса в длинном
    • Шаблон облегченного веса в Apache Common Pool2

Рекомендуемое чтение

Шаблоны проектирования | Простые фабричные шаблоны и типовые приложения
Шаблоны проектирования | Шаблоны фабричных методов и типичные приложения
Шаблоны проектирования | Абстрактные фабричные шаблоны и типовые приложения
Шаблоны проектирования | Шаблоны построителей и типичные приложения
Шаблоны проектирования | Шаблоны прототипов и типовые приложения
Шаблоны проектирования | Шаблоны внешнего вида и типичные приложения
Шаблоны проектирования | Шаблоны декораторов и типичные приложения
Шаблоны проектирования | Шаблоны адаптеров и типичные приложения

Нажмите [читать оригинал], чтобы посетить мой личный блог:laijianfeng.org

关注【小旋锋】微信公众号


наилегчайший образец

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

Роль

Наилегчайший вес (абстрактный наилегчайший вес): обычно интерфейс или абстрактный класс, в котором абстрактный класс-легковес объявляет общедоступные методы конкретного класса-легковеса.Эти методы могут предоставлять внутренние данные (внутреннее состояние) объекта-легковеса во внешний мир, а также могут быть установлены эти методы Внешние данные (внешнее состояние).

ConcreteFlyweight (конкретный класс наилегчайшего веса): он реализует абстрактный легковесный класс, а его экземпляры называются легковесными объектами; он предоставляет место для хранения внутреннего состояния в конкретном легковесном классе. Обычно мы можем разработать специальные классы наилегчайшего веса в сочетании с шаблоном singleton и предоставить уникальный объект наилегчайшего веса для каждого конкретного класса наилегчайшего веса.

UnsharedConcreteFlyweight (неразделяемый класс облегченного бетона): Not all subclasses of the abstract flyweight class need to be shared, and the subclasses that cannot be shared can be designed as non-shared concrete flyweight classes; when a non-shared concrete flyweight class object is required, it can be directly instantiated by Создайте.

FlyweightFactory (заводской класс в наилегчайшем весе): Фабричный класс легковеса используется для создания и управления объектами легковеса. Он запрограммирован для абстрактных классов легковесов и хранит различные типы конкретных объектов легковеса в пуле легковесов. Пул легковесов обычно разработан как хранилище «ключ-значение». «пары» (или другие типы коллекций) могут быть разработаны в сочетании с фабричным шаблоном; когда пользователь запрашивает конкретный объект-легковес, фабрика-легковес предоставляет созданный экземпляр, хранящийся в пуле-легковесе, или создает новый. он не существует), верните только что созданный экземпляр и сохраните его в пуле легковесов.

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

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

Код типичного фабричного класса наилегчайшего веса выглядит следующим образом:

class FlyweightFactory {
    //定义一个HashMap用于存储享元对象,实现享元池
    private HashMap flyweights = newHashMap();
    public Flyweight getFlyweight(String key){
        //如果对象存在,则直接从享元池获取
        if(flyweights.containsKey(key)){
            return(Flyweight)flyweights.get(key);
        }
        //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
        else {
            Flyweight fw = newConcreteFlyweight();
            flyweights.put(key,fw);
            return fw;
        }
    }
}

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

Типичный наслаждается, код класса следующим образом:

class Flyweight {
    //内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
    private String intrinsicState;
    public Flyweight(String intrinsicState) {
        this.intrinsicState=intrinsicState;
    }
    //外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象
    public void operation(String extrinsicState) {
        //......
    }
}

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

享元模式类图

Пример

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

Примечание: эта сцена - то, что думает редактор, и это не то же самое, что часто встречающиеся примеры. Редактор не уверен, является ли это режимом Flyweight или нет. Пожалуйста, дайте мне еще совет.

Сначала определите класс инструмента HashUtil для вычисления хеш-значения содержимого (Примечание: вычисляйте хеш-значение изwoo woo woo.cn blog on.com/oxfollow/afraid/396…скопировано)

public class HashUtil {
    public static String computeHashId(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }

    private static String bytesToHexString(byte[] bytes) {
        // http://stackoverflow.com/questions/332079
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
}

Ресурсный класс Ресурс, эквивалентный внутреннему состоянию легковесного класса

public class Resource {
    private String hashId;
    private int byteSize;
    private String content;

    public Resource(String content) {
        this.content = content;
        this.hashId = HashUtil.computeHashId(content);   // 文件的hash值
        this.byteSize = content.length();
    }
    // ....getter、setter、toString...
}

Класс файлов пользователя File, где ресурс — это внутреннее состояние, а владелец и имя файла — внешнее состояние.

public  class File {
    protected String owner;
    protected String filename;
    protected Resource resource;

    public File(String owner, String filename) {
        this.owner = owner;
        this.filename = filename;
    }

    public String fileMeta() {// 文件存储到文件系统中需要的key
        if (this.owner == null || filename == null || resource == null) {
            return "未知文件";
        }
        return owner + "-" + filename + resource.getHashId();
    }


    public String display() {
        return fileMeta() + ", 资源内容:" + getResource().toString();
    }
    // ....getter、setter、toString...
}

Класс сетевого диска PanServer, этот класс использует одноэлементный режим (в других примерах класс также использует режим фабричного метода), в методе загрузки судят о наличии файла с таким же содержимым по hashId загруженный файл и ссылка на него, если он существует. Загрузить файл, если он не существует.

public class PanServer {
    private static PanServer panServer = new PanServer(); // 单例模式
    private Map<String, Resource> resourceSystem; // 资源系统,相当于享元池
    private Map<String, File> fileSystem;   // 文件系统

    public PanServer() {
        resourceSystem = new HashMap<String, Resource>();
        fileSystem = new HashMap<String, File>();
    }

    public static PanServer getInstance() {
        return panServer;
    }

    public String upload(String username, LocalFile localFile) {
        long startTime = System.currentTimeMillis();
        File file = new File(username, localFile.getFilename());
        String hashId = HashUtil.computeHashId(localFile.getContent());     // 计算文件hash值
        System.out.println(username + " 上传文件");
        try {
            if (resourceSystem.containsKey(hashId)) {
                System.out.println(String.format("检测到内容相同的文件《%s》,为了节约空间,重用文件", localFile.getFilename()));
                file.setResource(this.resourceSystem.get(hashId));
                Thread.sleep(100);
            } else {
                System.out.println(String.format("文件《%s》上传中....", localFile.getFilename()));
                Resource newResource = new Resource(localFile.getContent());
                file.setResource(newResource);
                this.resourceSystem.put(newResource.getHashId(), newResource); // 将资源对象存储到资源池中
                Thread.sleep(3000);     // 上传文件需要耗费一定时间
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        fileSystem.put(file.fileMeta(), file);
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("文件上传完成,共耗费 %s 毫秒\n", endTime - startTime));
        return file.fileMeta();
    }


    public void download(String fileKey) {
        File file = this.fileSystem.get(fileKey);
        if (file == null) {
            System.out.println("文件不存在");
        } else {
            System.out.println("下载文件:" + file.display());
        }
        // 转为 LocalFile 返回
    }
}

Клиентские и локальные файловые классы

public class LocalFile {
    private String filename;
    private String content;

    public LocalFile(String filename, String content) {
        this.filename = filename;
        this.content = content;
    }
    //...省略...
}

public class Test {
    public static void main(String[] args) {
        PanServer panServer = PanServer.getInstance();

        String fileContent = "这是一个pdf文件《设计模式:从入门到放弃》";
        LocalFile localFile1 = new LocalFile("小明的设计模式.pdf", fileContent);
        String fikeKey1 = panServer.upload("小明", localFile1);

        LocalFile localFile2 = new LocalFile("大明的设计模式.pdf", fileContent);
        String fikeKey2 = panServer.upload("大明", localFile2);

        panServer.download(fikeKey1);
        panServer.download(fikeKey2);
    }
}

вывод

小明 上传文件
文件《小明的设计模式.pdf》上传中....
文件上传完成,共耗费 3077 毫秒

大明 上传文件
检测到内容相同的文件《大明的设计模式.pdf》,为了节约空间,重用文件
文件上传完成,共耗费 100 毫秒

下载文件:小明-小明的设计模式.pdf-f73ea50f00f87b42d1f2e4eb6b71d383, 资源内容:Resource {hashId='f73ea50f00f87b42d1f2e4eb6b71d383', byteSize=22, content='这是一个pdf文件《设计模式:从入门到放弃》'}
下载文件:大明-大明的设计模式.pdf-f73ea50f00f87b42d1f2e4eb6b71d383, 资源内容:Resource {hashId='f73ea50f00f87b42d1f2e4eb6b71d383', byteSize=22, content='这是一个pdf文件《设计模式:从入门到放弃》'}

Сяомин и Дамин загрузили файл.Содержимое (внутреннее состояние) файла одинаковое, но имя (внешнее состояние) отличается.Поскольку внутреннее состояние одинаково, нет необходимости хранить его повторно, поэтому делается копия внутреннего состояния.

Резюме модели наилегчайшего веса

наилегчайший весглавное преимуществоследующее:

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

наилегчайший весглавный недостатокследующее:

  • Шаблон Flyweight усложняет систему и требует разделения внутренних и внешних состояний, что усложняет логику программы.
  • Чтобы сделать объекты совместно используемыми, шаблон Приспособленца должен экстернализовать часть состояния объекта Приспособленца, а чтение внешнего состояния увеличит время выполнения.

Применимая сцена:

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

Типичное применение режима легковеса для анализа исходного кода

Шаблон легковеса в String

Класс String определен в Java как final (неизменяемый). Строки в JVM обычно хранятся в пуле строковых констант. Java гарантирует, что в пуле констант есть только одна копия строки. Этот пул строковых констант доступен в JDK6.0 Раньше он был в пуле констант и в постоянном поколении, но в JDK7.0 JVM вытащила его из постоянного поколения и поместила в кучу.

Проведем тест:

public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = "he" + "llo";
        String s4 = "hel" + new String("lo");
        String s5 = new String("hello");
        String s6 = s5.intern();
        String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8;
        System.out.println(s1==s2);//true
        System.out.println(s1==s3);//true
        System.out.println(s1==s4);//false
        System.out.println(s1==s9);//false
        System.out.println(s4==s5);//false
        System.out.println(s1==s6);//true
    }
}

Строковый классfinalИзменено, когда переменная String создается в виде литерала, jvm будет помещать литеральное значение во время компиляцииhelloОн помещается в пул строковых констант и загружается в память при запуске программы Java. Характеристика этого пула строковых констант заключается в том, что существует только одна копия одного и того же литерала. Если есть другие такие же литералы, jvm вернет ссылку на этот литерал. Если такого же литерала нет, он создаст его в строке постоянный пул.литерал и вернуть его ссылку.

Из-за литерала, на который указывает s2helloуже существует в пуле констант (s1 предшествует s2), поэтому jvm возвращает ссылку на эту литеральную привязку, поэтомуs1==s2.

Сращивание литералов в s3 на самом делеhello, jvm оптимизировал его при компиляции, так что s1 и s3 тоже равны.

в с4new String("lo")Создаются два объекта,lo,new String("lo"),loСуществует пул строковых констант,new String("lo")существует в куче,String s4 = "hel" + new String("lo")По сути, это сложение двух объектов, компилятор его не оптимизирует, результат сложения хранится в куче, а s1 хранится в пуле строковых констант, они, конечно, не равны.s1==s9тот же принцип.

s4==s5Результаты обоих сложений лежат в куче и, что и говорить, точно не равны.

s1==s6середина,s5.intern()Метод позволяет динамически добавлять строку в куче в пул строковых констант во время выполнения (содержимое пула строковых констант уже загружено при запуске программы), если объект находится в пуле строковых констант. Для соответствующего литерала вернуть ссылку на литерал в пуле строковых констант, в противном случае создать копию литерала в пуле строковых констант и вернуть его ссылку. следовательноs1==s6вывод верный.

Шаблон облегченного веса в Integer

Пример использования следующий:

    public static void main(String[] args) {
        Integer i1 = 12 ;
        Integer i2 = 12 ;
        System.out.println(i1 == i2);

        Integer b1 = 128 ;
        Integer b2 = 128 ;
        System.out.println(b1 == b2);
    }

вывод

true
false

Почему первое истинно, а второе ложно? Его можно найти после декомпиляцииInteger b1 = 128;на самом деле сталInteger b1 = Integer.valueOf(128);, так что давайте посмотримIntegerсерединаvalueOfреализация метода

public final class Integer extends Number implements Comparable<Integer> {
    public static Integer valueOf(int var0) {
        return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
    }
    //...省略...
}

Класс кэширования IntegerCache

    //是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。
    private static class IntegerCache {
        static final int low = -128;//区间的最低值
        static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数
        static final Integer cache[]; //缓存数组

        static {
            // high value may be configured by property
            int h = 127;
            //这里可以在运行时设置虚拟机参数来确定h  :-Djava.lang.Integer.IntegerCache.high=250
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {//用户设置了
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);//虽然设置了但是还是不能小于127
                // 也不能超过最大值
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            //循环将区间的数赋值给cache[]数组
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
        }

        private IntegerCache() {}
    }

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

Шаблон наилегчайшего веса в длинном

public final class Long extends Number implements Comparable<Long> {
    public static Long valueOf(long var0) {
        return var0 >= -128L && var0 <= 127L ? Long.LongCache.cache[(int)var0 + 128] : new Long(var0);
    }   
    private static class LongCache {
        private LongCache(){}

        static final Long cache[] = new Long[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Long(i - 128);
        }
    }
    //...
}

Так же,LongВ кэше также есть кеш, но максимальное значение кэша не может быть указано

Apache Commons Pool2 Flyweight

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

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

Есть несколько важных объектов:

ПулОбъект: используется для инкапсуляции объектов (таких как потоки, соединения с базой данных, TCP-соединения) и их переноса в объекты, которыми может управлять пул.
PooledObjectFactory.: Определяет некоторые способы эксплуатации жизненного цикла экземпляра PooledObject, а PooledObjectFactory должен реализовать безопасность потока.
Пул объектов: Пул объектов отвечает за управление объектами PooledObjects, например: предоставление объектов во временное пользование, возврат объектов, проверка объектов, количество активных объектов и количество незанятых объектов.

 // 对象池
 private final Map<S, PooledObject<S>> allObjects = new ConcurrentHashMap<S, PooledObject<S>>();

Важный метод:

ЗаемОбъект: заимствует объект из пула.
returnObject: возвращает объект в пул.

Из-за длительной, будет специально за введением и использованиемApache Commons Pool2статьи, ждите

Ссылаться на:
Лю Вэй: Шаблоны проектирования Java Edition
MOOC Java Design Patterns Интенсивный метод отладки + анализ памяти
Постоянный пул строк в Java
Анализ паттернов наилегчайшего веса Integer
Принцип излучения (Flyweight) База данных соединения Бассейн: 7 видов структурных режимов
Примечания к изучению исходного кода Apache commons-pool2-2.4.2
Анализ исходного кода Apache Commons Pool2