鸣谢:《大话设计模式》
简介
是一种结构型设计模式,旨在减少系统中对象的数量,以减小内存占用和提高性能。享元模式的核心思想是共享对象,即将可复用的对象共享,而不是每次需要时都创建新对象。这个模式适用于需要创建大量相似对象的情况。
运用共享技术有效地支持大量细粒度的对象。
实际上在Java中,字符串String就是运用了Flyweight模式。举个例子吧。’‘可以用来确定titleA与titleB是否是相同的实例,返回值为boolean值。当用new String()方法时,两个对象titleA和titleB的引用地址是不相同的,但当titleC和titleD都使用赋值的方式时,两个字符串的引用地址是相同的。
主要组成部分:
- 享元工厂(Flyweight Factory):享元工厂负责创建和管理享元对象。它维护一个享元对象的池,以便在需要时返回已存在的享元对象,或者创建新的享元对象。
-
具体享元(Concrete Flyweight):具体享元是享元对象的实现,它包含内部状态和外部状态。内部状态是不会随上下文而改变的部分,而外部状态是会随上下文而变化的部分。
-
客户端(Client):客户端使用享元工厂来获取享元对象,并向享元对象传递外部状态。客户端通常不直接创建享元对象,而是从享元工厂获取它们。
工作流程
-
客户端向享元工厂请求一个享元对象,并传递外部状态。
-
享元工厂检查是否已经有相应的享元对象存在。如果存在,返回现有的享元对象;如果不存在,创建一个新的享元对象并添加到池中,然后返回该对象。
-
客户端使用享元对象,将外部状态传递给它。
-
多个客户端可以共享相同的享元对象,减小内存占用。
例子
结构
代码
享元工厂(Flyweight Factory)
/**
* @author: lpy
* @Date: 2023/11/06
*/
public class WebsiteFactory {
private static Map<String,Website> map = new HashMap<>();
public static Website registerWebsite(String key){
if(null==map.get(key)){
ConcreteWebsite concreteWebsite = new ConcreteWebsite(key);
map.put(key,concreteWebsite);
return concreteWebsite;
}
return map.get(key);
}
public static Integer getCount(){
return map.size();
}
}
具体享元(Concrete Flyweight)
/**
* @author: lpy
* @Date: 2023/11/06
*/
public abstract class Website {
/**
* 抽象方法
* @param u
*/
public abstract void use(User u);
}
/**
* @author: lpy
* @Date: 2023/11/06
*/
public class ConcreteWebsite extends Website{
private String webName;
public ConcreteWebsite(String name){
this.webName = name;
}
@Override
public void use(User u) {
System.out.println("用户"+u.getName()+"< id:"+u.getId()+"使用网站"+webName);
}
}
user
/**
* @author: lpy
* @Date: 2023/11/06
* @desc: 用户类,用于网站的客户账号,是"网站"类的外部状态。
*/
@Data
@Accessors(chain = true)
public class User {
private String name;
private Integer id;
}
客户端(Client)
尽管给多个用户使用实例,但实际上只有两个网站实例
/**
* @author: lpy
* @Date: 2023/11/06
*/
public class FlyWeightTest {
public static void main(String[] args) {
Website blog = WebsiteFactory.registerWebsite("博客");
Website mail = WebsiteFactory.registerWebsite("商户");
blog.use(new User().setId(1).setName("费曼"));
blog.use(new User().setId(2).setName("费米"));
blog.use(new User().setId(3).setName("奥本海默"));
mail.use(new User().setId(4).setName("第谷"));
System.out.println(WebsiteFactory.getCount());
}
}
应用
系统中需要创建大量相似的对象,而这些对象的内部状态较稳定。对象的大多数状态可以变为外部状态,以便共享。系统需要高性能和低内存占用。
享元模式优缺点
- 优点:
- 减小内存占用:通过共享相似对象,减少了内存消耗,尤其是在需要大量对象时效果显著。
-
提高性能:减少了对象的创建和销毁次数,提高了系统性能。
-
支持多次复用:享元对象可以被多个客户端复用,从而减少了对象的创建和销毁次数。
-
分离内部状态和外部状态:享元模式将内部状态和外部状态分开,使系统更易于维护和扩展。
-
缺点:
- 复杂性增加:引入享元模式会增加系统的复杂性,因为需要维护享元工厂和共享对象池。这可能导致代码更难理解和维护。
-
外部状态管理:在享元模式中,对象的外部状态需要由客户端来管理和维护。这可能增加了客户端代码的复杂性,因为客户端需要确保正确传递外部状态给享元对象。
-
不适用于所有情况:享元模式适用于有大量相似对象且内部状态相对稳定的情况。对于对象变化较频繁的情况,不适合使用享元模式,因为频繁改变外部状态可能会导致混乱。
-
可能引入线程安全问题:如果多个线程同时访问共享对象池,可能需要考虑线程安全性,以防止竞态条件和数据一致性问题。
-
不易理解的内部状态:内部状态通常是共享的,因此如果多个对象共享相同的内部状态,可能会导致难以理解对象的行为。
-
性能问题:在某些情况下,享元模式可能会导致性能问题,尤其是当共享的对象池变得过大时,维护和查找对象可能会变得昂贵。
7 大设计原则
不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;
- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。
- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。
- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。
- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。
- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。
- 迪米特法则(Law of Demeter - LoD):
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。
源码位置
全部源码github.com/xs-alpha/designPattern
参考文献
- 《大话设计模式》
-
慕课视频《Java设计模式精讲 Debug方式+内存分析》
如有内容侵权,麻烦联系博主删除