[Design Pattern Series 13] I just found out today that I have been using Flyweight Model

Preface

We know that database connections are very performance-consuming, so there is a connection pool to reduce the performance consumption of connection operations;
if a large number of threads need to be created in a system, it will consume a lot of performance, so there is a thread pool. In the object-oriented process, we will create a large number of objects, and if some objects can be reused, can we create an object pool to reduce the performance cost of creating objects?

The answer is yes, this is what the Flyweight model we need to learn today does.

What is Flyweight Model

The Flyweight Pattern, also known as the Lightweight Pattern, is an implementation of the object pool. Mainly used to reduce the number of objects created to reduce memory usage and improve performance. Similar to our database connection pool and thread pool.

The purpose of the flyweight model is to share fine-grained objects, to centralize multiple accesses to the same object, without having to create a separate object for each visitor, so as to reduce memory consumption.

Flyweight mode is a structural mode.

Those who just talk about theory and don’t write code are hooligans, so the old rules: Talk is cheap, Show you the code .

Flyweight pattern example

Let's take the example of buying train tickets as an example.

1. First create a flyweight interface for a ticket, and define a method for querying ticket information:

package com.zwx.design.pattern.flyweight;

/**
 * 抽象享元角色
 */
public interface ITicket {
    
    
    void info();
}

2. Then define an implementation class to implement the ITicket interface:

package com.zwx.design.pattern.flyweight;

/**
 * 具体享元角色(粗粒度)
 */
public class TrainTicket implements ITicket{
    
    
    private String from;
    private String to;

    public TrainTicket(String from, String to) {
    
    
        this.from = from;
        this.to = to;
    }

    @Override
    public void info() {
    
    
        System.out.println(from + "->" + to + ":硬座:100元,硬卧:200元");
    }
}

3. Define a factory class to manage flyweight objects:

package com.zwx.design.pattern.flyweight;

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

/**
 * 享元对象工厂
 */
public class TicketFactory {
    
    
    private static Map<String,ITicket> CACHE_POOL = new HashMap<>();

    public static ITicket getTicketInfo(String from,String to){
    
    
        String key = from + "->" + to;
        if (TicketFactory.CACHE_POOL.containsKey(key)){
    
    
            System.out.println("使用缓存");
            return TicketFactory.CACHE_POOL.get(key);
        }
        System.out.println("未使用缓存");
        ITicket ticket = new TrainTicket(from,to);
        CACHE_POOL.put(key,ticket);
        return ticket;
    }
}

The factory class mainly uses a Map to store objects. The departure and destination of the train ticket are used as the key value. If it exists, it will be taken directly from the Map. Otherwise, a new object will be created and added to the Map.

4. Finally, write a test class to test:

package com.zwx.design.pattern.flyweight;

public class TestTicket {
    
    
    public static void main(String[] args) {
    
    
        ITicket ticket = TicketFactory.getTicketInfo("深圳","广州");
        ticket.info();//首次创建对象
        ticket = TicketFactory.getTicketInfo("深圳","广州");
        ticket.info();//使用缓存
        ticket = TicketFactory.getTicketInfo("深圳","北京");
        ticket.info();//使用缓存
    }
}

The output is:

未使用缓存
深圳->广州:硬座:100元,硬卧:200
使用缓存
深圳->广州:硬座:100元,硬卧:200
未使用缓存
深圳->广州:硬座:100元,硬卧:200

It can be seen that the tickets from Shenzhen -> Guangzhou are cached in the second query.

Seeing this writing method, some people may have questions. Isn't this the writing method of the container singleton pattern ? Yes, this is actually a way of writing the container singleton pattern, but it is not the same as the singleton pattern. The singleton pattern focuses on only one instance of the entire class, while the flyweight pattern focuses on the instance Object, cache (unique) for instance objects that can share state.

The above writing method also has a flaw. For example, the fare of the car is hard-coded. If we only want to check the hard seat or only the hard sleeper, how can this be achieved? This involves the granularity of the flyweight model

Flyweight mode status

In the above example, we can divide the instance objects, such as the ticket object above, we can regard the two attributes of from and to as shared states and cannot be changed. Then add another attribute to correspond to the seat. This is the internal state and external state of the flyweight model .

Internal state

The internal state refers to the information shared by the object, which is stored in the flyweight object and will not change with changes in the environment

External state

External state refers to a mark that an object can rely on. It is a state that changes with the environment and cannot be shared.

Please see the following example 2 is an example after rewriting according to the internal state and the external state:

Flyweight pattern example 2

1. First create a flyweight interface for tickets, define a method for querying ticket information and a method for setting seats:

package com.zwx.design.pattern.flyweight;

/**
 * 抽象享元角色
 */
public interface IShareTicket {
    
    
    void info();
    void setSeat(String seatType);
}

2. Then define an implementation class to implement the IShareTicket interface (of course, the price here is still hard-coded, the example is mainly used to experience the idea of ​​design patterns):

package com.zwx.design.pattern.flyweight;

import java.math.BigDecimal;

/**
 * 具体享元角色(细粒度)
 */
public class TrainShareTicket implements IShareTicket {
    
    
    private String from;//内部状态
    private String to;//内部状态

    private String seatType = "站票";//外部状态

    public TrainShareTicket(String from, String to) {
    
    
        this.from = from;
        this.to = to;
    }

    @Override
    public void setSeat(String seatType){
    
    
        this.seatType = seatType;
    }

    @Override
    public void info() {
    
    
        System.out.println(from + "->" + to + ":" + seatType + this.getPrice(seatType));
    }

    private BigDecimal getPrice(String seatType){
    
    
        BigDecimal value = null;
        switch (seatType){
    
    
            case "硬座":
                value = new BigDecimal("100");
                break;
            case "硬卧":
                value = new BigDecimal("200");
                break;
            default:
                value = new BigDecimal("50");
        }
        return value;
    }
}

Compared with the above example, this has an additional seatType attribute and an additional external method for setting seats.

3. Define a factory class to manage flyweight objects:

package com.zwx.design.pattern.flyweight;

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

/**
 * 享元对象工厂
 */
public class TicketShareFactory {
    
    
    private static Map<String,IShareTicket> CACHE_POOL = new HashMap<>();

    public static IShareTicket getTicketInfo(String from,String to){
    
    
        String key = from + "->" + to;
        if (TicketShareFactory.CACHE_POOL.containsKey(key)){
    
    
            System.out.println("使用缓存");
            return TicketShareFactory.CACHE_POOL.get(key);
        }
        System.out.println("未使用缓存");
        IShareTicket ticket = new TrainShareTicket(from,to);
        CACHE_POOL.put(key,ticket);
        return ticket;
    }
}

4. Finally, write a test class to test:

package com.zwx.design.pattern.flyweight;

public class TestShareTicket {
    
    

    public static void main(String[] args) {
    
    
        IShareTicket ticket = TicketShareFactory.getTicketInfo("深圳","广州");
        ticket.setSeat("硬座");
        ticket.info();//首次创建对象
        ticket = TicketShareFactory.getTicketInfo("深圳","广州");
        ticket.setSeat("硬卧");
        ticket.info();//外部状态改变了,但是内部状态共享,依然可以使用缓存
    }
}

Output result:

未使用缓存
深圳->广州:硬座100
使用缓存
深圳->广州:硬卧200

It can be seen that even if the external state changes, the internal state can still be shared.

Flyweight model role

From the above two examples, we can divide the flyweight model into the following three roles:

  • Abstract Flyweight: The abstract base class or interface of the Flyweight object, which defines the implementation interface of the external state and internal state of the object.
  • Concrete Flyweight role (ConcreateFlyweight): Realize the business defined by abstract roles. The internal state processing of the role should be independent of the environment.
  • FlyweightFactory: Responsible for managing the flyweight object pool and creating flyweight objects. Flyweight mode generally appears together with factory mode .

Flyweight mode in the JDK source code

  • 1. String String String String in
    Java will exist in the constant pool when it is used for the first time, and it can be taken out directly from the constant pool when it is used next time, without repeated creation. For details on the characteristics of String, you can click here to learn more.
  • 2. Integer object
    Integer is often used by everyone, in fact, Flyweight mode is also used in Integer.
    Let's first look at the following example:
package com.zwx.design.pattern.flyweight;

public class TestInteger {
    
    
    public static void main(String[] args) {
    
    
        Integer a = Integer.valueOf(10);
        Integer b = 10;
        System.out.println(a==b);

        Integer m = Integer.valueOf(128);
        Integer n = 128;
        System.out.println(m==n);
    }
}

The output here may be unexpected to many people:

true
false

The first sentence is true, and the second sentence outputs false. This seems a bit magical, let’s take a look at the source code of valueOf:
Insert picture description here
here is the Flyweight mode, which will be fetched from the cache first, but we see that there is a condition when fetching the cache, that is, the number must be between low and high:
Insert picture description here
You can see that low is -128, and high is 127 by default, so when we take 128, it is not equal.
Of course, why the cache is limited to -128~127 here is just an empirical reason. The numbers in this range are the most commonly used. In addition, the Flyweight model is also used in Long, so I won't continue to give examples here.

PS: Integer b=10 is actually executed after decompilation is the valueOf method.

Flyweight mode application scenarios

When some public information needs to be used in multiple places in the system, the information can be encapsulated into an object to implement the Flyweight mode to avoid the overhead of the system caused by repeated object creation.

Flyweight mode is mainly used in scenarios where there are a large number of similar objects in the system and a buffer pool is required. Generally, the flyweight mode is used for more underlying development to improve system performance.

Advantages and disadvantages of Flyweight model

advantage

Reducing the creation of objects reduces the number of objects in the system, so it can reduce the memory usage of the system and improve efficiency.

Disadvantage

  • 1. The complexity of the system is increased, and attention needs to be paid to separate the external state and the internal state.
  • 2. Need to pay attention to thread safety issues

to sum up

This article mainly introduces the simple writing method of Flyweight Mode and the writing method after separating the internal state and external state, and describes the application scenarios of Flyweight Mode in combination with the JDK source code.
Please pay attention to me and learn and progress with the lone wolf .

Guess you like

Origin blog.csdn.net/zwx900102/article/details/108554247