设计模式十一——享元模式

1.享元模式概念

1.1定义

享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

  1. 享元模式(Flyweight Pattern)也叫绳量模式:运用共享技术有效地支持大量细粒度的对象
  2. 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的,就可以直接拿来使用,避免重复创建,如果没有需要的就创建一个。
  3. 享元模式能够解决重复对象的内存浪费问题,当系统中有大量的相似对象,需要缓冲池时,不需要总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
  4. 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。

1.2内部状态与外部状态

享元模式以共享的方式高效地支持大量细粒度对象的重用,能做到共享的关键是区分了内部状态以及外部状态。

  • 内部状态:存储在享元对象内部并且不会随环境改变而改变,内部状态可以共享,例如字符的内容,字符a永远是字符a,不会变为字符b
  • 外部状态:能够随环境改变而改变,不可以共享的状态,通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。外部状态之间通常是相互独立的,比如字符的颜色,字号,字体等,可以独立变化,没有影响,客户端在使用时将外部状态注入到享元对象中
    正因为区分了内部状态以及外部状态,可以将具有相同内部状态的对象存储在享元池中,享元池的对象是可以实现共享的,需要的时候从中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象实际上只存储一份。

1.3原理类图

在这里插入图片描述
角色说明:

  • Flyweights(抽象享元类):通常是一个接口或者抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)
  • ConcreteFlyweight(具体享元类):实现/继承抽象共享类,实例称为共享对象,在具体享元类中为内部状态提供了存储空间,通常可以结合单例模式来设计具体享元类
  • UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元子类都需要被共享,不能被共享的子类可设计为非共享具体享元类,当需要一个非具体享元对象时可以直接实例化创建
  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,针对抽象享元类编程,将具体享元对象存储于享元池中。一般使用键值对集合(比如Java中的HashMap)作为享元池,当客户端获取享元对象时,首先判断是否存在,存在则从集合中取出并返回,不存在则创建新具体享元的实例,存储于享元池中并返回新实例

1.4分类

  • 单纯享元模式:在单纯享元模式中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。
  • 复合享元模式:将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享

1.5.典型实现

在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
典型的享元工厂类的代码如下:

class FlyweightFactory {
    //定义一个HashMap用于存储享元对象,实现享元池
    private HashMap<String,Flyweight> flyweights = new HashMap<>();
    public static Flyweight getFlyweight(String key){
        //如果对象存在,则直接从享元池获取
        if(flyweights.containsKey(key)){
            return(Flyweight)flyweights.get(key);
        }
        //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
        else {
            Flyweight fw = newConcreteFlyweight();
            flyweights.put(key,fw);
            return fw;
        }
    }
}

享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。
典型的享元类代码如下所示:

 1 public abstract class Flyweight {
 2 
 3     //内部状态
 4     public String intrinsic;
 5     //外部状态
 6     protected final String extrinsic;
 7     
 8     //要求享元角色必须接受外部状态
 9     public Flyweight(String extrinsic) {
10         this.extrinsic = extrinsic;
11     }
12     
13     //定义业务操作
14     public abstract void operate(int extrinsic);
15 
16     public String getIntrinsic() {
17         return intrinsic;
18     }
19 
20     public void setIntrinsic(String intrinsic) {
21         this.intrinsic = intrinsic;
22     }
23 
24 }

ConcreteFlyweight类继承Flyweight超类或实现Flyweight接口,并为其内部状态增加存储空间。

 1 public class ConcreteFlyweight extends Flyweight {
 2 
 3     //接受外部状态
 4     public ConcreteFlyweight(String extrinsic) {
 5         super(extrinsic);
 6     }
 7 
 8     //根据外部状态进行逻辑处理
 9     @Override
10     public void operate(int extrinsic) {
11         System.out.println("具体Flyweight:" + extrinsic);
12     }
13 
14 }

UnsharedConcreteFlyweight类:指那些不需要共享的Flyweight子类。

 1 public class UnsharedConcreteFlyweight extends Flyweight {
 2 
 3     public UnsharedConcreteFlyweight(String extrinsic) {
 4         super(extrinsic);
 5     }
 6 
 7     @Override
 8     public void operate(int extrinsic) {
 9         System.out.println("不共享的具体Flyweight:" + extrinsic);
10     }
11 
12 }

Client客户端

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         int extrinsic = 22;
 5         
 6         Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
 7         flyweightX.operate(++ extrinsic);
 8         
 9         Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
10         flyweightY.operate(++ extrinsic);
11         
12         Flyweight flyweightZ = FlyweightFactory.getFlyweight("Z");
13         flyweightZ.operate(++ extrinsic);
14         
15         Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
16         flyweightReX.operate(++ extrinsic);
17         
18         Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
19         unsharedFlyweight.operate(++ extrinsic);
20     }
21     
22 }

2.享元模式的实现

比如接了我一个小型的外包项目,是做一个产品展示网站,后来他的朋友们也希望做这样的网站,但要求都有些不同,我们当然不能直接复制粘贴再来一份,有任希望是新闻发布形式的,有人希望是博客形式的等等,而且因为经费原因不能每个网站租用一个空间。

其实这里他们需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,这是造成服务器的大量资源浪费。如果整合到一个网站中,共享其相关的代码和数据,那么对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源;而对于代码,由于是一份实例,维护和扩展都更加容易。
那么此时就可以用到享元模式了。类图如下:
在这里插入图片描述
网站抽象类

public abstract class WebSite {
     public abstract void user(User user);
}

外部状态

public class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

具体网站类

//具体网站
public class ConcreteWebSite extends WebSite{

    //共享的部分   内部状态
    private String type="";  //发布的类型

    public ConcreteWebSite(String type) {
        this.type = type;
    }

    @Override
    public void user(User user) {
        System.out.println("网站的发布形式为:"+type+",使用者是:"+user.getName());
    }
}

网站工厂类

//网站工厂类
public class WebSiteFactory {

    //集合,充当池的作用
    private HashMap<String,ConcreteWebSite> pool=new HashMap<>();
    //根据网站的类型,返回一个网站,如果没有就创建一个网站,放入池中并返回
    public WebSite getWebSiteCategory(String type){
        if(!pool.containsKey(type)){
            pool.put(type,new ConcreteWebSite(type));
        }
        return (WebSite)pool.get(type);
    }
    public void getPoolCount(){
        System.out.println(pool.size());
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        //创建一个工厂
        WebSiteFactory factory=new WebSiteFactory();
        WebSite webSite1=factory.getWebSiteCategory("新闻");
        webSite1.user(new User("tom"));

        WebSite webSite2=factory.getWebSiteCategory("博客");
        webSite2.user(new User("king"));

        WebSite webSite3=factory.getWebSiteCategory("微信");
        webSite3.user(new User("lisi"));

        WebSite webSite4=factory.getWebSiteCategory("掘金");
        webSite4.user(new User("zhangsan"));

        WebSite webSite5=factory.getWebSiteCategory("掘金");
        webSite5.user(new User("wangwu"));

        WebSite webSite6=factory.getWebSiteCategory("掘金");
        webSite6.user(new User("wangwu"));

        factory.getPoolCount();
    }
}

运行结果
在这里插入图片描述

3.享元模式的优缺点和适用场景

主要优点

  • 降低内存消耗:享元模式可以极大地减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而节约系统资源,提供系统性能
  • 外部状态独立:享元模式外部状态相对独立,不会影响到内部状态,从而使得享元对象可以在不同环境中被共享

主要缺点

  • 增加复杂度:享元模式使得系统变复杂,需要分离出内部状态以及外部状态,使得程序逻辑复杂化
  • 运行时间变长:为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态使得运行时间变长

适用场景

  • 一个系统有大量相似或相同对象,造成大量内存浪费
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
  • 由于需要维护享元池,造成一定的资源开销,因此在需要真正多次重复使用享元对象时才值得使用享元模式

设计模式学习笔记(十四):享元模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值