一 定义
享元模式是池技术的重要实现。
定义:使用共享对象可有效地支持大量的细粒度的对象。
享元模式的定义为我们提出了两个要求,细粒度的对象和共享对象。我们知道创建过多的对象分配到内存中很容易造成内存溢出,享元模式就可以尽可能地减少内存的使用量,比较适用于可能存在大量重复对象的场景,通过共享技术,避免创建过多的对象。
先来了解两个概念:内部状态和外部状态
-
内部状态
内部状态是对象可以共享出来的信息,存储在享元对象内部并且不会随着环境改变而改变。 -
外部状态
外部状态是对象得以依赖的一个标记,是随着环境改变而改变的,不可以共享的状态。
二 模式结构
角色介绍:
-
Flyweight:抽象享元角色
享元对象的抽象基类或者接口。 -
ConcreteFlyweight:具体享元角色
具体的一个产品类,实现抽象角色定义的业务。 -
FlyweightFactory:享元工厂
负责管理享元对象池和创建享元对象。
三 实例
- 抽象享元角色,一般为抽象类。在抽象角色中,一般需要把外部状态和内部状态定义出来,当然,如果没有内部状态,也是可以的。
内部状态是共享出来的,不随环境改变而改变。
外部状态是对象作为依赖的标记。
public abstract class Flyweight {
// 内部状态
private String intrinsic;
// 外部状态
protected final String Extrinsic;
// 要去享元角色必须接收外部状态
public Flyweight(String extrinsic){
this.Extrinsic=extrinsic;
}
// 定义业务操作
public abstract void operate();
// 内部状态的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
- 具体的享元角色
实现自己的业务逻辑,然后接收外部状态,以便内部业务逻辑对外部状态的依赖。
public class ConcreteFlyweight extends Flyweight{
// 接受外部状态
public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}
// 根据外部状态进行逻辑处理
@Override
public void operate() {
// 业务处理
}
}
- 不需要共享的Flyweight子类
public class UnsharedConcreteFlyweight extends Flyweight{
public UnsharedConcreteFlyweight(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
}
}
- 享元工厂
public class FlyweightFactory {
// 定义一个池容器
private static HashMap<String, Flyweight> pool = new HashMap<>();
// 享元工厂
public static Flyweight getFlyweight(String Extrinsic) {
// 需要返回的对象
Flyweight flyweight=null;
// 在池中没有该对象
if (pool.containsKey(Extrinsic)){
flyweight=pool.get(Extrinsic);
System.out.print("已有 " + Extrinsic + " 直接从池中取---->\n");
}else {
// 根据外部状态创建享元对象
flyweight=new ConcreteFlyweight(Extrinsic);
// 放置到池中
pool.put(Extrinsic,flyweight);
System.out.print("创建 " + Extrinsic + " 并从池中取出---->\n");
}
return flyweight;
}
}
- 测试代码
Flyweight flyweightX = FlyweightFactory.getFlyweight("X");
Flyweight flyweightY = FlyweightFactory.getFlyweight("Y");
Flyweight flyweightZ = FlyweightFactory.getFlyweight("Z");
Flyweight flyweightReX = FlyweightFactory.getFlyweight("X");
Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
-
运行结果
我们以过年回家买火车票为例,过年回家买火车票是件痛苦的事情,很多人都用书票软件向服务端发出请求,对于每个请求,服务端必须做出应答。在用户设置了出发地和目的地后,每次请求都要返回查询结果,当无数的人请求时,如果每次都需要重新创建一个查询结果,那么必然造成大量重复对象的创建,销毁等,使得内存占用率高居不下,所以这个问题可以通过享元模式得到很好的解决。
对于一列火车来说,我们拿高铁举例,
不变的状态比如高铁的名字都是复兴号。
变化的状态是出发城市和终止城市。
- 创建抽象享元对象
我们把火车的始发城市和终止城市作为外部状态,然后定义业务逻辑,根据座位等级,显示车票票价。
public abstract class Ticket {
// 内部状态
private String name="和谐号";
// 外部状态
protected String from;
protected String to;
public Ticket(String from,String to){
this.from=from;
this.to=to;
}
// 定义业务逻辑,根据座位等级,显示价格
public abstract void showTicketInfo(String seat);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
- 具体的享元角色
public class TrainTicket extends Ticket{
public TrainTicket(String from, String to) {
super(from, to);
}
@Override
public void showTicketInfo(String seat) {
if (from.equals("北京") && to.equals("青岛")){
switch (seat){
case "business":
System.out.println(from+"到"+to+getName()+"列车:商务座的价格是988元!");
break;
case "one":
System.out.println(from+"到"+to+getName()+"列车:一等座的价格是689元!");
break;
case "two":
System.out.println(from+"到"+to+getName()+"列车:二等座的价格是298元!");
break;
}
}else if (from.equals("北京") && to.equals("济南")){
switch (seat){
case "business":
System.out.println(from+"到"+to+getName()+"列车:商务座的价格是598元!");
break;
case "one":
System.out.println(from+"到"+to+getName()+"列车:一等座的价格是389元!");
break;
case "two":
System.out.println(from+"到"+to+getName()+"列车:二等座的价格是186元!");
break;
}
}
}
}
- 享元工厂
我们以 "出发地-目的地" 为键,存储出票信息的对象,这个是对象得以依赖的标记,就这样,如果缓存中没有这个对象,则创建这个对象,并将对象加入到缓存中,如果有则直接从缓存中取出。
public class TicketFactory {
// 定义一个池容器
private static Map<String,Ticket> pool=new HashMap<>();
// 享元工厂
public static Ticket getTicket(String from,String to){
String key=from+"-"+to;
if (pool.containsKey(key)){
System.out.println("使用缓存==>"+key);
return pool.get(key);
}else {
System.out.println("创建对象==>"+key);
Ticket ticket=new TrainTicket(from,to);
pool.put(key, ticket);
return ticket;
}
}
}
- 测试代码
Ticket ticket01=TicketFactory.getTicket("北京","青岛");
ticket01.showTicketInfo("business");
Ticket ticket02=TicketFactory.getTicket("北京","青岛");
ticket02.showTicketInfo("one");
Ticket ticket03=TicketFactory.getTicket("北京","青岛");
ticket03.showTicketInfo("two");
Ticket ticket04=TicketFactory.getTicket("北京","济南");
ticket04.showTicketInfo("two");
-
运行结果
从运行结果可以看出,除了第一次是创建的对象以外,其它的都是使用缓存的对象。
四 优缺点
- 优点:享元模式可以大大减少应用程序创建的对象,降低应用程序内存的占用,增强程序的性能
- 缺点:享元模式提高了系统的复杂性,需要分离出外部状态和内部状态,而且外部状态具有固化效果,导致系统的逻辑混乱。
五 使用场景
- 系统中存在大量的相似对象。
- 细粒度的对象都具备较接近的外部状态,而且内部状态和环境无关。
- 需要缓冲池的场景。
网友评论