设计模式详解:单例模式及其在软件开发中的应用与优势
一、什么是设计模式

设计模式是软件开发过程中面临的一般问题的解决方案。众多软件开发人员经过长时间的试验和错误后总结出了这些解决方案。
设计模式是代码设计经验的总结。
二、设计模式的作用
使用设计模块的目的在于重用代码,能让代码更易被他人理解,还可保证代码的可靠性。
三、常见的设计模式
常见的设计模型有23中
3.1单例模式
单类模式----保证一个类仅有一个实例
当类频繁地进行创建和销毁操作时,我们会采用单例模式。这样做能够减少内存方面的开销,同时可以避免对资源进行多重占用。
单例模式条件:
1.构造方法私有
2.提供一个静态方法【公共】返回创建好的当前类对象
两种表示方式:
1.懒汉式
例如:
package com.wangxing.test1;
/*
* 懒汉式
*/
public class lanhan {
//创建构造方法
public lanhan(){}
//先创建好lanhan对象
静态的 lanhan 对象 obj 为 null 。
创建过的 lanhan 类对象的方法,这样可以避免每次都创建出新的对象,从而减少内存的开销。
3. 这个类的作用是获取 lanhan 。
if (obj==null) {
obj=new lanhan();
}
return obj;
}
}
2.饿汉式
例如:
package com.wangxing.test1;
/*
* 饿汉式
*/
public class ehan {
//创建构造方法
public ehan(){}
//提前创建好ehan对象
3. 有一个静态的 ehan 类的实例 obj,它等于 new ehan()。
//创建得到饿汉对象的方法
获取 ehan 类的方法是一个公共的同步静态方法。
return obj;
}
}
懒汉式与饿汉式的区别
相同点:保证当前类的对象只有一个
书写上:
1.构造方法私有
2.提供一个静态方法【公共】返回创建好的当前类对象
不同点:
书写上:懒汉式中保存当前类的对象变量初始为null,
饿汉式中保存当前类的对象变量初始为new好的当前类对象
运行速度上:懒汉式比饿汉式稍微差一些。
资源利用率:饿汉式比懒汉式稍微差一些。
3.2工厂模式
工厂模式---有一个专门的Java类充当生产对象的工厂
使用工厂模式的条件:
1.需求量大
2.牵一发,动全身
工厂模式中的角色:
工厂角色---生产对象
抽象产品对象-----【抽象/接口】
具体产品------【抽象类/接口类】
例如:有农场各种水果,有西瓜。苹果,香蕉等
工厂角色-----农厂
抽象产品角色-----水果
西瓜、苹果、香蕉----具体产品
1. 先创建抽象产品的共同功能接口
package com.wangxing.test2;
public interface ShuiGuo {
void eat();
}
2.创建每个具体的水果的类继承功能
package com.wangxing.test2;
公共类苹果实现水果类。
@Override
public void eat() {
System.out.println("需要清洗以后吃!!!");
}
}
package com.wangxing.test2;
public 类 xigua 实现了 shuiguo 接口。
@Override
public void eat() {
System.out 把 "我是西瓜,切开吃" 进行输出。
}
}
创建农场的用户有一个选择水果的系统菜单,通过这个菜单能够获取到某个水果所对应的反射对象,然后利用该反射对象来创建实例对象
menu.txt
苹果=com.wangxing.test2.pingguo
西瓜=com.wangxing.test2.xigua
4.创建农厂类实现工厂模式
package com.wangxing.test2;
导入 Java 的输入输出流相关类。使用 BufferedReader 类可以方便地读取字符流。它提供了一些方法来逐行读取输入流中的内容。通过创建 BufferedReader 对象并将其与输入流关联,可以轻松地进行字符流的读取操作。
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
/**
*
* @author Administrator
*
*/
public class NongChang {
创建 ShuiGuo 这个对象需要一个名为 name 的字符串参数。
ShuiGuo sg=null;
//读取菜单
HashMap menuMap=readMenu();
获取到的键名为 name 的值在 menuMap 中对应的类名被赋值给了 className 。
try{
//利用反射机制创建对象
通过 Class.forName(className) 来获取 Class 类的实例,将其赋值给 classobj 这个变量。
创建了一个名为 sg 的对象,这个对象是通过 ShuiGuo 类的 newInstance()方法创建出来的。
}catch(Exception e){
e.printStackTrace();
}
return sg;
}
/**
* 读取菜单
*/
private static HashMap readMenu() {
HashMap menuMap=new HashMap();
try{
创建了一个 BufferedReader 对象 read,它是通过将一个新的 FileReader 对象与一个名为 "menu.txt" 的文件相关联而创建的,而这个 FileReader 对象又是通过将一个新的 File 对象与 "menu.txt" 相关联而创建的。
String menuitem=null;
while 不是 null 时,读取一行内容赋值给 menuitem,即 (menuitem = read.readLine())!= null 时执行循环体中的操作。
苹果等于 com.wangxing.test2.PingGuo 。
3. 分割操作使用的是“=”符号。
menuMap 将 menuarray 的第一个元素作为键,将 menuarray 的第二个元素作为值进行存储。
}
read.close();
}catch(Exception e){
}
return menuMap;
}
}
5.创建测试类
package com.wangxing.test2;
import java.io.BufferedReader;
导入 Java 的输入流读取器类,即 java.io.InputStreamReader 。
public class TestMain {
公共静态的 void 主函数(字符串数组参数 args)抛出异常(Exception)。 主函数用于程序的入口点。 它接收一个字符串数组作为参数。 这个参数可以用于传递命令行参数给程序。 通过抛出异常(Exception),主函数可以处理可能在程序执行过程中出现的异常情况。
创建了一个 BufferedReader 对象 read,它是通过将一个新的 InputStreamReader 对象(用于从 System.in 读取字节流并将其转换为字符流)包装在其中而创建的。
输出提示信息:请输入需要的水果。
String name=read.readLine();
ShuiGuo 是由 NongChang 创建的 ShuiGuo 对象,其名称是通过某种方式确定的。
pg.eat();
}
}
这时我们无需依据获取用户的需求判断来每次创建新的水果对象,而是让工厂自行去创建实例对象以获取具体物品。这样就避免了重复代码的书写。
3.3代理模式
代理模式会为其他对象提供一种代理,通过这种代理来控制对该对象的访问。
买火车票不一定在火车站买,也可以在代售点
代理模式被分为静态代理和动态代理
静态代理有被分为兄弟模式和父子模式
缺点:需要额外提供业务功能实现相似的子类【工作量大】
兄弟模式----同一个接口的两个子类
例如:
1.创建一个火车站和代售点有共同功能的接口
package com.wangxing.test3;
public interface SellPiao {
void maiPiao();
}
2.让火车站和代售点继承这个接口
package com.wangxing.test3;
公共类“火车站”实现了“卖票”的功能。 这个类被定义为“火车站”,它具备卖票的行为和属性。 类“火车站”在程序中承担着卖票的任务,与卖票相关的操作都在这个类中实现。 火车站这个类具有卖票的能力,它是与卖票相关的代码的载体。 类“火车站”的主要作用是卖票,它包含了所有与卖票相关的代码和逻辑。 火车站类被设计用来卖票,卖票的各种操作都在这个类里进行。 这个类被命名为“火车站”,它的主要功能是卖票,与卖票相关的代码都集中在这个类中。 类“火车站”以卖票为主要职责,与卖票相关的部分都在该类中实现。 火车站类被构建用于卖票,卖票的相关操作都在这个类里得以体现。 类“火车站”在程序架构中负责卖票,与卖票相关的代码都包含在其中。
@Override
public void maiPiao() {
System.out.println("正在火车站买票中");
}
}
package com.wangxing.test3;
公共类代收店实现了卖票的功能。
@Override
public void maiPiao() {
System.out 显示了“我是代售点,在买票中”。
}
}
3.测试
package com.wangxing.test3;
public class Main {
公共静态 void 主函数(字符串数组 args) { }
创建了一个名为 huoCheZhan 的对象 hcz 。
hcz.maiPiao();
创建了一个名为 daiShouDian 的对象 dsd,即 new 出了一个 daiShouDian 的实例并赋值给 dsd。
dsd.maiPiao();
}
}
父子模式---继承关系
1.创建一个父类
package com.wangxing.test4;
public class huoCheZhan {
public void maipiao(){
System.out 把“我是火车站,正在卖票”进行了输出。
}
}
2.创建一个子类继承父类重写卖票的方法
package com.wangxing.test4;
公共类“代售点”继承自“火车站”。
public void maipiao(){
System.out 把“我是代售点,正在卖票”进行输出。
}
}
3.测试
package com.wangxing.test4;
public class Main {
public static void main(String[] args) {
创建了一个名为 huoCheZhan 的对象 huoCheZhan hc 。
hc.maipiao();
创建了一个名为 daiShouDian 的对象 ds ,即 daiShouDian ds 被创建出来了 。
ds.maipiao();
}
}
动态代理---由一个java类来负责创建代理类的对象
JDK 动态代理是通过 java.lang.reflect 包中的 Class Proxy 类来创建代理类对象的。
【只能为实现过某个接口的java类提供代理类对象】
例如:
1..创建接口
package com.wangxing.test5;
public interface SellPiao {
void maipiao();
}
2.创建继承接口的类
package com.wangxing.test5;
public class huoCheZhan implements SellPiao{
@Override
public void maipiao(){
System.out.println("我是火车站,正在卖票");
}
}
创建动态代理,通过实现接口的类获取反射对象,进而得到接口子类继承接口的方法。可以直接创建新的对象,然而使用的依然是接口子类的方法。
package com.wangxing.test5;
导入 Java 语言的反射机制中的调用处理器类。
引入 Java 语言的反射机制中的 Method 类。
引入 Java 语言的反射机制中的 Proxy 类。
一个名为 ProxyObject 的类实现了 InvocationHandler 接口。
//定义目标对象
private Object targetObject;
公共的代理对象(Object targetObject)。它用于创建一个代理对象,该代理对象可以在目标对象上执行方法调用,并在调用前后进行一些额外的操作或拦截。通过代理对象,可以对目标对象的方法调用进行监控、修改或增强。
this 的 targetObject 等于 targetObject 。
}
//得到代理对象
public Object getProxy() {
java.lang.reflect 包中有 Class Proxy 类。
//ClassLoader loader--类加载器
ClassLoader 是一个类加载器,这里的 loader 等于当前类(this.getClass())的类加载器(getClassLoader())。
//Class>[] interfaces---接口的反射对象
这个目标对象的类所获取到的接口数组为 Class[] 类型,这些接口数组就是 interfaces,而 interfaces 等于 this.targetObject.getClass().getInterfaces()。
//InvocationHandler h---this
创建一个代理对象,该代理对象通过 Proxy.newProxyInstance 方法创建,使用指定的类加载器(loader)、接口数组(interfaces)以及当前对象(this)作为参数。
}
@Override
调用该方法时,传入代理对象、目标方法以及方法参数数组,会抛出可抛出异常并返回一个对象。在这个过程中,代理对象会根据目标方法的定义来执行相应的操作,而方法参数数组则提供了执行该方法所需的实际参数。最终,该方法会返回执行结果或者抛出异常。
通过调用 method 来对 targetObject 进行操作,并传入参数 args 。具体来说,就是让 method 在 targetObject 上执行,并将 args 作为参数传递进去,最后返回执行的结果。
}
}
package com.wangxing.test5;
public class Main {
public static void main(String[] args) {
创建了一个名为 hcz 的 huoCheZhan 对象。
hcz.maipiao();
ProxyObject 被创建了一个实例,这个实例名为 proxyobj,并且它被初始化为 hcz。
//代理类对象
通过代理对象 proxyobj 获取到 SellPiao 的代理,赋值给 SellPiao 的变量 dsd ,即 SellPiao dsd 等于通过代理对象获取到的代理。
dsd.maipiao();
}
}
CGlib 是一个第三方的开发包,在使用时需要提前自行下载并导入到项目中。
【所有的java类提供代理类对象】
例如:
1.创建需要代理的类
package com.wangxing.test6;
public class huoCheZhan{
public void maipiao(){
System.out.println("我是火车站,正在卖票");
}
}
2.创建获得代理对象
package com.wangxing.test6;
import java.lang.reflect.Method;
引入 net.sf.cglib.proxy 包中的 Enhancer 类。
导入 net.sf.cglib.proxy 包中的 MethodInterceptor 类。
引入 net.sf.cglib.proxy 包中的 MethodProxy 类。
公共类 ProxyObject 实现了 MethodInterceptor 接口。
//定义目标对象
private Object targetObject;
public ProxyObject(Object targetObject){
this.targetObject=targetObject;
}
//得到代理对象
public Object getProxy() {
创建了一个 Enhancer 类的实例,名为 enhancer 。
enhancer 把目标对象的类设置为它的超类。
enhancer.setCallback(this);
return enhancer.create();
}
@Override
该方法用于拦截对象的方法调用。它接收代理对象、方法对象、参数数组以及方法代理对象作为参数。在方法内部,会抛出可捕获的异常。通过这些参数,可以对方法的调用进行处理和控制。
调用 methodProxy 的 invokeSuper 方法,传入 proxy 和 params 作为参数,然后返回该方法的执行结果。
}
}
3.测试
package com.wangxing.test6;
public class Main {
public static void main(String[] args) {
huoCheZhan hcz=new huoCheZhan();
ProxyObject proxyobj=new ProxyObject(hcz);
//代理类对象
火车站代理 = (火车站) proxyobj.getProxy();
daili.maipiao();
}
}
比较:
静态代理是在代码里明确地定义一个业务实现类和一个代理。然后在代理类中对同名的业务方法进行包装。用户是通过代理类来调用被包装过的业务方法。
手动创建一个与目标类相同接口的子类,包装目标类。
JDK 动态代理依据接口中的方法名,于动态生成的代理类里调用业务实现类的同名方法;【兄弟模式】
利用 jdk 所提供的反射包中的 Proxy 类,能够动态地创建出一个与目标类实现相同接口的子类对象,以此来包装目标。
CGlib 动态代理是通过对业务类进行继承来实现的。生成的动态代理类属于业务类的子类。通过重写业务方法来进行代理操作。【父子模式】
利用 CGlib 所提供的 Enhancer 这个类,去动态地创建出一个目标类的子类对象,进而对目标类进行包装。
静态代理
JDK动态代理
CGlib动态代理
手动创建代理类
动态生成代理类对象
动态生成代理类对象
jdk提供的反射包中Proxy这个类
CGlib提供的Enhancer这个类
只能为实现过接口的Java类创建代理对象
为任何Java类创建代理对象