Flyweight(享元)
1)意图
运用共享技术有效地支持大量细粒度的对象。
2)结构
享元模式的结构如图 7-36 所示。
其中:
-
Flyweight 描述一个接口,通过这个接口 Flyweight 可以接受并作用于外部状态
-
ConcreteFlyweight 实现 Flyweight 接口,并为内部状态(如果有)增加存储空间。ConcreteFlywweight 对象必须是可共享的。它所存储的状态必须是内部的,即它必须独立于 ConcreteFlyweight 对象的场景。
-
并非所有的 Flyweight 子类都需要被共享。Flywweight 接口使共享成为可能,但它并不强制共享。在 Flyweight 对象结构的某些层次,UnsharedConcreteFlyweight 对象通常将ConcreteFlyweight 对象作为子结点。
-
FlyweightFactory 创建并管理 Flyweight 对象;确保合理地共享 Flyweight,当用户请求一个 Flyweight 时,FlyweightFactony 对象提供一个已创建的实例或者在不存在时创建一个实例。
-
Client 维持一个对 Flyweight 的引用;计算或存储一个或多个 Flyweight 的外部状态。
3)适用性
Flyweight 模式适用于:
-
一个应用程序使用了大量的对象。
-
完全由于使用大量的对象,造成很大的存储开销。
-
对象的大多数状态都可变为外部状态。
-
如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
-
应用程序不依赖于对象标识。由于 Flyweight 对象可以被共享,所以对于概念上明显有别的对象,标识测试将返回真值。
4 应用举例
示例:商品展示
- 场景描述
假设我们正在开发一个电子商务网站,该网站上有成千上万种商品。每种商品都有许多属性,如名称、价格、描述、图片等。如果每次加载页面时都为每个商品创建一个完整的对象,将会消耗大量的内存资源。使用Flyweight模式可以帮助我们减少内存使用,提高页面加载速度。
- 实现步骤
- 定义享元接口
首先,定义一个接口来表示商品的基本功能。
public interface Product {
void display();
}
- 创建具体享元类
然后,实现这个接口的具体类,例如ConcreteProduct
,用来表示具体的商品。
public class ConcreteProduct implements Product {
private String name;
private double price;
private String description;
public ConcreteProduct(String name, double price, String description) {
this.name = name;
this.price = price;
this.description = description;
}
@Override
public void display() {
System.out.println("Product Name: " + name);
System.out.println("Price: $" + price);
System.out.println("Description: " + description);
}
}
- 创建享元工厂
接下来,创建一个工厂类来管理这些商品对象,确保相同的商品只创建一次,并且可以被多次复用。
import java.util.HashMap;
import java.util.Map;
public class ProductFactory {
private static final Map<String, Product> products = new HashMap<>();
public static Product getProduct(String key) {
if (!products.containsKey(key)) {
// 假设key是一个包含了商品名称、价格和描述的字符串
String[] parts = key.split(",");
products.put(key, new ConcreteProduct(parts[0], Double.parseDouble(parts[1]), parts[2]));
}
return products.get(key);
}
}
- 在Web应用中使用享元
在Web应用中,当用户访问商品列表页面时,根据商品ID从工厂中获取相应的商品对象,并显示这些商品。
public class ECommerceWebsite {
public static void main(String[] args) {
// 模拟用户访问商品列表页面
Product product1 = ProductFactory.getProduct("Laptop,999.99,A high-performance laptop for gaming and work");
Product product2 = ProductFactory.getProduct("Laptop,999.99,A high-performance laptop for gaming and work"); // 不会创建新的对象
Product product3 = ProductFactory.getProduct("Smartphone,699.99,A sleek and powerful smartphone with a great camera");
product1.display(); // 显示商品1
product2.display(); // 显示商品2
product3.display(); // 显示商品3
}
}
通过上述实现,即使有成千上万的商品具有相同的属性,系统也只需要维护一个对应的ConcreteProduct
对象。这样不仅减少了内存的使用,还提高了页面加载速度,特别是在高并发的情况下,这种优化尤为重要。
在Web开发中,Flyweight模式可以有效地帮助我们管理大量相似的对象,减少资源消耗,提高应用性能。特别是在涉及商品展示、用户评论、文章列表等场景下,该模式能够发挥重要作用。通过共享相同的数据,我们可以显著提高系统的效率和响应速度。
示例:在线学习平台
在Web开发中,Flyweight(享元)模式同样有其应用场景,尤其是在处理大量相似的数据对象时,可以显著提高性能和减少内存使用。下面以一个在线学习平台为例,说明如何在Web开发中使用享元模式。
- 场景描述
假设我们正在构建一个在线学习平台,用户可以在平台上观看课程视频、阅读文章和参与讨论。为了提供个性化的用户体验,平台需要存储用户的偏好设置,例如字体大小、主题颜色等。如果每个用户的偏好设置都独立存储为一个完整的对象,当用户数量非常大时,这将占用大量的内存资源。这时,可以采用享元模式来优化。
- 实现步骤
- 定义享元接口
首先,定义一个接口来表示用户偏好设置的功能。
public interface UserPreference {
void applySettings();
}
- 创建具体享元类
然后,实现这个接口的具体类,例如UserSetting
,用来表示具体的用户设置。
public class UserSetting implements UserPreference {
private String themeColor;
private int fontSize;
public UserSetting(String themeColor, int fontSize) {
this.themeColor = themeColor;
this.fontSize = fontSize;
}
@Override
public void applySettings() {
System.out.println("Applying settings: Theme Color - " + themeColor + ", Font Size - " + fontSize);
}
}
- 创建享元工厂
接下来,创建一个工厂类来管理这些设置对象,确保相同的设置只创建一次,并且可以被多次复用。
import java.util.HashMap;
import java.util.Map;
public class PreferenceFactory {
private static final Map<String, UserPreference> preferences = new HashMap<>();
public static UserPreference getUserPreference(String key) {
if (!preferences.containsKey(key)) {
// 假设key是一个包含了主题颜色和字体大小的字符串
String[] parts = key.split(",");
preferences.put(key, new UserSetting(parts[0], Integer.parseInt(parts[1])));
}
return preferences.get(key);
}
}
- 在Web应用中使用享元
在Web应用中,当用户登录时,根据用户的偏好设置获取相应的UserPreference
对象,并应用这些设置。
public class OnlineLearningPlatform {
public static void main(String[] args) {
// 模拟用户A和用户B登录,他们有相同的偏好设置
UserPreference userAPref = PreferenceFactory.getUserPreference("dark,14");
UserPreference userBPref = PreferenceFactory.getUserPreference("dark,14"); // 不会创建新的对象
userAPref.applySettings(); // 应用用户A的偏好设置
userBPref.applySettings(); // 应用用户B的偏好设置
// 模拟用户C登录,有不同的偏好设置
UserPreference userCPref = PreferenceFactory.getUserPreference("light,12");
userCPref.applySettings(); // 应用用户C的偏好设置
}
}
通过上述实现,即使有成千上万的用户具有相同的偏好设置,系统也只需要维护一个对应的UserSetting
对象。这样不仅减少了内存的使用,还提高了系统的响应速度,特别是在高并发的情况下,这种优化尤为重要。
在Web开发中,Flyweight模式可以有效地帮助我们管理大量相似的对象,减少资源消耗,提高应用性能。特别是在涉及用户个性化设置、界面组件等场景下,该模式能够发挥重要作用。
示例:文本编辑器中的字符格式化
Flyweight(享元)模式是一种用于性能优化的模式,主要用于减少创建大量对象所造成的内存开销。通过共享尽可能多的信息来有效地支持大量的细粒度的对象。享元模式特别适用于对象的大部分状态可以外部化的情况,也就是说这些信息可以从对象中移除并作为参数传入到对象的方法中。
- 应用场景
一个典型的应用场景是文本编辑器中的字符格式化。在大型文档中,如果每个字符都包含自己的格式设置(如字体、大小、颜色等),将会消耗大量的内存。使用享元模式,可以将相同的格式设置共享给多个字符,从而节省内存空间。
- 应用实例
假设我们正在开发一个简单的文本编辑器,需要处理带有不同格式的文本。为了提高效率,我们可以使用享元模式来实现字体格式的共享。
- 定义享元接口
首先,定义一个表示字符格式的接口或抽象类,这里我们使用接口CharacterFormat
。
public interface CharacterFormat {
void display(char character);
}
- 创建具体享元类
然后,实现这个接口的具体类,比如FontFormat
,它代表了具体的字体格式。
public class FontFormat implements CharacterFormat {
private String fontName;
private int fontSize;
private String color;
public FontFormat(String fontName, int fontSize, String color) {
this.fontName = fontName;
this.fontSize = fontSize;
this.color = color;
}
@Override
public void display(char character) {
System.out.println("Character: " + character + " with Font: " + fontName + ", Size: " + fontSize + ", Color: " + color);
}
}
- 创建享元工厂
接着,我们需要一个工厂类来管理这些格式化的实例,确保相同的格式只创建一次,并且可以被多次使用。
import java.util.HashMap;
import java.util.Map;
public class FormatFactory {
private static final Map<String, CharacterFormat> formats = new HashMap<>();
public static CharacterFormat getFormat(String key) {
if (!formats.containsKey(key)) {
// 假设key是一个包含了字体名称、大小和颜色的字符串
String[] parts = key.split(",");
formats.put(key, new FontFormat(parts[0], Integer.parseInt(parts[1]), parts[2]));
}
return formats.get(key);
}
}
- 使用享元
最后,在实际的应用程序中,当我们需要显示某个特定格式的字符时,可以通过FormatFactory
获取已经存在的格式对象,或者如果该对象不存在,则由工厂创建一个新的对象。
public class TextEditor {
public static void main(String[] args) {
CharacterFormat format1 = FormatFactory.getFormat("Arial,12,red");
CharacterFormat format2 = FormatFactory.getFormat("Arial,12,red"); // 这里不会创建新的对象
format1.display('H');
format2.display('e');
// 显示不同的格式
CharacterFormat format3 = FormatFactory.getFormat("Times New Roman,14,blue");
format3.display('l');
}
}
在这个例子中,即使两次请求了相同的字体格式,FormatFactory
也只会返回同一个FontFormat
对象,这样就避免了不必要的内存消耗。通过这种方式,享元模式帮助我们在处理大量相似对象时提高了应用程序的性能。