Java进阶篇设计模式之七 ----- 享元模式和代理模式
前言
在中我们学习了结构型模式的组合模式和过滤器模式。本篇则来学习下结构型模式最后的两个模式, 享元模式和代理模式。
享元模式
简介
享元模式主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
用通俗的话来说就是进行共用。生活中也有一些例子,比如之前很火的共享单车,更早之前的图书馆,编程中经常用的string类,数据库连接池等等。当然,享元模式主要的目的是复用,如果该对象没有的话,就会进行创建。
享元模式的角色主要分为三大类,抽象享元类、具体享元类以及享元工厂类。
- 抽象享元类:所有具体享元类的超类或者接口,通过这个接口,可以接受并作用于外部专题。
- 具体享元类:实现抽象享元类接口的功能并增加存储空间。
- 享元工厂类:用来创建并管理抽象享元类对象,它主要用来确保合理地共享。每当接受到一个请求是,便会提供一个已经创建的抽象享元类对象或者新建一个。 享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在 ,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
其它的就不再多说,这里依旧使用一个简单的示例来加以说明。
在我们以前读书的时候,经常会用到笔,其中铅笔又是最早接触的,我们最开始使用铅笔可能不是写字,而是进行画画。这里我们可以把笔当作一个抽象享元类,铅笔当作一个具体享元类,然后再创建一个享元工厂类,用于创建和管理,最后再由调用者决定用铅笔进行干嘛。
首先,我们创建一个接口。
interface pen { void write(); }
然后再创建一个享元工厂类,指定需要内部需要做的事情。
class penil implements pen { private string name; private string something; private int i; public penil(string name) { this.name = name; i++; system.out.println(name+" 第:"+i+"次创建"); } public string getname() { return name; } public void setname(string name) { this.name = name; } public string getsomething() { return something; } public void setsomething(string something) { this.something = something; } @override public void write() { system.out.println(name+" 用于铅笔 "+something); } }
继而再创建一个工厂类,用于创建和管理。
class penfactory { private static final map<string, penil> map = new hashmap<string, penil>(); public static penil get(string name) { penil penil = map.get(name); if (penil == null) { penil = new penil(name); map.put(name, penil); } return penil; } }
最后再来进行调用测试。
public class flyweighttest { public static void main(string[] args) { string names[] = { "张三", "李四", "王五", "虚无境" }; for (int i = 0; i < 8; i++) { penil penil = penfactory.get(names[i>3?i-4:i]); penil.setsomething("画了一条鱼"); penil.write(); } } }
输出结果:
张三 第:1次创建 张三 用于铅笔 画了一条鱼 李四 第:1次创建 李四 用于铅笔 画了一条鱼 王五 第:1次创建 王五 用于铅笔 画了一条鱼 虚无境 第:1次创建 虚无境 用于铅笔 画了一条鱼 张三 用于铅笔 画了一条鱼 李四 用于铅笔 画了一条鱼 王五 用于铅笔 画了一条鱼 虚无境 用于铅笔 画了一条鱼
上述示例中,每个对象都使用了两次,但是每个对象都只是创建了一次而已,而享元模式核心的目的其实就是复用,只要我们理解了这一点,想必掌握该模式也就不在话下了。
享元模式优点:
极大的减少对象的创建,从而降低了系统的内存,提升了效率。
享元模式缺点:
提高了系统的复杂度,因为需要将状态进行分离成内部和外部,并且也使外部状态固有化,使得随着内部状态的变化而变化,会造成系统的混乱。
使用场景:
系统有大量相似对象。
注意事项:
需要注意划分外部状态和内部状态,否则可能会引起线程安全问题。 这些类必须有一个工厂对象加以控制。
与单例模式比较
虽然它们在某些方面很像,但是实际上却是不同的东西,单例模式的目的是限制创建多个对象,避免冲突,比如使用数据库连接池。而享元模式享元模式的目的是共享,避免多次创建耗费资源,比如使用string类。
代理模式
简介
代理模式于结构型模式,主要是通过一个类代表另一个类的功能。通常,我们创建具有现有对象的对象,以便向外界提供功能接口。
代理模式,如其名,也就是代理作用。 我们生活中也有不少示例,比如典型的代购,土豪专用的支票,windows 里面的快捷方式,以及spring中的aop 等等。
代理模式主要由这三个角色组成,抽象角色、代理角色和真实角色。
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
代理模式又分为静态代理、动态代理。
- 静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
- 动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
这里我们依旧用一个简单的示例来进行说明。
张三和李四是室友,某天,张三在寝室内玩游戏正带劲,感觉肚子饿了,本想下楼去吃饭的,但是想起李四可能快要回来,于是打电话给李四,让李四帮自己带份盒饭。这里的李四就扮演着代理者的作用。
静态代理
首先我们用静态代理
来实现该功能。
这里实现相对而言较为简单,依旧是定义一个接口,然后定义一个真实的角色,实现该接口的功能,继而定义一个代理者,也实现该接口,但是添加该真实角色的对象进行相应的业务逻辑处理。
那么该静态代理
代码实现如下:
interface shopping { void buyfood(); } class executeperson implements shopping { private string name; public executeperson(string name) { this.name = name; } @override public void buyfood() { system.out.println(name + " 买东西"); } } class proxyperson implements shopping { private executeperson ep; public proxyperson(executeperson ep) { this.ep = ep; } @override public void buyfood() { ep.buyfood(); } } public class proxytest { public static void main(string[] args) { string name = "李四"; shopping shopping = new proxyperson(new executeperson(name)); shopping.buyfood(); } }
输入结果:
李四 买东西
在使用静态代理实现该功能之后,我们发现实现起来很简单,通过一个代理类就可以在不影响目标对象的前提进行扩展使用。但是我们也发现一个问题,如果我们不确定需要代理某个真实类的时候会比较麻烦,而且在类过多的时候,目标对象与代理对象都要维护,会使系统复杂度提升,维护起来也更加麻烦。
不过这时我们就可以使用动态代理
来进行解决。
动态代理
所谓动态代理
可以不必强行指定某个真实的角色,只需要在运行时决定就可以了。这里我们可以使用jdk中java.lang.reflect
来进行开发。
jdk对动态代理提供了以下支持:
- java.lang.reflect.proxy 动态生成代理类和对象
- java.lang.reflect.invocationhandler
- 可以通过invoke方法实现对真实角色的代理访问;
- 每次通过proxy生成代理类对象时都要指定对象的处理器对象.
那么废话不在多说,开始进行代码改造,之前的接口和真实者不需要更改,我们只需要更改代理者就可以了。
更改之后的代码如下:
class proxyperson2 implements invocationhandler { private shopping shopping; private final string methodname = "buyfood"; public proxyperson2(shopping shopping) { this.shopping = shopping; } @override public object invoke(object proxy, method method, object[] args) throws throwable { object result = null; if (methodname.equals(method.getname())) { result = method.invoke(shopping, args); } return result; } }
测试代码,注意这里调用和之前不同!
这里通过proxy类中的newproxyinstance方法会动态生成一个代理类,然后进行调用。其中这三个参数的说明如下:
- classloader: 生成一个类, 这个类也需要加载到方法区中, 因此需要指定classloader来加载该类
- class[] interfaces: 要实现的接口
- invocationhandler: 调用处理器
public class proxytest { public static void main(string[] args) { shopping shopping2 = (shopping)proxy.newproxyinstance(classloader.getsystemclassloader(), new class[]{shopping.class}, new proxyperson2(new executeperson(name))); shopping2.buyfood(); } }
代理模式优点:
1、职责清晰。 2、高扩展性。 3、智能化。
代理模式缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
注意事项:
和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
代理模式参考:
http://www.runoob.com/design-pattern/proxy-pattern.html
https://baike.baidu.com/item/%e4%bb%a3%e7%90%86%e6%a8%a1%e5%bc%8f/8374046
https://blog.csdn.net/zjf280441589/article/details/50411737
其它
音乐推荐
分享一首很带感的电音!
项目的代码
是本人在学习java过程中记录的一些代码,也包括之前博文中使用的代码。如果感觉不错,希望顺手给个start,当然如果有不足,也希望提出。
github地址: https://github.com/xuwujing/java-study
原创不易,如果感觉不错,希望给个推荐!您的支持是我写作的最大动力!
版权声明:
作者:虚无境
博客园出处:http://www.cnblogs.com/xuwujing
csdn出处:http://blog.csdn.net/qazwsxpcm
个人博客出处:http://www.panchengming.com