YAZONG 我的开源

设计模式(十九)结构型模式(对象结构型模式)享元/蝇绳模式-FLYWEIGHT

 
0 评论0 浏览

2、19享元/蝇绳模式

定义

1)享元模式是池技术的重要实现方式。运用共享对象技术可有效地支持大量的细粒度的对象。享元模式的定义提出了两个要求:细粒度对象与共享对象。

2)要求细粒度对象,那么不可避免地使得对象数量多且性质相近,那么我们就将这些对象的信息分为两个部分:内部状态与外部状态。

2.1)内部状态

内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变,如例子中的id、postAddress等,它们可以作为一个对象的动态附加信息,不必直接储存在具体某个对象中,属于可以共享的部分。

2.2)外部状态

外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态,如例子中的”考试科目+考试地点”复合字符串,它是一批对象的统一标识,是唯一的一个索引值。

3)在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

通用类图

image.png

1)Flyweight抽象享元角色

产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。

2)ConcreteFlyweight具体享元角色

具体的一个产品类,实现抽象角色定义的业务。该角色中需要注意的是内部状态处理应该与环境无关,不应该出现一个操作改变了内部状态,同时修改了外部状态,这是绝对不允许的。

3)unsharedConcreteFlyweight不可共享的享元角色

不存在外部状态或者安全要求(如线程安全)不能够使用共享技术的对象,该对象一般不会出现在享元工厂中。

4)FlyweightFactory享元工厂

职责非常简单,就是构造一个池容器,同时提供从池中获得对象的方法。

通用案例

import java.util.HashMap;

 

/**

 * 场景类

 */

public class Client1 {

    public static void main(String[] args) {

        System.out.println(FlyweightFactory.getFlyweight("test1"));

    }

}

 

/**

 * 抽象享元角色

 * TODO 抽象享元角色一般为抽象类,在实际项目中,一般是一个实现类,它是描述一类事物的方法.

 * TODO 在抽象角色中,一般需要把外部状态和内部状态(当然了,也可以没有内部状态,只有行为也是可以的.)定义出来,避免子类的随意扩展.

 */

abstract class Flyweight{

    //内部状态

    private String intrinsic;

    //外部状态

    //注意这里加上了final关键字,如果无意修改,那么池就混乱了.

    //在程序开发中,确认只需要一次赋值的属性则设置为final类型,避免无意修改导致逻辑混乱,特别是session级的常量或变量.

    protected final String extrinsic;

 

    //要求享元角色必须接受外部状态

    public Flyweight(String extrinsic) {

        this.extrinsic = extrinsic;

    }

 

    //定义业务操作

    abstract void operate();

 

    public String getIntrinsic() {

        return intrinsic;

    }

 

    public void setIntrinsic(String intrinsic) {

        this.intrinsic = intrinsic;

    }

}

 

/**

 * 具体享元角色

 */

class ConcreteFlyweight1 extends Flyweight{

 

    //接受外部状态

    public ConcreteFlyweight1(String extrinsic) {

        super(extrinsic);

    }

    //根据外部状态进行逻辑处理

    @Override

    void operate() {

        //业务逻辑

    }

}

/**

 * 具体享元角色

 */

class ConcreteFlyweight2 extends Flyweight{

 

    //接受外部状态

    public ConcreteFlyweight2(String extrinsic) {

        super(extrinsic);

    }

    //根据外部状态进行逻辑处理

    @Override

    void operate() {

        //业务逻辑

    }

}

 

/**

 * 享元工厂

 * TODO 设计一个共享对象池的功能:

 * TODO (把对象的相同属性提取出来,不同的属性在系统内进行赋值处理,那么就可以建立一个对象池了.)

 * TODO 1)容器定义(定义一个池容器,在这个容器中容纳哪些对象)

 * TODO 2)提供客户端访问的接口(提供一个接口供客户端访问,池中有可用对象时,可以直接从池中获得,否则建立一个新的对象,并放置到池中.)

 */

class FlyweightFactory{

    //定义一个池容器

    private static HashMap<String,Flyweight> pool = new HashMap<>();

    //享元工厂

    public static Flyweight getFlyweight(String extrinsic){

        //需要返回的对象

        Flyweight flyweight;

        //在池中没有该对象

        if(pool.containsKey(extrinsic)){

            flyweight = pool.get(extrinsic);

        }else{

            //根据外部状态创建享元对象

            flyweight = new ConcreteFlyweight1(extrinsic);

            //放置到池中

            pool.put(extrinsic,flyweight);

        }

        return flyweight;

    }

 

}

线程安全问题

类图

image.png

案例

import java.util.HashMap;

/**
 * 线程安全问题场景类
 * TODO 在需要的地方考虑一下线程安全。对象池中的对象尽量多,多到足够满足业务为止
 */
public class Client3 {
    public static void main(String[] args) {
        //在对象池中初始化四个对象
        SignInfoFactory.getSignInfo("科目1");
        SignInfoFactory.getSignInfo("科目2");
        SignInfoFactory.getSignInfo("科目3");
        SignInfoFactory.getSignInfo("科目4");
        //取得对象
        SignInfo signInfo = SignInfoFactory.getSignInfo("科目4");
        while(true){
            signInfo.setId("zhangsan");
            signInfo.setLocation("zhangsan");
            new MultiThread(signInfo).start();
            signInfo.setId("lisi");
            signInfo.setLocation("lisi");
            new MultiThread(signInfo).start();
        }
    }
}
class SignInfoFactory {
    //池容器
    private static HashMap<String,SignInfo> pool = new HashMap<>();
    //从池中获取对象
    public static SignInfo getSignInfo(String key){
        //设置返回对象
        SignInfo result;
        //池中若没有该对象,则建立,并放入池中
        if(!pool.containsKey(key)){
            result = new SignInfo();
            pool.put(key,result);
        }else{
            result = pool.get(key);
        }
        return result;
    }
}

class MultiThread extends Thread{

    private SignInfo signInfo;

    public MultiThread(SignInfo signInfo){
        this.signInfo = signInfo;
    }

    @Override
    public void run() {
        if(!signInfo.getId().equals(signInfo.getLocation())){
            System.out.println("编号:" + signInfo.getId());
            System.out.println("考试地点:" + signInfo.getLocation());
            System.out.println("线程不安全了!");
        }
    }
}
class SignInfo{

    //报名人员的ID
    private String id;
    //考试地点
    private String location;
    //考试科目
    private String subject;
    //邮寄地址
    private String postAddress;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getPostAddress() {
        return postAddress;
    }

    public void setPostAddress(String postAddress) {
        this.postAddress = postAddress;
    }
}

编号:lisi
考试地点:zhangsan
线程不安全了!
编号:zhangsan
考试地点:lisi
线程不安全了!

性能平衡

类图1

image.png

案例1(以ExtrinsicState类作为外部状态)

import java.util.HashMap;

/**
 * 场景类
 * TODO 以ExtrinsicState类作为外部状态
 */
public class Client4 {
    public static void main(String[] args) {
        //初始化对象池
        ExtrinsicState state1 = new ExtrinsicState();
        state1.setSubject("科目1");
        state1.setLocation("上海1");
        SignInfoFactory.getSignInfo(state1);

        ExtrinsicState state2 = new ExtrinsicState();
        state2.setSubject("科目2");
        state2.setLocation("北京2");
        SignInfoFactory.getSignInfo(state2);

        //计算执行100万次需要的时间
        long currentTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            SignInfoFactory.getSignInfo(state2);
        }
        long tailTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (tailTime - currentTime) + "ms");

    }
}

/**
 * 享元工厂
 */
class SignInfoFactory{
    //池容器
    private static HashMap<ExtrinsicState,SignInfo> pool = new HashMap<>();
    //从池中获取对象
    public static SignInfo getSignInfo(ExtrinsicState key){
        //设置返回对象
        SignInfo result;
        //池中没有该对象,则建立,并放入池中。
        if(!pool.containsKey(key)){
            result = new SignInfo();
            result.setState(key);
            pool.put(key,result);
        }else{
            result = pool.get(key);
        }
        return result;
    }
}
/**
 * 外部状态类
 * TODO 如果把一个对象作为MAP类的键值,一定要确保重写了equals和hashCode方法,只有hashCode值相等,并且equals返回结果为true,两个对象才相等。
 * TODO 否则会出现通过键值搜索失败的情况,例如map.get(object)、map.contains(object)等会返回失败的结果。
 */
class ExtrinsicState{

    //考试科目
    private String subject;
    //考试地点
    private String location;

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }


    @Override
    public boolean equals(Object obj) {
        if(obj instanceof ExtrinsicState){
            ExtrinsicState state = (ExtrinsicState)obj;
            return state.getLocation().equals(this.location) && state.getSubject().equals(this.subject);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return this.subject.hashCode() + this.location.hashCode();
    }

}

/**
 * 报考信息
 */
class SignInfo{

    //报名人员的ID
    private String id;
    //外部状态对象
    private ExtrinsicState state;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public ExtrinsicState getState() {
        return state;
    }

    public void setState(ExtrinsicState state) {
        this.state = state;
    }
}

输出结果:
执行时间:236ms

类图2

image.png

案例2-推荐性能高(以java基本类型等(这里String)作为外部状态)

import java.util.HashMap;

/**
 * 场景类
 * TODO 以java基本类型等(这里String)作为外部状态。
 * TODO 外部状态最好以java的基本类型作为标识,可以大幅地提升效率。
 */
public class Client5 {
    public static void main(String[] args) {

        String key1 = "科目1";
        String key2 = "上海2";
        //初始化对象池
        SignInfoFactory.getSignInfo(key1);

        //计算执行100万次需要的时间
        long currentTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            SignInfoFactory.getSignInfo(key2);
        }
        long tailTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (tailTime - currentTime) + "ms");

    }
}

/**
 * 享元工厂
 */
class SignInfoFactory{
    //池容器
    private static HashMap<String,SignInfo> pool = new HashMap<>();
    //从池中获取对象
    public static SignInfo getSignInfo(String key){
        //设置返回对象
        SignInfo result;
        //池中没有该对象,则建立,并放入池中。
        if(!pool.containsKey(key)){
            result = new SignInfo();
            result.setState(key);
            pool.put(key,result);
        }else{
            result = pool.get(key);
        }
        return result;
    }
}
/**
 * 报考信息
 */
class SignInfo{

    //报名人员的ID
    private String id;
    //考试地点
    private String location;
    //考试科目
    private String subject;
    //邮寄地址
    private String postAddress;
    //外部状态对象
    private String state;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getPostAddress() {
        return postAddress;
    }

    public void setPostAddress(String postAddress) {
        this.postAddress = postAddress;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

输出结果:
执行时间:65ms

API中的享元/蝇绳模式

/**

 * 场景类

 * TODO 虽然可以使用享元模式实现对象池,但是享元模式和对象池还有是比较大的区别的.

 * TODO 对象池着重在对象的复用上,池中的每个对象都是可替换的,从同一个池中获得A对象和B对象对客户端来说是完全相同的,它主要解决复用。

 * TODO 享元模式主要解决的是对象的共享问题,如果建立多个可共享的细粒度对象则是其关注的重点.

 */

public class Client6 {

    public static void main(String[] args) {

        String str1 = "和谐";

        String str2 = "社会";

        String str3 = "和谐社会";

        String str4;

        str4 = str1 + str2;

        System.out.println(str3 == str4);

        //String类中的intern()方法,如果是String对象池中有该类型的值,则直接返回对象池中的对象.

        //比如字符串把它的引用指向XX在内存中的共享。

        str4 = (str1 + str2).intern();

        System.out.println(str3 == str4);

    }

}

优点和缺点

1)享元模式是一个非常简单的模式,它可以大大减少应用程序创建的对象,降低程序内存的占用,增加程序的性能,但它同时也提高了系统复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化特性,不应该随内部状态改变而改变,否则导致系统的逻辑混乱。

2)减少运行时对象实例的个数,节省内存。

3)将许多”虚拟”对象的状态集中管理。

4)单个的逻辑实例将无法拥有独立而不同的行为。(个人理解:多个的逻辑实例就可以。足够多的实例在内存中,参考线程安全问题实例)

使用场景

1)系统中存在大量的相似对象。

2)细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。

3)需要缓冲池的场景。

4)类要轻量级,粒度要小。粒度小了,带来的问题就是对象太多,那就用共享技术来解决。

5)如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

6)效果:因为使用了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。

7)使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。

8)当一个类有许多的实例,而这些实例能被同一个方法(比如工厂类等)控制的时候,就可以使用享元模式。


标题:设计模式(十九)结构型模式(对象结构型模式)享元/蝇绳模式-FLYWEIGHT
作者:yazong
地址:https://blog.llyweb.com/articles/2020/08/28/1598547445111.html