In general, it is not easy to have a lot of scenarios using design patterns for business development. Here is a summary of the more frequent use of design patterns in business development. Of course the language is Java, based on the Spring Framework.

1. Flyweight Pattern

If there are a large number of duplicate objects in the system, or if a large number of duplicate objects need to be created and destroyed constantly. This is when the pattern can be used to cache these duplicate objects for sharing purposes, thus greatly reducing memory usage.

There are generally 3 roles in the flyweight Pattern:

  • Flyweight: flyweight class (mainly defines internal state and external state)
  • FlyweightFactory: flyweight factory class (mainly used to create and manage flyweight classes)
1.1 Flyweight Pattern

This is a design pattern that is triggered by actual production accidents and driven backwards to use. Because of doing marketing related, many users stay capital page (landing page) will use to the country region and telephone country code. As the basic data, we did not do storage on our side, and even caching was not done at that time. Completely by the basic services group to provide the relevant Feign interface calls, to put it bluntly, we are a middleman. The only thing we did was to identify the current country region based on the user’s IP, and store the top 15 hot country region data.

This data, I remember that there were about 30KB of statistics. That is to say, each request will create such an object in the heap, and then it will become a garbage object. At that time, it was doing a questionnaire that would be pushed to all APP users. Only one server was unresponsive at the beginning, followed by a second one, and the operation and maintenance rebooted the machine while notifying the alarm group at the side.

After investigation, and combined with the recent online content, and interface request monitoring data. Determined to be caused by a sudden increase in the number of visits to the interface in the country. But the specific causes and solutions need to look at the specific code logic to determine.

After introducing the business background, let’s talk about the general data structure.

Regional metadata

1
2
3
4
5
6
7
8
{
"code": "US",
"nameEn": "United States",
"tel": "+1",
"pyName": "mg",
"sortNo": 1,
"areaId": null
}

Overall Structure

1
2
3
4
5
{
"allCountry": [],
"commonCountry": [],
"currentCountry": {}
}

In this data, it is easy to see. allCountry basically does not change and can be considered as internal state, but of course this is where it takes up the most space. commonCountry basically does not change within a certain period of time, but we treat it here as an external state (internal implementation can be achieved by caching or other means). currentCountry needs to be generated dynamically based on the requested IP, and this is treated as an external variable. At this point, the overall idea is clear.

1.2 Code Implementation

Country Information POJO

1
2
3
4
5
6
7
8
9
@Data
public class Country {
private String code;
private String nameEn;
private String tel;
private String pyName;
private String sortNo;
private Integer areaId;
}

Flyweight classes (internal state, external state)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
public class CountryList {
// internal state
private final List<Country> allCountry;
// external state
private List<Country> commonCountry;
private Country currentCountry;

// Internal state, set when creating the object
public CountryList(List<Country> allCountry) {
this.allCountry = allCountry;
}

public void setCommonCountry(List<Country> commonCountry) {
this.commonCountry = commonCountry;
}

public void setCurrentCountry(Country currentCountry) {
this.currentCountry = currentCountry;
}
}

Flyweight factory class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
@Component
@RequiredArgsConstructor
public class CountryListFactory {
private static final String DEFAULT = "DEFAULT";
private static final Map<String, CountryList> CL_MAP = Maps.newHashMap();

private final CountryClient countryClient;

@PostConstruct
public void init() {
final List<Country> countryList = countryClient.getCountryList();
CL_MAP.put(DEFAULT, new CountryList(countryList));
}

public static CountryList getDefaultCl() {
return CL_MAP.get(DEFAULT);
}
}

Because there is only one kind of internal state in the flyweight class of this example. So the DEFAULTkey value is added, which is actually the only one.
Did you find anything here???? If there is no this DEFAULT, this is not a singleton pattern?
This piece is written in this way, mainly to better understand the flyweight pattern itself.

CountryClient is a service that mimics the external Feign interface and provides full country data acquisition service. And the data is injected into the static dictionary via @PostConstruct. Through this factory class, you can get the CountryList and use it after injecting external state into this class as needed.

1.3 Single test
1
2
3
4
5
6
7
8
9
@SpringBootTest
class CountryListTest {

@Test
void testFactory() {
final CountryList cl = CountryListFactory.getDefaultCl();
assertNotNull(cl);
}
}

Reflections

What is the difference with the singleton pattern?

  • Singleton is mainly about single, i.e. there is only one instance globally; while Flyweight is mainly about share, i.e. shared.
  • To some extent, Singleton can be considered as a special case of Flyweight.
  • Flyweight is more about saving memory by sharing.

Something similar to caching?

  • Caching, the main purpose of which is to speed up access. Flyweight, lies in data sharing for the purpose of saving space.
  • Caching is an idea that can be implemented through the flyweight pattern.

Some other thoughts?

  • flyweight is similar to singleton, which may involve thread safety issues?
    • It's mostly in the perspective of the problem and there is no conflict. When it comes to concurrency, there are thread safety issues to consider.
      
  • Can the same effect be achieved without the flyweight model?
    • Design patterns are generic solutions for specific (certain scenarios) and adaptations are important. Problems come first before solutions, not design patterns before specific scenarios.