项目多也别傻做--享元模式

1.1 项目多也别傻做!

        "如果有100家企业来找你做网站,你难道去申请100个空间,用100个数据库,然后用类似的代码复制100遍,去实现吗?"
        "啊,那如果有Bug或是新的需求改动,维护量就太可怕了。"

package code.chapter26.flyweight1;

import java.util.Hashtable;

public class Test {

    public static void main(String[] args) {

        System.out.println("**********************************************");       
        System.out.println("《大话设计模式》代码样例");
        System.out.println(); 

        WebSite fx = new WebSite("产品展示");
        fx.use();

        WebSite fy = new WebSite("产品展示");
        fy.use();

        WebSite fz = new WebSite("产品展示");
        fz.use();

        WebSite fl = new WebSite("博客");
        fl.use();

        WebSite fm = new WebSite("博客");
        fm.use();

        WebSite fn = new WebSite("博客");
        fn.use();


        System.out.println();
        System.out.println("**********************************************");
    }
}

//网站
class WebSite {
    private String name = "";
    public WebSite(String name) {
        this.name = name;
    }
    public void use() {
        System.out.println("网站分类:" + name);
    }
}




        "对的,也就是说,如果要做三个产品展示,三个博客的网站,就需要六个网站类的实例,而其实它们本质上都是一样的代码,如果网站增多,实例也就随着增多,这对服务器的资源浪费得很严重。你说有什么办法解决这个问题?"
        "我不知道,我想过大家的网站共用一套代码,但毕竟是不同的网站,数据都不相同的。"
        "我就希望你说出共享代码这句话,为什么不可以呢?比如现在大型的博客网站、电子商务网站,里面每一个博客或商家也可以理解为一个小的网站,但它们是如何做的?"
        "啊,我明白了,利用用户ID的不同,来区分不同的用户,具体数据和模板可以不同,但代码核心和数据库却是共享的。"

        项目多也别傻做呀。你想,首先你的这些企业客户,他们需要的网站结构相似度很高,而且都不是那种高访问量的网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,这造成服务器的大量资源浪费,当然更实际的其实就是钞票的浪费,如果整合到一个网站中,共享其相关的代码和数据,那么对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源,而对于代码,由于是一份实例,维护和扩展都更加容易。

1.2 享元模式

        享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。[DP]
享元模式(Flyweight)结构图

package code.chapter26.flyweight0;

import java.util.Hashtable;

public class Test {

    public static void main(String[] args) {

        System.out.println("**********************************************");       
        System.out.println("《大话设计模式》代码样例");
        System.out.println(); 

        int extrinsicstate = 22;

        FlyweightFactory f = new FlyweightFactory();

        Flyweight fx = f.getFlyweight("X");
        fx.operation(--extrinsicstate);

        Flyweight fy = f.getFlyweight("Y");
        fy.operation(--extrinsicstate);

        Flyweight fz = f.getFlyweight("Z");
        fz.operation(--extrinsicstate);

        Flyweight uf = new UnsharedConcreteFlyweight();

        uf.operation(--extrinsicstate);

        System.out.println();
        System.out.println("**********************************************");
    }
}



abstract class Flyweight {
    public abstract void operation(int extrinsicstate);
}

//需要共享的具体Flyweight子类
class ConcreteFlyweight extends Flyweight {
    public void operation(int extrinsicstate){
        System.out.println("具体Flyweight:"+extrinsicstate);
    }
}

//不需要共享的Flyweight子类
class UnsharedConcreteFlyweight extends Flyweight {
    public void operation(int extrinsicstate){
        System.out.println("不共享的具体Flyweight:"+extrinsicstate);
    }
}

//享元工厂
class FlyweightFactory {
    private Hashtable<String,Flyweight> flyweights = new Hashtable<String,Flyweight>();

    public FlyweightFactory(){
        flyweights.put("X", new ConcreteFlyweight());
        flyweights.put("Y", new ConcreteFlyweight());
        flyweights.put("Z", new ConcreteFlyweight());
    }

    public Flyweight getFlyweight(String key) { 
        return (Flyweight)flyweights.get(key);
    }
}


Flyweight类,是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
ConcreteFlyweight是继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间。
UnsharedConcreteFlyweight是指那些不需要共享的Flyweight子类。因为Flyweight接口共享成为可能,但它并不强制共享。
FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)。

        FlyweightFactory根据客户需求返回早已生成好的对象,但一定要事先生成对象实例吗?
        "实际上是不一定需要的,完全可以初始化时什么也不做,到需要时,再去判断对象是否为null来决定是否实例化。"
        "还有个问题,为什么要有UnsharedConcreteFlyweight的存在呢?"
        "这是因为尽管我们大部分时间都需要共享对象来降低内存的损耗,但个别时候也有可能不需要共享,那么此时的UnsharedConcreteFlyweight子类就有存在的必要了,它可以解决那些不需要共享对象的问题。"

1.3 网站共享代码

package code.chapter26.flyweight2;

import java.util.Hashtable;

public class Test {

    public static void main(String[] args) {

        System.out.println("**********************************************");       
        System.out.println("《大话设计模式》代码样例");
        System.out.println(); 

        WebSiteFactory f = new WebSiteFactory();

        WebSite fx = f.getWebSiteCategory("产品展示");  
        fx.use();

        WebSite fy = f.getWebSiteCategory("产品展示");
        fy.use();

        WebSite fz = f.getWebSiteCategory("产品展示");
        fz.use();

        WebSite fl = f.getWebSiteCategory("博客");
        fl.use();

        WebSite fm = f.getWebSiteCategory("博客");
        fm.use();

        WebSite fn = f.getWebSiteCategory("博客");
        fn.use();

        System.out.println("网站分类总数为:"+f.getWebSiteCount()); //统计实例化个数,结果应该为2

        System.out.println();
        System.out.println("**********************************************");
    }
}

//抽象的网站类
abstract class WebSite{
    public abstract void use();
}

//具体网站类
class ConcreteWebSite extends WebSite {
    private String name = "";
    public ConcreteWebSite(String name) {
        this.name = name;
    }
    public void use() {
        System.out.println("网站分类:" + name);
    }
}

//网站工厂
class WebSiteFactory {
    private Hashtable<String,WebSite> flyweights = new Hashtable<String,WebSite>();

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

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

        "这样写算是基本实现了享元模式的共享对象的目的,也就是说,不管建几个网站,只要是'产品展示',都是一样的,只要是'博客',也是完全相同的,但这样是有问题的,你给企业建的网站不是一家企业的,它们的数据不会相同,所以至少它们都应该有不同的账号,你怎么办?"
        "啊,对的,实际上我这样写没有体现对象间的不同,只体现了它们共享的部分。"

1.4 内部状态与外部状态

        "在享元对象内部并且不会随环境改变而改变的共享部分,可以称为享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式Flyweight执行时所需的状态有内部的也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。"
        "那你的意思是说,客户的账号就是外部状态,应该由专门的对象来处理。"

代码结构图

package code.chapter26.flyweight3;

import java.util.Hashtable;

public class Test {

    public static void main(String[] args) {

        System.out.println("**********************************************");       
        System.out.println("《大话设计模式》代码样例");
        System.out.println(); 

        WebSiteFactory f = new WebSiteFactory();

        WebSite fx = f.getWebSiteCategory("产品展示");
        fx.use(new User("小菜"));
        
        WebSite fy = f.getWebSiteCategory("产品展示");
        fy.use(new User("大鸟"));

        WebSite fz = f.getWebSiteCategory("产品展示");
        fz.use(new User("娇娇"));

        WebSite fl = f.getWebSiteCategory("博客");
        fl.use(new User("老顽童"));

        WebSite fm = f.getWebSiteCategory("博客");
        fm.use(new User("桃谷六仙"));

        WebSite fn = f.getWebSiteCategory("博客");
        fn.use(new User("南海鳄神"));

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

        System.out.println();

        // String titleA = new String("大话设计模式");
        // String titleB = new String("大话设计模式");

        // System.out.println(" titleA==titleB:          "+(titleA == titleB));        //比较内存引用地址
        // System.out.println(" titleA.equals(titleB):   "+(titleA.equals(titleB)));   //比较字符串的值

        // String titleC = "大话设计模式";
        // String titleD = "大话设计模式";

        // System.out.println(" titleC==titleD:          "+(titleC == titleD));        //比较内存引用地址
        // System.out.println(" titleC.equals(titleD):   "+(titleC.equals(titleD)));   //比较字符串的值

        System.out.println();
        System.out.println("**********************************************");
    }
}

//用户
class User{
    private String name;
    public User(String value){
        this.name=value;
    }

    public String getName(){
        return this.name;
    }
}

//抽象的网站类
abstract class WebSite{

    public abstract void use(User user);
}

//具体网站类
class ConcreteWebSite extends WebSite {
    private String name = "";
    public ConcreteWebSite(String name) {
        this.name = name;
    }
    public void use(User user) {
        System.out.println("网站分类:" + name+" 用户:"+user.getName());
    }
}

//网站工厂
class WebSiteFactory {
    private Hashtable<String,WebSite> flyweights = new Hashtable<String,WebSite>();

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

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


        这样就可以协调内部与外部状态了。由于用了享元模式,哪怕你接手了1000个网站的需求,只要要求相同或类似,你的实际开发代码也就是分类的那几种,对于服务器来说,占用的硬盘空间、内存、CPU资源都是非常少的,这确实是很好的一个方式。

1.5 享元模式应用

        如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以是外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
        "在实际使用中,享元模式到底能达到什么效果呢?"
        "因为用了享元模式,所以有了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。"
        "能具体一些吗?有些什么情况是用到享元模式的?"
        "哈,实际上在Java中,字符串String就是运用了Flyweight模式。举个例子吧。'=='可以用来确定titleA与titleB是否是相同的实例,返回值为boolean值。当用new String()方法时,两个对象titleA和titleB的引用地址是不相同的,但当titleC和titleD都使用赋值的方式时,两个字符串的引用地址竟然是相同的。"


        "啊,返回值竟然是True,titleC和titleD这两个字符串是相同的实例。"
        "试想一下,如果每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存的开销会很大。所以如果第一次创建了字符串对象titleC,下次再创建相同的字符串titleD时只是把它的引用指向'大话设计模式',这样就实现了'大话设计模式'在内存中的共享。"
        "哦,原来我一直在使用享元模式呀,我以前都不知道。还有没有其他现实中的应用呢?"
"虽说享元模式更多的时候是一种底层的设计模式,但现实中也是有应用的。比如说休闲游戏开发中,像围棋、五子棋、跳棋等,它们都有大量的棋子对象,你分析一下,它们的内部状态和外部状态各是什么?"
        "围棋和五子棋只有黑白两色、跳棋颜色略多一些,但也是不太变化的,所以颜色应该是棋子的内部状态,而各个棋子之间的差别主要就是位置的不同,所以方位坐标应该是棋子的外部状态。"
        "对的,像围棋,一盘棋理论上有361个空位可以放棋子,那如果用常规的面向对象方式编程,每盘棋都可能有两三百个棋子对象产生,一台服务器就很难支持更多的玩家玩围棋游戏了,毕竟内存空间还是有限的。如果用了享元模式来处理棋子,那么棋子对象可以减少到只有两个实例,结果……你应该明白的。"


        "太了不起了,这的确是非常好地解决了对象的开销问题。"
        "在某些情况下,对象的数量可能会太多,从而导致了运行时的资源与性能损耗。那么我们如何去避免大量细粒度的对象,同时又不影响客户程序,是一个值得去思考的问题,享元模式,可以运用共享技术有效地支持大量细粒度的对象。不过,你也别高兴得太早,使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。"
        "哦,明白了,像我给人家做网站,如果就两三个人的个人博客,其实是没有必要考虑太多的。但如果是要开发一个可供多人注册的博客网站,那么用共享代码的方式是一个非常好的选择。"

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值