Flyweight Design Pattern
is a Structural Design Pattern
and is one of the Gang of Four Design Patterns
is mainly used for increasing performance
and minimizing memory usage
by reducing the number of objects
created and sharing
data and objects as much as possible to avoid OutOfMemoryError
.
Reuse existing objects and only create if no matching object is found.
all objects created are immutable
in nature and cannot be changed once created. hence can be shared
and used at multiple places. it stores
all existing objects into a collection
or cache
to reuse
them and only creates new ones if required.
Flyweight Design Pattern is preferred when the application requires a large number of objects or object creation is complex and requires lots of memory.
a Flyweight Object
has majorly 2 types of variables Intrinsic
and Extrinsic
, Let's understand them.
Intrinsic Variables
Intrinsic
or Invariant variables
whose state doesn't change once created or immutable
in nature, these variables act as the key
for the object to put in the map
or cache
. they can be reused
or transferred throughout the application.
Extrinsic Variables
Extrinsic
or Variant variables
state can be changed as per requirement. these values pass at the run time as per the requirements. these objects are dependent on application context and hence cannot be reused
or transferred
Some real-world examples of Flyweight Design Patterns are.
games have lots of bots or characters controlled by a computer, cars, or trees, they all behave similarly but with only a few differentiations for example color or shape, etc. One way is to create a lot of objects for each bot or car, or the better option is to create a single object with basic features then resue and update it again and again as per the requirement.
package org.wesome.design.patterns;
public interface Fruit {
void setAppleTaste(AppleTaste appleTaste);
void setQuantity(int quantity);
void eatApple();
}
package org.wesome.design.patterns;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@AllArgsConstructor
public class Apple implements Fruit {
private final AppleType appleType;
private AppleTaste appleTaste;
private int quantity;
@Override
public void eatApple() {
System.out.println("i am eating " + quantity + " " + appleType.name() + " apple, its very " + appleTaste);
}
}
package org.wesome.design.patterns;
public enum AppleType {
MACINTOSH, FUJI
}
package org.wesome.design.patterns;
public enum AppleTaste {
SWEET, JUICY
}
package org.wesome.design.patterns;
import java.util.HashMap;
import java.util.Map;
public class AppleShop {
public static final Map<AppleType, Fruit> appleShopInventoryCache = new HashMap<>();
public static Fruit getApple(AppleType appleType, AppleTaste appleTaste, int quantity) {
Fruit fruit;
if (appleShopInventoryCache.containsKey(appleType)) {
/* object is present in the cache, get the object and update other properties */
System.out.println("shop has " + appleType.name() + " apple, serving from stock.");
fruit = appleShopInventoryCache.get(appleType);
fruit.setQuantity(quantity);
fruit.setAppleTaste(appleTaste);
} else {
/* object is not present in the cache, create a new object and add it to the inventory cache */
System.out.println("shop doesn't have " + appleType.name() + " apple, placing order to farm.");
fruit = new Apple(appleType, appleTaste, quantity);
appleShopInventoryCache.put(appleType, fruit);
}
return fruit;
}
}
package org.wesome.design.patterns;
public class Flyweight {
public static void main(String[] args) {
Fruit fruit;
fruit = AppleShop.getApple(AppleType.MACINTOSH, AppleTaste.SWEET, 2);
fruit.eatApple();
fruit = AppleShop.getApple(AppleType.MACINTOSH, AppleTaste.JUICY, 3);
fruit.eatApple();
fruit = AppleShop.getApple(AppleType.FUJI, AppleTaste.SWEET, 4);
fruit.eatApple();
fruit = AppleShop.getApple(AppleType.FUJI, AppleTaste.JUICY, 5);
fruit.eatApple();
}
}
package org.wesome.design.patterns;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.stream.IntStream;
import static org.wesome.design.patterns.AppleShop.appleShopInventoryCache;
public class FlyweightTest {
@Test
void testFlyweight() {
IntStream.rangeClosed(1, 10).forEach(value -> {
Fruit fruit = AppleShop.getApple(AppleType.FUJI, AppleTaste.SWEET, value);
fruit.eatApple();
fruit = AppleShop.getApple(AppleType.MACINTOSH, AppleTaste.JUICY, value);
fruit.eatApple();
});
Assertions.assertEquals(appleShopInventoryCache.size(), 2);
}
}
plugins {
id 'java'
id "io.freefair.lombok" version "6.2.0"
}
group = 'org.wesome'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_1_8
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2'
}
test {
useJUnitPlatform()
}