【设计模式】享元模式和委派模式

享元模式

享元模式的意图是复用对象(对象的不可变部分),节省内存。属于结构型模式

当系统需要在多个地方使用相同的信息(对象)的时候,可以将这个对象缓存起来,这样就可以避免创建多个相同的对象,节省内存

享元对象其实可以看做是工厂模式的一种改进,它也会创建多个对象,只是会将这些对象都缓存起来,便于复用.

其代码实现⾮常简单,主要是通过⼯⼚模式,在⼯⼚类中,通过⼀个 Map 或者 List来缓存已经创建好的享元对象,以达到复⽤的⽬的。之前工厂模式种的工厂方法模式就用到了享元模式,这里就不再赘述【设计模式】工厂模式

JDK源码中的享元模式

String中的享元模式

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = "he" + "llo";
        String s4 = "he" + new String("llo");
        String s5 = new String("hello");
        String s6 = s4.intern();
        String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8;
        System.out.println(s1 == s2);//true
        System.out.println(s1 == s3);//true
        System.out.println(s1 == s4);//false
        System.out.println(s1 == s6);//true
        System.out.println(s1 == s9);//false
        System.out.println(s4 == s5);//false

    }

String类是以final修饰的,JVM中的字符串一般保存在字符串常量池中,java会确保同一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0之前是位于永久代的,从JDK7.0中,将其从永久代取出放到了堆中。

当以字面量的形式创建String变量时,JVM会在编译期间把该字面量"hello"放到字符串常量池中,在java程序启动的时候就会加载到内存中。如果有其他相同的字面量,JVM就会返回这个字面量的引用

s3中的字面量拼接后就是"hello",JVM在编译期间就会对其进行优化,所以s1和s3的引用是一个。
s4中new String(“llo”)生成了两个对象,"llo"和new String(“llo”), 一个存在于常量池,一个存在于堆中,所以s4实际是两个对象的相加,编译器不会对其进行优化,其计算结果会存放在堆中,所以s1和s4不相等

s4.intern()方法会将一个位于堆中的字符串在运行期间动态的加入到常量池中,如果字符串常量池中有该对象的字面量则返回该字面量的引用,所以s1==s6

Integer中的享元模式

 public static void main(String[] args) {
        Integer a = Integer.valueOf(100);
        Integer b = Integer.valueOf(100);
        Integer c = Integer.valueOf(1000);
        Integer d = Integer.valueOf(1000);
        System.out.println(a==b);//true
        System.out.println(c==d);//false
    }
    
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

Integer.valueOf的源码中对数字大小做了个判断,如果是在[-128,127]的范围内,其值是从缓存中获取的, 因为这个范围的数字的使用频率是最高的,所以jdk使用了享元模式来共享数据,提高性能。

前面说享元模式会将共享的对象缓存起来,便于复用,那么它和单例,缓存,对象池有哪些区别呢?

享元模式跟单例的区别

在单例模式中,⼀个类只能创建⼀个对象,⽽在享元模式中,⼀个类可以创建多个对象,每个
对象被多处代码引⽤共享。尽管从代码实现上来看,享元模式和单例模式有很多相似之处,但从设计意图上来
看,它们是完全不同的。使用享元模式是为了对象复⽤,节省内存,⽽应⽤单例模式是为了限制对象的个数。

享元模式跟缓存的区别

在享元模式的实现中,我们通过⼯⼚类来“缓存”已经创建好的对象。这⾥的“缓存”实际上是“存储”的意思,跟我们平时所说的数据库缓存,redis缓存是两回事。我们平时所讲的缓存,主要是为了提⾼访问效率,⽽⾮复⽤

享元模式跟连接池的区别

连接池(⽐如数据库连接池)、线程池等也是为了复⽤,那它们跟享元模式有什么区别呢?

虽然连接池、线程池、享元模式都是为了复⽤,但是池化技术中的“复⽤”和享元模式中的“复⽤”实际上是不同的概念。

池化技术中的“复⽤”可以理解为“重复使⽤”,主要⽬的是节省时间(⽐如从数据库池中取⼀个连接,不需要重新创建)。在任意时刻,每⼀个连接、线程,并不会被多处使⽤,⽽是被⼀个使⽤者独占,当使⽤完成之后,放回到池中,再由其他使⽤者重复利⽤。享元模式中的“复⽤”可以理解为“共享使⽤”,在整个⽣命周期中,都是被所有使⽤者共享的,主要⽬的是节省空间。

委派模式

委派模式属于行为型模式,但不属于23种设计模式中的任何一种。它的基本作用就是负责任务调度和任务分配,可以看作是一种特殊情况下的静态代理的全权代理

有两个类A和类B,他们之间没有任何关联,但是B具有和A一模一样的方法和属性,并且调用B中的方法、属性就是调用A中同名的方法和属性。B好像就是一个受A委托的中介。第三方的代码不需要知道A的存在,也不需要和A发生直接的联系,通过B就可以直接使用A的功能,这样既能够使用到A的各种功能,又能够很好的将A保护起来,一举两得。

通俗理解:我们在项目开发中都是一个项目团队,老板把任务交给项目经理后,项目经理制定项目计划、将任务下发到底下的开发人员,这就是委派模式。但是我们发现这个跟之前学的代理模式非常相似,其实这也可以当做是静态代理模式的一种特例。此外,项目经理接到任务后会做一个权衡,怎么去选择分配这些任务。我们又发现这个跟之前学的策略模式非常相似,其实这也可以当做是策略模式的一种特例。(其实设计模式一般都不会独立存在,都是混合使用)

委派模式跟静态代理模式以及策略模式的区别?
委派模式:代理人全权负责这一件事。如:老板给项目经理安排任务,项目经理只是负责调度工作,真正干活的是底下的开发人员。
静态代理模式:代理人只是参与被代理人一小部分的工作,最终代理人还是要参与工作的。如:我需要中介帮我找房子,但是最后看房还是需要本人去看的。

委派模式主要有3个角色:
抽象任务(ITask):定义一个抽象接口
委派者(Delegate):负责在各个具体任务实例之间选择执行任务的实例,由其分派任务
具体任务(Concrete):真正执行任务的对象。

代码示例

以项目团队展开工作为例,实现如下

public interface IEmployee {

    void work(String work);
}

public class CodeEmployee implements IEmployee {

    @Override
    public void work(String work) {
        System.out.println("我做coding工作");
    }
}

public class UIEmployee implements IEmployee {

    @Override
    public void work(String work) {
        System.out.println("我做ui工作....");
    }
}


public class PMEmployee implements IEmployee{
    private static Map<String, IEmployee> map = new HashMap<>();

    static {
        map.put("画图", new UIEmployee());
        map.put("接口开发", new CodeEmployee());
    }

    @Override
    public void work(String work) {
        if (map.containsKey(work)) {
            map.get(work).work(work);
        }
    }
}
public class Test {

    public static void main(String[] args) {
        new Boss().commend("画图", new PMEmployee());
    }

    static class Boss {
       public void commend(String work, PMEmployee pmEmployee) {
           pmEmployee.work(work);
       }
    }

}

委派模式在源码中的应用

委派模式在spring源码中使用到很多,最经典的就是DispatcherServlet,根据不同的url将其交给不同的处理器去处理

我们的JVM在加载类时也使用到了双亲委派模型。一个类加载器在加载类的时候,先把这个请求委派给自己的父类加载器去执行,如果父类加载器还存在父类加载器,则继续向上委派,直到分派给顶层的启动类加载器。如果父类加载器可以完成类加载,那么就返回成功,自己需要干任何事情;只有父加载器无法完成加载,子加载器才会尝试自己去加载。

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //尝试让父加载器去加载class
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值