魔鬼还是天使的博客

魔鬼还是天使的博客

「补课」进行时:设计模式(21)——享元模式-LMLPHP

1. 前文汇总

「补课」进行时:设计模式系列

2. 享元模式

2.1 定义

享元模式(Flyweight Pattern)很简单,它解决的需求也很直接,同时它也是池技术的重要实现方式,先看下它的定义:

Use sharing to support large numbers of fine-grained objects efficiently.(使用共享对象可有效地支持大量的细粒度的对象。)

2.2 通用类图

「补课」进行时:设计模式(21)——享元模式-LMLPHP

  • Flyweight 抽象享元角色:它是一个产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现。
  • ConcreteFlyweight 具体享元角色:具体的一个产品类, 实现抽象角色定义的业务。
  • unsharedConcreteFlyweight 不可共享的享元角色:不存在外部状态或者安全要求(如线程安全) 不能够使用共享技术的对象, 该对象一般不会出现在享元工厂中。
  • FlyweightFactory 享元工厂:它的职责非常简单, 就是构造一个池容器, 同时提供从池中获得对象的方法。

2.3 通用代码

抽象享元角色:

public abstract class Flyweight {
    // 内部状态
    private String intrinsic;
    // 外部状态
    protected final String extrinsic;
    // 要求享元角色必须接受外部状态
    protected Flyweight(String extrinsic) {
        this.extrinsic = extrinsic;
    }
    // 定义业务操作
    abstract void operate();

    public String getIntrinsic() {
        return intrinsic;
    }

    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }
}

具体享元角色:

public class ConcreteFlyweight1 extends Flyweight{
    protected ConcreteFlyweight1(String extrinsic) {
        super(extrinsic);
    }

    @Override
    void operate() {

    }
}

public class ConcreteFlyweight2 extends Flyweight{
    protected ConcreteFlyweight2(String extrinsic) {
        super(extrinsic);
    }

    @Override
    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);
        } else {
            // 根据外部状态创建享元对象
            flyweight = new ConcreteFlyweight1(Extrinsic);
            // 放置到池中
            pool.put(Extrinsic, flyweight);
        }
        return flyweight;
    }
}

2.4 优缺点

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

3. 一个小例子

享元模式很简单,上面的通用代码其实就是一个很好的示例,类似于 Java 中的 String 常量池,没有的对象创建后存在池中,若池中存在该对象则直接从池中取出。

我这里还是再举一个简单的例子,比如接了我一个小型的外包项目,是做一个产品展示网站,后来他的朋友们也希望做这样的网站,但要求都有些不同,我们当然不能直接复制粘贴再来一份,有人希望是视频站,有人希望是图文站等等,而且因为经费原因不能每个网站租用一个空间。

这种事情在生活中很长见,不过大多数情况都是直接 copy 一份代码,再做做改动,但是在享元模式中,就不存在这种情况啦~~~

网站抽象类:

public abstract class WebSite {
    abstract void use();
}

具体网站类:

public class ConcreteWebSite extends WebSite {

    private String name;

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

    @Override
    void use() {
        System.out.println("网站分类:" + name);
    }
}

网络工厂类:

public class WebSiteFactory {
    private HashMap<String, WebSite> pool = new HashMap<>();

    //获得网站分类
    public WebSite getWebSiteCategory(String key) {
        if(!pool.containsKey(key)) {
            pool.put(key, new ConcreteWebSite(key));
        }
        return pool.get(key);
    }

    //获得网站分类总数
    public int getWebSiteCount() {
        return pool.size();
    }
}

Client 客户端:

public class Client {
    public static void main(String[] args) {
        WebSiteFactory factory = new WebSiteFactory();

        WebSite fx = factory.getWebSiteCategory("视频站");
        fx.use();

        WebSite fy = factory.getWebSiteCategory("视频站");
        fy.use();

        WebSite fz = factory.getWebSiteCategory("视频站");
        fz.use();

        WebSite fa = factory.getWebSiteCategory("图文站");
        fa.use();

        WebSite fb = factory.getWebSiteCategory("图文站");
        fb.use();

        WebSite fc = factory.getWebSiteCategory("图文站");
        fc.use();

        System.out.println("网站分类总数为:" + factory.getWebSiteCount());
    }
}

执行结果:

网站分类:视频站
网站分类:视频站
网站分类:视频站
网站分类:图文站
网站分类:图文站
网站分类:图文站
网站分类总数为:2

可以看出,虽然我们做了 6 个网站,但网站分类只有 2 个。

这样基本算是实现了享元模式的共享对象的目的,但是这里实际上没有体现对象间的不同。

我们再加入一个用户类:

public class User {
    private String name;

    public String getName() {
        return name;
    }

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

然后对 WebSiteConcreteWebSiteuse() 方法进行修改,添加 User 参数:

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

public class ConcreteWebSite extends WebSite {

    private String name;

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

    @Override
    void use(User user) {
        System.out.println("网站分类:" + name + " 用户:" + user.getName());
    }
}

最后修改一下 Client 类:

public class Client {
    public static void main(String[] args) {
        WebSiteFactory factory = new WebSiteFactory();

        WebSite fx = factory.getWebSiteCategory("视频站");
        fx.use(new User("tom"));

        WebSite fy = factory.getWebSiteCategory("视频站");
        fy.use(new User("cat"));

        WebSite fz = factory.getWebSiteCategory("视频站");
        fz.use(new User("nginx"));

        WebSite fa = factory.getWebSiteCategory("图文站");
        fa.use(new User("apache"));

        WebSite fb = factory.getWebSiteCategory("图文站");
        fb.use(new User("netty"));

        WebSite fc = factory.getWebSiteCategory("图文站");
        fc.use(new User("jboss"));

        System.out.println("网站分类总数为:" + factory.getWebSiteCount());
    }
}

最终结果:

网站分类:视频站 用户:tom
网站分类:视频站 用户:cat
网站分类:视频站 用户:nginx
网站分类:图文站 用户:apache
网站分类:图文站 用户:netty
网站分类:图文站 用户:jboss
网站分类总数为:2

这样就可以协调内部与外部状态,哪怕接手了上千个网站的需求,只要要求相同或类似,实际开发代码也就是分类的那几种。

12-24 14:45