Java 中的享元模式:通过共享对象实例最大化内存效率
享元设计模式的意图
Java 中的享元设计模式对于优化内存使用和提升应用程序性能至关重要。通过最大限度地减少创建的对象数量,它显著降低了内存占用。享元模式的主要目标是在尽可能多的相似对象之间共享数据,从而提高效率和性能。
享元模式的详细解释及现实世界中的例子
现实世界的例子
Java 中享元模式的现实世界应用可以在 Microsoft Word 或 Google Docs 等文本编辑器中看到。这些应用程序使用享元模式通过共享字符对象来有效管理内存,从而显著减少内存占用。在这些应用程序中,文档中的每个字符都可能是一个独立的对象,这在内存使用方面非常低效。相反,享元模式可以用来共享字符对象。例如,所有字母“A”的实例都可以共享一个包含其内在状态(例如字符形状)的单一“A”对象。外在状态,如位置、字体和颜色,可以单独存储并在需要时应用。这样,应用程序就可以通过重复使用重复出现的字符的现有对象来有效地管理内存。
简单来说
它用于通过尽可能多地与相似对象共享来最大限度地减少内存使用或计算成本。
维基百科说
在计算机编程中,享元模式是一种软件设计模式。享元模式是一种通过尽可能多地与其他相似对象共享数据来最大限度地减少内存使用的方式;当简单重复表示将使用无法接受的内存量时,它是一种使用大量对象的方法。
Java 中享元模式的编程示例
炼金术士的商店里摆满了各种魔法药剂。许多药剂都是相同的,因此无需为每种药剂创建一个新对象。相反,一个对象实例可以代表多个货架上的物品,这样内存占用就可以保持很小。
首先,我们有不同的 `Potion` 类型
public interface Potion {
void drink();
}
@Slf4j
public class HealingPotion implements Potion {
@Override
public void drink() {
LOGGER.info("You feel healed. (Potion={})", System.identityHashCode(this));
}
}
@Slf4j
public class HolyWaterPotion implements Potion {
@Override
public void drink() {
LOGGER.info("You feel blessed. (Potion={})", System.identityHashCode(this));
}
}
@Slf4j
public class InvisibilityPotion implements Potion {
@Override
public void drink() {
LOGGER.info("You become invisible. (Potion={})", System.identityHashCode(this));
}
}
然后是实际的享元类 `PotionFactory`,它是用于创建药剂的工厂。
public class PotionFactory {
private final Map<PotionType, Potion> potions;
public PotionFactory() {
potions = new EnumMap<>(PotionType.class);
}
Potion createPotion(PotionType type) {
var potion = potions.get(type);
if (potion == null) {
switch (type) {
case HEALING -> potion = new HealingPotion();
case HOLY_WATER -> potion = new HolyWaterPotion();
case INVISIBILITY -> potion = new InvisibilityPotion();
default -> {
}
}
potions.put(type, potion);
}
return potion;
}
}
`AlchemistShop` 包含两排魔法药剂。药剂是使用上述 `PotionFactory` 创建的。
@Slf4j
public class AlchemistShop {
private final List<Potion> topShelf;
private final List<Potion> bottomShelf;
public AlchemistShop() {
var factory = new PotionFactory();
topShelf = List.of(
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.STRENGTH),
factory.createPotion(PotionType.HEALING),
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.STRENGTH),
factory.createPotion(PotionType.HEALING),
factory.createPotion(PotionType.HEALING)
);
bottomShelf = List.of(
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.HOLY_WATER),
factory.createPotion(PotionType.HOLY_WATER)
);
}
public final List<Potion> getTopShelf() {
return List.copyOf(this.topShelf);
}
public final List<Potion> getBottomShelf() {
return List.copyOf(this.bottomShelf);
}
public void drinkPotions() {
LOGGER.info("Drinking top shelf potions\n");
topShelf.forEach(Potion::drink);
LOGGER.info("Drinking bottom shelf potions\n");
bottomShelf.forEach(Potion::drink);
}
}
在我们的场景中,一位勇敢的访客走进炼金术士的商店,喝掉了所有的药剂。
public static void main(String[] args) {
// create the alchemist shop with the potions
var alchemistShop = new AlchemistShop();
// a brave visitor enters the alchemist shop and drinks all the potions
alchemistShop.drinkPotions();
}
程序输出
09:02:52.731 [main] INFO com.iluwatar.flyweight.AlchemistShop -- Drinking top shelf potions
09:02:52.733 [main] INFO com.iluwatar.flyweight.InvisibilityPotion -- You become invisible. (Potion=1395089624)
09:02:52.733 [main] INFO com.iluwatar.flyweight.InvisibilityPotion -- You become invisible. (Potion=1395089624)
09:02:52.733 [main] INFO com.iluwatar.flyweight.StrengthPotion -- You feel strong. (Potion=1450821318)
09:02:52.733 [main] INFO com.iluwatar.flyweight.HealingPotion -- You feel healed. (Potion=668849042)
09:02:52.733 [main] INFO com.iluwatar.flyweight.InvisibilityPotion -- You become invisible. (Potion=1395089624)
09:02:52.733 [main] INFO com.iluwatar.flyweight.StrengthPotion -- You feel strong. (Potion=1450821318)
09:02:52.733 [main] INFO com.iluwatar.flyweight.HealingPotion -- You feel healed. (Potion=668849042)
09:02:52.733 [main] INFO com.iluwatar.flyweight.HealingPotion -- You feel healed. (Potion=668849042)
09:02:52.733 [main] INFO com.iluwatar.flyweight.AlchemistShop -- Drinking bottom shelf potions
09:02:52.734 [main] INFO com.iluwatar.flyweight.PoisonPotion -- Urgh! This is poisonous. (Potion=2096057945)
09:02:52.734 [main] INFO com.iluwatar.flyweight.PoisonPotion -- Urgh! This is poisonous. (Potion=2096057945)
09:02:52.734 [main] INFO com.iluwatar.flyweight.PoisonPotion -- Urgh! This is poisonous. (Potion=2096057945)
09:02:52.734 [main] INFO com.iluwatar.flyweight.HolyWaterPotion -- You feel blessed. (Potion=1689843956)
09:02:52.734 [main] INFO com.iluwatar.flyweight.HolyWaterPotion -- You feel blessed. (Potion=1689843956)
何时在 Java 中使用享元模式
享元模式的有效性在很大程度上取决于它在何处以及如何使用。当以下所有条件都满足时,请应用享元模式
- 享元模式在使用大量对象的 Java 应用程序中特别有效。
- 当由于对象数量过多导致存储成本很高时,享元模式通过共享内在数据并分别管理外在状态来提供帮助。
- 大多数对象状态可以被设置为外在状态。
- 一旦去除外在状态,许多组对象可能被相对较少的共享对象所取代。
- 应用程序不依赖于对象标识。由于享元对象可以共享,因此对概念上不同的对象的标识测试将返回真值。
Java 中享元模式的现实世界应用
- java.lang.Integer#valueOf(int) 以及类似的 Byte、Character 和其他包装类型。
- Java 的 String 类使用享元模式来有效地管理字符串字面量。
- GUI 应用程序通常使用享元模式来共享字体或图形组件等对象,从而节省内存并提高性能。
享元模式的优缺点
优点
- 减少对象实例的数量,节省内存。
- 集中状态管理,降低状态不一致的风险。
权衡
- 通过为共享对象添加管理层来增加复杂性。
- 如果实现不当,访问共享对象可能会产生潜在的开销。