+1

[Design Patterns] Flyweight

Flyweight Pattern là gì?

Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.

  • Flyweight giúp chúng ta tối ưu việc khởi tạo một số lượng lớn đối tượng bằng cách share những dữ liệu giống nhau và không bị thay đổi giữa các object.

Để áp dụng Flyweight Patter, chúng ta cần tách/xác định 2 loại properties của tối tượng gốc:

  • 1st là dữ liệu có thể thay đổi - Đây là dữ liệu thể hiện state của một đối tượng, thể hiện sự khác biệt giữa các đối tượng.

    Ví dụ: Toạ độ của một cái cây trên màn hình

  • 2nd là dữ liệu không thay đổi - Đây là loại dữ liệu sẽ không thay đổi trong suốt vòng đời của object

    Ví dụ: Khi chúng ta muốn vẽ một cánh rừng trong game, mỗi cái cây sẽ được xác định màu sắc, kích thước và hình ảnh. Những cái cây cùng loại tất nhiên sẽ có cùng những giá trị này

Lúc này, những dữ liệu ở kiểu thứ 2 có thể gộp lại thành một object, chỉ cần khởi tạo 1 lần và sử dụng lại ở tất cả các object có cùng kiểu - Đây chính là Flyweight object.

Implementation

package com.jacktt.designpatterns.structural.flyweight;

public class TreeType {
    private final String name;
    private final Integer width;
    private final Integer height;
    private final String base64Image;

    public String getName() {
        return name;
    }

    public Integer getWidth() {
        return width;
    }

    public Integer getHeight() {
        return height;
    }

    public String getBase64Image() {
        return base64Image;
    }

    public TreeType(String name,  Integer width, Integer height, String base64Image) {
        try {
            System.out.println("Initializing TreeType...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.name = name;
        this.width = width;
        this.height = height;
        this.base64Image = base64Image;
    }
}
package com.jacktt.designpatterns.structural.flyweight;

public class Tree {
    private TreeType type;
    private Integer id;
    private Integer x, y;

    public Tree(int id, TreeType type, Integer x, Integer y) {
        this.id = id;
        this.type = type;
        this.x = x;
        this.y = y;
    }

    public void draw() {
        System.out.println("Tree #" + this.id + " [x: " + this.x + ", y: " + y + "]" + " | Tree type: " + this.type.getName() + " ("+ this.type.hashCode()+ ")");
    }
}
package com.jacktt.designpatterns.structural.flyweight;

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

public class TreeTypeFactory {
    private static final Map<String, TreeType> treeTypeByName = new HashMap<>();

    public static TreeType getInstance(String name, int width, int height, String base64Image) {
        TreeType type = treeTypeByName.get(name);
        if (type == null) {
            type = new TreeType(name, width, height, base64Image);
            treeTypeByName.put(name, type);
        }
        return type;
    }
}
package com.jacktt;

import com.jacktt.designpatterns.structural.flyweight.Tree;
import com.jacktt.designpatterns.structural.flyweight.TreeType;
import com.jacktt.designpatterns.structural.flyweight.TreeTypeFactory;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Date startTime = new Date();
        List<Tree> trees = new ArrayList<>();

        for (int i = 0; i < 100_000; i++) {
            TreeType type;
            if (i % 2 == 0) {
                type = TreeTypeFactory.getInstance("Red Maple", 20, 100, "Long base64 data");
            } else {
                type = TreeTypeFactory.getInstance("Scarlet Oak", 35, 120, "Long base64 data");
            }
            trees.add(new Tree(i, type, i, i * 2));
        }

        trees.forEach(Tree::draw);
        System.out.println("Completed in " + (new Date().getTime() - startTime.getTime()) + "ms");
    }
}
...
Tree #99990 [x: 99990, y: 199980] | Tree type: Red Maple (1057941451)
Tree #99991 [x: 99991, y: 199982] | Tree type: Scarlet Oak (873415566)
Tree #99992 [x: 99992, y: 199984] | Tree type: Red Maple (1057941451)
Tree #99993 [x: 99993, y: 199986] | Tree type: Scarlet Oak (873415566)
Tree #99994 [x: 99994, y: 199988] | Tree type: Red Maple (1057941451)
Tree #99995 [x: 99995, y: 199990] | Tree type: Scarlet Oak (873415566)
Tree #99996 [x: 99996, y: 199992] | Tree type: Red Maple (1057941451)
Tree #99997 [x: 99997, y: 199994] | Tree type: Scarlet Oak (873415566)
Tree #99998 [x: 99998, y: 199996] | Tree type: Red Maple (1057941451)
Tree #99999 [x: 99999, y: 199998] | Tree type: Scarlet Oak (873415566)
Completed in 2519ms

Output cho thấy:

  • Hash của các TreeType có cùng name là như nhau, tức là chúng ta đang trỏ tới cùng một TreeType instance trong bộ nhớ.

    Vì vậy, tổng memory sử dụng cho các instance TreeType chỉ có 2, trong khi nếu không áp dụng Pattern này, tổng memory sẽ là 100_000 nhân size của một TreeType.

  • Tổng thời gain khởi tạo và vẽ ra 100_000 Tree chỉ mất 2,5s. Trong khi thời gian khởi tạo của mỗi TreeType là 1s. Tức là chúng ta đã tiết kiệm được 99,998s so với cách khởi tạo TreeType mới cho mỗi Tree.

Real-world application

  • Áp dụng khi chúng ta cần tạo một số lượng lớn các đối tượng của 1 lớp nào đó mà các đối tượng đó có những dữ liệu immutable giống nhau.
  • java.lang.Integer#valueOf(int) (also Boolean, Byte, Character, Short, Long and BigDecimal)

Pros & Cons

image.png

Tại sao nó lại tên là Flyweight?

Trong ngành công nghiệp sản xuất và tiêu dùng, "flyweight" là một thuật ngữ được sử dụng để chỉ các sản phẩm nhẹ và có thể được vận chuyển dễ dàng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí