Android Design Pattern - Flyweight Pattern

1. Flyweight mode

The Flyweight pattern uses shared objects to efficiently support a large number of fine-grained objects. Flyweight pattern is a structural pattern.

Flyweight mode is often used in the underlying development of the system to solve system performance problems. It is mostly used in scenarios where there are a large number of duplicate objects, or when a buffer pool is required. Flyweight mode is an important implementation method of object pool technology. The created objects in the pool can be used directly without re-creation. This reduces the creation of repeated objects, thereby reducing memory and improving performance.

 

fa7c4c18bfe941b08b6fc48f1c0c67b2.png

Flyweight: The abstract Flyweight role is an abstract class of the product, and at the same time defines the interface or implementation of the external state and internal state of the object.

ConcreteFlyweight: The specific flyweight role is a specific product category, which implements abstract roles and defines related businesses.

UnsharedConcreteFlyweight: A role that cannot be shared, and generally does not appear in the flyweight factory.

FlyweightFactory: Flyweight factory class, used to build a pool container (collection), while providing methods to get objects from the pool.

Internal state: refers to the information shared by the object, which is stored inside the flyweight object and will not change with changes in the environment.

External state: refers to a mark on which an object can depend, which is a non-shareable state that changes as the environment changes.

 

2. Example of use

For example, when people browse websites, because there are various websites, if a large number of people use the same website, if new objects are created every time, it will inevitably cause the creation and destruction of a large number of repeated objects. Shared objects are cached, and the cache is used first when users query, and recreated if there is no cache. For a website, the type of the website is the internal state, and many users are the external state.

7936b0252e894c658ee15d398738f0c4.png

 Abstract flyweight role (site):

public abstract class Website {

    public abstract void use(User user);

}

Specific Flyweight role (website):

public class ConcreteWebsite extends Website {

    private String type;

    public ConcreteWebsite(String type) {

        this.type = type;

    }

    @Override

    public void use(User user) {

        System.out.println(user.getName() + "in use" + type + "website");

    }

}

Non-shareable roles (per user):

public class User {

    private String name;

    public User(String name) {

        this.name = name;

    }

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

}

Flyweight factory class:

public class WebsiteFactory {

    private Map cache = new ConcurrentHashMap<>();

    public Website getWebsite(String type) {

        if (!cache.containsKey(type)) {

           cache.put(type, new ConcreteWebsite( type));

        }

        return cache.get(type);

    }

    public int getCacheSize() {

        return cache.size();

    }

}

transfer:

public class Client {

public static void main(String[] args) {

    WebsiteFactory factory = new WebsiteFactory();

    Website news = factory.getWebsite("News");

    news.use(new User("Tom"));

    Website blog = factory.getWebsite("Blog");

    blog.use(new User("Jack"));

    Website blog2 = factory.getWebsite("Blog");

    blog2.use(new User("Smith"));

    Website blog3 = factory.getWebsite("Blog");

    blog3.use(new User("Alice"));

    System.out.println(factory.getCacheSize());

}

The printed results are as follows:

Tom is using a news site

Jack is using a blog site

Smith is using a blog site

Alice is using a blog site

2

It can be seen that 4 people are using the website, but only 2 website objects are actually created. This is because in the enjoyment factory class, map is used to save various websites, and keys are used to query, and if there are duplicates, they will be reused, and if there are no duplicates, they will be created directly, avoiding the creation of a large number of duplicate objects. If the Flyweight mode is not used, each user browses a website to create a website object. When the user data is large, a large number of objects with duplicate content will inevitably be generated. When these objects are useless, GC recycling will consume a lot of resources.

 

3. Use in source code

Message, String, Parcel and TypedArray in Android all take advantage of Flyweight mode.

Taking Message as an example, the handler sends the message as follows:

public final boolean sendEmptyMessageAtTime( int what, long uptimeMillis) {

    Message msg = Message.obtain();

    msg.what = what;

    return sendMessageAtTime(msg, uptimeMillis);

}

When sending a message, sendEmptyMessageAtTime is finally called, and in this method, a message is created and sent through Message.obtain(); Flyweight mode is to cut in from obtain.

public final class Message implements Parcelable {

    private static int sPoolSize = 0;

    Message next;

    private static final Object sPoolSync = new Object();

    private static Message sPool;

    private static final int MAX_POOL_SIZE = 10;

    public static Message obtain() {

        synchronized (sPoolSync) {

            if (sPool != null) {

                Message m = sPool; //m is equal to single linked list

                sPool = m.next; //sPool singly linked list discards header elements

                m.next = null; //m discards all elements except the header

                m.flags = 0; //flag is set to 0

                sPoolSize--; //Single linked list size minus 1

                return m;

            }

        }

        return new Message();

    }

}

Message is a singly linked list object. Message contains a next Message object. sPoolSize represents the number. Among them, Message holds a reference to the next Message through the next member variable, thus forming a Message linked list. The Message Pool manages all idle Messages through the header of the linked list.

It can be seen that when sPool is empty, new Message returns a newly created object; when sPool is not empty, the header element of the single-linked list is taken out and returned, and the single-linked list sPool discards the header, thus returning the existing The repeated object created completes the reuse of elements.

The class diagram of Message is as follows:

e6a0f85e27564a65aff72a0c60331232.jpg

After a Message is used, it can enter the Message Pool through the recycle() method, and can be obtained from the Message Pool through the obtain static method when needed.

The recycle implementation code is as follows:

public void recycle() {

    if (isInUse()) {

        if (gCheckRecycle) {

            throw new IllegalStateException("This message cannot be recycled because it is still in use.");

        }

        return;

    }

    recycleUnchecked();

}

void recycleUnchecked() {

    // Mark the message as in use while it remains in the recycled object pool. Clear out all other details.

    flags = FLAG_IN_USE;

    what = 0;

    arg1 = 0;

    arg2 = 0;

    obj = null;

    replyTo = null;

    sendingUid = -1;

    when = 0;

    target = null;

    callback = null;

    data = null;

    synchronized (sPoolSync) {

        if (sPoolSize < MAX_POOL_SIZE) { //The size of the singly linked list has not exceeded MAX_POOL_SIZE, then start inserting

            next = sPool; //next is directly equal to itself

            sPool = this; //sPool is equal to the element inserted now

            sPoolSize++; //size+1

        }

    }

}

The recycle() method first determines whether the Message object is being used, and if so, throws an exception; otherwise, it starts to insert the recycleUnchecked single-linked list. Each parameter is cleared before inserting.

Although Message is not the most standard Flyweight mode usage, it also realizes the idea of ​​object pool through single linked list.

 

In addition, String in JDK is also a similar message pool. After a String is defined, it exists in the constant pool. When the same string is used elsewhere, what is actually being used is the cache.

String str1 = new String("abc");

String str2 = new String("abc");

String str3 = "abc";

String str4 = "ab" + "c";

String generally has two judgments, one is equals, this is the comparison content, and the above four are all equal. The other is the "==" judgment, which is to compare whether the references are the same.

There is a String pool in Java. This String pool uses the Flyweight mode, and the same string will be obtained from the String pool. For example, str3 and str4 are the same objects obtained from the String pool.

str1 is not equal to str2, not equal to str3. However, str3 is equal to str4. Because str1 and str2 are new, and str3 is an equal sign literal assignment, they are different objects. And str4 is also a literal assignment, and str4 uses the str3 constant object cached in the buffer pool. So when using == to judge, they are the same object.

 

4. Summary

The flyweight pattern abandons the way of saving all data in each object, but loads more objects in a limited memory capacity by sharing the same state common to multiple objects.

In order to understand the flyweight mode more clearly, you can see the following example:

Consider a game where players move around a map and shoot each other. In this game, a large number of bullets, missiles and explosive shrapnel will travel across the entire map. If each particle (a bullet, a missile, or a piece of shrapnel) was represented by a separate object containing the complete data, then at some point after the player's fierce battle in the game reached a climax, the game would not be able to load in the remaining memory. Enter new particles, so the program crashes.

If you look closely at the particle class, you will find that the two member variables color (color) and sprite (sprite) consume much more memory than other variables. To make matters worse, the data stored in these two member variables is almost identical for all particles (such as the same color and sprite map for all bullets). The other states of each particle (coordinates, moving vector, and speed) are different, because the values ​​of these member variables are constantly changing, and these data represent the changing situation of the particle during its existence, but the color and sprite of each The graph will remain unchanged.

An object's constant data is often referred to as intrinsic state, which resides within the object and whose values ​​can only be read but not modified by other objects. The other states of objects can often be changed "from the outside" by other objects, so they are called extrinsic states.

The Flyweight pattern suggests not storing extrinsic state in an object, but passing it to a special method that depends on it. The program only saves the internal state in the object to facilitate reuse in different situations. These objects differ only in their internal state (which has far fewer variants than the external state), so the number of objects required is greatly reduced.

Get back into the game. If you can extract the external state from the particle class, then you only need three different objects (bullet, missile, and shrapnel) to represent all the particles in the game. Such an object that only stores internal state is called a flyweight.

So where would the outer state be moved? There has to be classes to store them. In most cases, they will be moved to the container object, which is the aggregate object before applying the flyweight pattern.

In this example, the container object is the main Game object, which stores all the particles in a member variable called particles. In order to be able to move the external state into this class, multiple array member variables need to be created to store the coordinates, direction vector and velocity of each particle. Beyond that, another array is needed to store references to specific flyweights that represent particles. These arrays must be kept in sync so that all data about a particle can be retrieved using the same index.

A more elegant solution is to create a separate situation class to store the extrinsic state and references to flyweight objects, in which case the container class only needs to contain an array. Although in this case, the number of scene objects will be the same as the number of objects when the flyweight mode is not used, but these objects are much smaller than before, and the member variables that consume the most memory have been moved to a few flyweight objects. Now, one flyweight large object will be reused by thousands of context small objects, so there is no need to store the data of thousands of large objects repeatedly.

Flyweight and immutability:

Since flyweight objects can be used in different contexts, it is necessary to ensure that their state cannot be modified. The state of the flyweight class can only be initialized once by the parameters of the constructor, and it cannot expose its setters or public member variables to other objects.

Flyweight factory:

In order to access various flyweights more conveniently, a factory method can be created to manage the cache pool of existing flyweight objects. The factory method receives the internal state of the target flyweight object from the client as a parameter, if it can find the desired flyweight in the buffer pool, it will return it to the client; if not found, it will create a new flyweight, and add it to the buffer pool.

Note: Flyweight mode is just an optimization. Before applying this mode, make sure that there are memory consumption problems related to a large number of similar objects occupying memory at the same time in the program, and make sure that the problem cannot be solved in other better ways.

The Flyweight class contains some state of the original object that can be shared among multiple objects. The same flyweight object can be used in many different scenarios. The state stored in the flyweight is called "intrinsic state". The state passed to the flyweight method is called "external state".

The Context class contains the various external states of the original object. Combining context and flyweight objects can represent the full state of the original object.

Typically, the behavior of the original object is preserved in the flyweight class. Therefore, calling the flyweight method must provide some external state as a parameter. But it's also possible to move the behavior into the context class, and then treat the attached flyweight as a mere data object.

The client (Client) is responsible for calculating or storing the external state of flyweight. From the perspective of the client, flyweight is a template object that can be configured at runtime. The specific configuration method is to pass some context data parameters into its method.

Flyweight Factory manages the cache pool of existing flyweights. With the factory in place, clients don't need to create flyweights directly, they just call the factory and pass it some internal state of the target flyweight. The factory will search the previously created flyweight according to the parameters, and return it if it finds a flyweight that meets the conditions; if it does not find it, it will create a new flyweight according to the parameters.

Advantages of Flyweight mode: If there are many similar objects in the program, it will save a lot of memory.

Disadvantages: ① Execution speed may need to be sacrificed in exchange for memory, because some scene data needs to be recalculated every time others call the flyweight method. ②The code will become more complicated. New members of the team always ask: why split an entity's state like this?

Guess you like

Origin blog.csdn.net/zenmela2011/article/details/127695440