享元模式

  享元模式是设计模式中少数几个以提高系统性能为目的的模式之一。它的核心思想是:如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用都创建新的对象。在享元模式中,由于需要构造和维护这些可以共享的对象,因此,常常会出现一个工厂类,用于维护和创建对象。

  享元模式对性能提升的主要帮助有亮点:

  (1)可以节省重复创建对象的开销,因为被享元模式维护的相同对象只会被创建一次,当创建对象比较耗时时,便可以节省大量时间。

  (2)由于创建对象的数量减少,所以对系统内存的需求也减小,这将使得GC的压力也相应地降低,进而使得系统拥有一个更健康的内存结构和更快的反应速度。

  享元模式的主要角色由享元工厂、抽象享元、具体享元类和主函数几部分组成。他们的功能如下:

角色 作用
享元工厂 用以创建具体享元类,维护相同的享元对象。它保证相同的享元对象可以被系统共享。即,期内部使用类类似单例模式的算法,当请求对象已经存在时,直接返回对象,不存在时,在创建对象。
抽象享元 定义需共享的对象的业务接口。享元类被创建出来总是为了实现某些特定的业务逻辑,而抽象享元便定义这些逻辑的语义行为
具体享元类 实现抽象享元类的接口,完成某一具体逻辑
Main 使用享元模式的组件,通过享元工厂取得享元对象

 

 

 

 

 

 

 

 

  基于以上角色,享元模式的结构图如下:

   享元工厂是享元模式的核心,它需要确保系统可以共享相同的对象。一般情况下,享元工厂会维护一个对象列表,当任何组件尝试获取享元类时,如果请求的享元类已经被创建,则直接返回已有的享元类;若没有,则创建一个新的享元对象,并将它加入到维护队列中。

  享元模式的一个典型应用是在SAAS系统中。SAAS即As A Service,目前比较流行的一种软件应用模式。

  以一个人事管理系统的SAAS软件为例,假设公司甲、乙、丙均为这个SAAS系统的用户,则定义每个公司为这套系统的一个租户。每个公司(租户)又各有100个员工。如果这个公司的所有员工都可以登陆这套系统查看自己的收入情况,并且为了系统安全,每个公司(租户)都拥有自己独立的数据库。为了是系统的设计最为合理,在这种情况下,便可以使用享元模式为每个租户分别提供工资查询的接口,而一个公司(租户)下的所有员工可以共享一个查询(因为一个租户下所有的员工数据都存放在一个数据库中,它门共享数据连接)。这样,系统只需要3个享元实例,就足以应付300个员工的查询请求。系统的结构如下图:

   图中,ReportManagerFactory为享元工厂,负责创建具体的报表工具,它确保每个公司(租户)下所有的员工,都共享一个具体的享元实例(FinancialReportManager 或者EmployeeReportManager)。这样,当公司甲的两个员工登陆,进行财务查询时,系统不必为两个员工都新建FinancialReportManager,而可以让他们共享一个FinancialReportManager实例。

  通过本示例,还可以进一步了解享元工厂和对象池的一个重要区别。在一个对象池中,所有的对象都是等价的,任意两个对象在任何使用场景中都可以被对象池中的其他对象代替。而在享元模式中,享元工厂所维护的所有对象都是不同的,任何两个对象间不能相互代替。如本例中,为公司甲创建的FinancialReportManagerA和为公司乙创建的FinancialReportManagerB分别对应了后台各自不同的数据库,因此,两者是不可能相互替代的。

  本例中享元对象接口的实现如下,他用于创建一个报表。即,所有报表生成类将作为享元对象在一个公司(租户)中共享。

public interface IReportManager {
    public String createReport();
}

  以下是两个报表生成的实例,分别对应员工财务收入报表和员工个人信息报表。他们都是具体的享元类。

public class FinancialReportManager implements IReportManager {//财务报表
    protected String tenantId = null;           //租户ID

    public FinancialReportManager(String tenantId) {
        this.tenantId = tenantId;
    }

    @Override
    public String createReport() {
        return "This is a financial report";
    }
}
public class EmployeeReportManager implements IReportManager {//员工报表
    protected String tenantId = null;

    public EmployeeReportManager(String tenantId) {
        this.tenantId = tenantId;           //租户ID
    }

    @Override
    public String createReport() {
        return "This is a employee report";
    }
}

  最为核心的享元工厂类实现如下,它也是享元模式的精髓所在。它确保同一个公司(租户)使用相同的对象产生报表。这是相当有意义的,否则系统可能会为每个员工生成各自的报表对象,导致系统开销激增。

public class ReportManagerFactory {
    Map<String,IReportManager> financialReportManager = new HashMap<String,IReportManager>();
    Map<String,IReportManager> employeeReportManager = new HashMap<String, IReportManager>();
    
    IReportManager getFinancialReportManager(String tenantId) {
        IReportManager  r = financialReportManager.get(tenantId);//通过租户ID获取享元
        if(r == null){
            r = new FinancialReportManager(tenantId);
            financialReportManager.put(tenantId,r);//维护已创建的享元对象
        }
        return r;
    }
    
    IReportManager getEmployeeReportManager(String tenantId){
        IReportManager r = employeeReportManager.get(tenantId);//通过租户ID获取享元
        if(r == null) {
            r = new EmployeeReportManager(tenantId);
            employeeReportManager.put(tenantId,r);//维护已创建的享元对象
        }
        return r;
    }
}

使用享元模式的方法如下:

public static void main(String[] args) {
        ReportManagerFactory rmf = new ReportManagerFactory();
        IReportManager rm = rmf.getFinancialReportManager("A");
        System.out.println(rm.createReport());
}

  ReportManagerFactory作为享元工厂,以租客的ID为索引,维护了一个享元对象的集合,它确保相同的租客的请求都返回同一个享元实例,确保享元对象的有效复用。

读---Java 程序性能优化  葛一鸣

 

原文地址:https://www.cnblogs.com/klyjb/p/11527199.html