20、FlyWeight 享元模式

时间:2022-08-26 14:56:50

池化的思想

1、Flyweight享元模式

运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来说,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。

意图:运用共享技术有效地支持大量细粒度的对象。

主要解决:在有大量对象时,有可能会造成内存溢出,我们把其*同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用: 1、系统中有大量对象。2、这些对象消耗大量内存。3、这些对象的状态大部分可以外部化。4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。5、系统不依赖于这些对象身份,这些对象是不可分辨的。

使用场景: 1、系统有大量相似对象。2、需要缓冲池的场景。

2、示例代码

使用享元模式来设计围棋软件中的棋子

IgoChessman充当抽象享元类,BlackIgoChessman和WhiteIgoChessman充当具体享元类,IgoChessmanFactory充当享元工厂类。完整代码如下所示:

 import java.util.*;  
 
 //围棋棋子类:抽象享元类  
 abstract class IgoChessman {  
     public abstract String getColor();  
 
     public void display() {  
         System.out.println("棋子颜色:" + this.getColor());    
    }  
 }  
 
 //黑色棋子类:具体享元类  
 class BlackIgoChessman extends IgoChessman {  
     public String getColor() {  
         return "黑色";  
    }    
 }  
 
 //白色棋子类:具体享元类  
 class WhiteIgoChessman extends IgoChessman {  
     public String getColor() {  
         return "白色";  
    }  
 }  
 
 //围棋棋子工厂类:享元工厂类,使用单例模式进行设计  
 class IgoChessmanFactory {   
    private static IgoChessmanFactory instance = new IgoChessmanFactory();   
    private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池         private IgoChessmanFactory() {   
        ht = new Hashtable();   
        IgoChessman black,white;   
        black = new BlackIgoChessman();   
        ht.put("b",black);   
        white = new WhiteIgoChessman();   
        ht.put("w",white);   
   }         //返回享元工厂类的唯一实例   
    public static IgoChessmanFactory getInstance() {   
        return instance;   
   }         //通过key来获取存储在Hashtable中的享元对象   
    public static IgoChessman getIgoChessman(String color) {   
        return (IgoChessman)ht.get(color);     
   }   
}

编写如下客户端测试代码:

 class Client {  
     public static void main(String args[]) {  
         IgoChessman black1,black2,black3,white1,white2;  
         IgoChessmanFactory factory;  
 
         //获取享元工厂对象  
         factory = IgoChessmanFactory.getInstance();  
 
         //通过享元工厂获取三颗黑子  
         black1 = factory.getIgoChessman("b");  
         black2 = factory.getIgoChessman("b");  
         black3 = factory.getIgoChessman("b");  
         System.out.println("判断两颗黑子是否相同:" + (black1==black2));  
 
         //通过享元工厂获取两颗白子  
         white1 = factory.getIgoChessman("w");   
        white2 = factory.getIgoChessman("w");   
        System.out.println("判断两颗白子是否相同:" + (white1==white2));             //显示棋子   
        black1.display();   
        black2.display();   
        black3.display();   
        white1.display();   
        white2.display();   
   }   
}

编译并运行程序,输出结果如下:

 判断两颗黑子是否相同:true
 判断两颗白子是否相同:true
 棋子颜色:黑色
 棋子颜色:黑色
 棋子颜色:黑色
 棋子颜色:白色
 棋子颜色:白色

从输出结果可以看出,虽然我们获取了三个黑子对象和两个白子对象,但是它们的内存地址相同,也就是说,它们实际上是同一个对象。在实现享元工厂类时我们使用了单例模式和简单工厂模式,确保了享元工厂对象的唯一性,并提供工厂方法来向客户端返回享元对象。

引入外部状态

发现虽然黑色棋子和白色棋子可以共享,但是它们将显示在棋盘的不同位置,如何让相同的黑子或者白子能够多次重复显示且位于一个棋盘的不同地方?解决方法就是将棋子的位置定义为棋子的一个外部状态,在需要时再进行设置。

除了增加一个坐标类Coordinates以外,抽象享元类IgoChessman中的display()方法也将对应增加一个Coordinates类型的参数,用于在显示棋子时指定其坐标,Coordinates类和修改之后的IgoChessman类的代码如下所示:

 //坐标类:外部状态类  
 class Coordinates {  
     private int x;  
     private int y;  
 
     public Coordinates(int x,int y) {  
         this.x = x;  
         this.y = y;  
    }  
 
     public int getX() {  
         return this.x;  
    }  
 
     public void setX(int x) {  
         this.x = x;  
    }  
 
     public int getY() {  
         return this.y;  
    }  
 
     public void setY(int y) {  
         this.y = y;  
    }  
 }  
 
 //围棋棋子类:抽象享元类  
 abstract class IgoChessman {  
     public abstract String getColor();  
 
     public void display(Coordinates coord){   
        System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "," + coord.getY() );     
   }   
}

客户端测试代码修改如下:

 class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  
 
        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  
 
        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  
 
        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");   
       white2 = factory.getIgoChessman("w");   
       System.out.println("判断两颗白子是否相同:" + (white1==white2));            //显示棋子,同时设置棋子的坐标位置   
       black1.display(new Coordinates(1,2));   
       black2.display(new Coordinates(3,4));   
       black3.display(new Coordinates(1,3));   
       white1.display(new Coordinates(2,5));   
       white2.display(new Coordinates(2,4));   
   }   
}

编译并运行程序,输出结果如下:

 判断两颗黑子是否相同:true
 判断两颗白子是否相同:true
 棋子颜色:黑色,棋子位置:1,2
 棋子颜色:黑色,棋子位置:3,4
 棋子颜色:黑色,棋子位置:1,3
 棋子颜色:白色,棋子位置:2,5
 棋子颜色:白色,棋子位置:2,4

从输出结果可以看到,在每次调用display()方法时,都设置了不同的外部状态——坐标值,因此相同的棋子对象虽然具有相同的颜色,但是它们的坐标值不同,将显示在棋盘的不同位置。

3、flyweight类图

20、FlyWeight 享元模式

登场角色

◆Flyweight (轻量级)按照通常方式编写程序会导致程序变重,所以如臬能够共享实例会比较好,而Flyweight角色表示的就是那些实例会被共享的类。在示例程序中,由BigChar类扮演此角色。

◆FlyweightFactory (轻量级工厂)FlyweightFactory角色是生成Flyweight角色的工厂。在工厂中生成Flyweight角色可以实现共享实例。在示例程序中,由BigCharFactory类扮演此角色。

◆Client (请求者)Client角色使用FlyweightFactory角色来生成Flyweight角色。在示例程序中,由BigString类扮演此角色。

4、小结

当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。相比其他结构型设计模式,享元模式的使用频率并不算太高,但是作为一种以“节约内存,提高性能”为出发点的设计模式,它在软件开发中还是得到了一定程度的应用。

适用场景

(1) 一个系统有大量相同或者相似的对象,造成内存的大量耗费。

(2) 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

(3) 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

在以下情况下可以考虑使用享元模式:

(1) 一个系统有大量相同或者相似的对象,造成内存的大量耗费。

(2) 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

(3) 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

公众号发哥讲

这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。

20、FlyWeight 享元模式

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈~

● 扫码关注我们

据说看到好文章不推荐的人,服务器容易宕机!

本文版权归发哥讲博客园共有,原创文章,未经允许不得转载,否则保留追究法律责任的权利。