欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

程序员文章站 2024-03-16 18:58:52
...

一、单列模式

问题1:什么是单列模式?
---------保证一个类只有一个实例,并且提供一个访问该全局访问点
问题2:单列的应用场景?
---------数据库连接池
---------任务管理器
---------回收站
---------Web应用的配置对象的读取
---------计数器
---------HttpApplication 也是单位例的典型应用
---------等等…

问题3:单列的优缺点?
优点:
--------- 1.在单例模式中,活动的单例只有一个实例,可以确保所有的对象都访问一个实例
--------- 2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
--------- 3.提供了对唯一实例的受控访问。
--------- 4.由于在系统内存中只存在一个对象,因此可以节约系统资源,避免对共享资源的多重占用

缺点:
--------- 1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误
--------- 2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
--------- 3.单例类的职责过重,在一定程度上违背了“单一职责原则”。
--------- 4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
--------- 5、如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
问题4:我该使用什么模式?
--------- 如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
--------- 如果需要延迟加载,可以使用静态内部类或者懒韩式,相对来说静态内部类好于懒韩式。

1.1、饿汉式

类初始化时,会立即加载该对象,线程天生安全,调用效率高
优点:
不会被反射入侵

/**
 * 饿汉式
 */
public class SingletonDemo01 {


   /**
    * 类初始化时,会立即加载该对象,线程天生安全,调用效率高
    */
	private static SingletonDemo01 singletonDemo01 = new SingletonDemo01();


	private SingletonDemo01() {
		System.out.println("初始化");
	}

    /**
    * 获取类的实例对象
    */
	public static SingletonDemo01 getInstance() {
		return singletonDemo01;
	}
}

测试调用

	public static void main(String[] args) {
		SingletonDemo01 s1 = SingletonDemo01.getInstance();
		SingletonDemo01 s2 = SingletonDemo01.getInstance();
		System.out.println(s1 == s2);
	}

1.2、懒汉式

类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
优点:
使用时才初始化

/**
 * 懒汉式
 */
public class SingletonDemo02 {

  /**
   * 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
   */
	private static SingletonDemo02 singletonDemo02;

	private SingletonDemo02() {
        System.out.println("初始化");
	}

  /**
   * 当实例对象为空,创建实例对象,会有线程安全问题,两个线程同一时间执行会重复创建对象
   */
	public synchronized static SingletonDemo02 getInstance() {
		if (singletonDemo02 == null) {
			singletonDemo02 = new SingletonDemo02();
		}
		return singletonDemo02;
	}
}

测试调用

	public static void main(String[] args) {
		SingletonDemo02 s1 = SingletonDemo02.getInstance();
		SingletonDemo02 s2 = SingletonDemo02.getInstance();
		System.out.println(s1 == s2);
	}

1.3、枚举

/**
 * 使用枚举实现单例模式 
 * 优点:实现简单、枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞 
 * 缺点没有延迟加载
 */
public class User {

    /**
    * 使用/获取枚举创建的实例方法
    */
	public static User getInstance() {
		return SingletonDemo04.INSTANCE.getInstance();
	}

   /**
    * 使用枚举创建实例
    */
	private static enum SingletonDemo04 {
		INSTANCE;
		// 枚举元素为单例
		private User user;

       /**
        * 创建实例
        */
		private SingletonDemo04() {
			System.out.println("初始化");
			user = new User();
		}

        /**
        * 返回实例
        */
		public User getInstance() {
			return user;
		}
	}
}

测试调用

	public static void main(String[] args) {
		User u1 = User.getInstance();
		User u2 = User.getInstance();
		System.out.println(u1 == u2);
	}

1.4、静态内部类

优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象


/**
* 静态内部类方式
*/
public class SingletonDemo03 {


   private SingletonDemo03() {
          System.out.println("初始化..");
   }

   /**
    * 创建实例
    */
	public static class SingletonClassInstance {
		private static final SingletonDemo03 singletonDemo03 = new SingletonDemo03();
	}
	
   /**
    * 调用返回实例
    */
	public static SingletonDemo03 getInstance() {
		return SingletonClassInstance.singletonDemo03;
	}

}

测试调用


	public static void main(String[] args) {
		SingletonDemo03 s1 = SingletonDemo03.getInstance();
		SingletonDemo03 s2 = SingletonDemo03.getInstance();
		System.out.println(s1 == s2);
	}

1.5、双重锁

就是在懒汉式的基础上在加一把锁,处理线程安全问题

public class SingletonDemo04 {

	private SingletonDemo04 singletonDemo04;

	private SingletonDemo04() {

	}

	public SingletonDemo04 getInstance() {
		if (singletonDemo04 == null) {
		    //锁
			synchronized (this) {
				if (singletonDemo04 == null) {
					singletonDemo04 = new SingletonDemo04();
				}
			}
		}
		return singletonDemo04;
	}
}

避免反射攻击

在构造函数中,只能允许初始化化一次即可

private static boolean flag = false;

	private SingletonDemo04() {

		if (flag == false) {
			flag = !flag;
		} else {
			throw new RuntimeException("单例模式被侵犯!");
		}
	}

	public static void main(String[] args) {

	}

二、工厂模式

2.1、简单工厂模式

说明:
相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。
创建示例流程:
1、创建业务接口类,编写一个接口方法
2、创建N个接口实现类,并实现接口方法
3、创建Factory 工厂类,并根据不同的参数判断,来判断调用接口实现方法
4、消费测试
设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

2.2、工厂方法模式

说明:
工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
创建示例流程:
1、创建业务接口,添加业务逻辑方法接口
2、创建实现业务接口的方法类,(图中示例为3个)
3、创建工厂接口,添加接口方法,返回值为业务接口类名
4、创建实现工厂接口的方法类,返回值为每个工厂对应的接口实现类名 , (图中示例为3个)
5、消费者创建根据需求创建工厂实例,调用具体工厂接口实现类的方法获得返回的具体工厂实例
6、调用业务实现接口
---- ,
每个工厂下的业务逻辑实现类可以和简单工厂一样有无数个,a品种苹果,b品种苹果,c…
设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

2.2、抽象工厂模式

说明:
抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。
创建示例流程:
1、创建业务逻辑的通用接口抽象类,并创建抽象方法,1、生产发动机,2、生产座椅
2、创建产品族a 和 b 的抽象类 --> 继承通用接口抽象类
3、创建产品族a 和 b 的实现类,并按需实现抽象类接口 (如图,暂时可以定义2个)
4、创建抽象工厂,并创建抽象方法,1、生产发动机,2、生产座椅(方法返回值=产品族接口名)
5、创建具体工厂a 和 b 继承抽象工厂,并返回具体的工厂实例对象( return new 产品族具体实现类())
6、根据需求调用获取工厂对象,通过工厂对象调用具体的实现类
-------- 如:
我现在需要生产一辆车,现在有两个厂家的零件
我需要a 厂商的发动机和 b 厂商的座椅
1.通过工厂直接获得a 厂商的发动机实例对象 ( new 生产发动机a() )
2.通过工厂直接获得b 厂商的座椅实例对象 ( new 生产座椅b() )
3.调用抽象接口业务逻辑的具体实现就ok了

设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

三、代理模式

说明:
类似于 个人 --> 中介 --> 房东
个人所有需求都需要通过中介,谈好了直接中介通知房东,房东ok了直接和中介签合同就完了
应用场景:
– 1、事务
– 2、spring 环绕通知
– 3、spring 切点切面
– 4、日志打印等等

3.1、静态代理模式

说明:
每个类都需要添加一个代理类,每个方法也需要单独处理,不现实
实现流程:
1、代理类继承和实现类一样的接口
2、代理类调用实现类的接口方法,并在调用方法前后做处理
如图:
创建代理类 xxxProxy,消费者只能调用xxxProxy,不能直接调用实现类

设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

3.2、jdk 动态代理

说明:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
原理
是根据类加载器和接口创建代理类(此代理类是接口的实现类,所以必须使用接口 面向接口生成代理,位于java.lang.reflect包下)
代码示例:

/**
* 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对
*/
public class InvocationHandlerImpl implements InvocationHandler {
    // 这其实业务实现类对象,用来调用具体的业务方法
	private Object target;
	// 通过构造函数传入目标对象
	public InvocationHandlerImpl(Object target) {
		this.target = target;
	}

   /**
    *  处理调用方法前后任务
    */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		System.out.println("调用开始处理");
		// 执行调用的方法
		result = method.invoke(target, args);
		System.out.println("调用结束处理");
		return result;
	}
}

使用:

   
	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// 被代理的对象
		IUserDao userDao = new UserDao();
		InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
		// 获取类加载器
		ClassLoader loader = userDao.getClass().getClassLoader();
	    // 获取接口
		Class<?>[] interfaces = userDao.getClass().getInterfaces();
		// 主要装载器、一组接口及调用处理动态代理实例
		IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
		// 调用代理类的 save方法
		newProxyInstance.save();
	}

3.3、CGLIB 动态代理

说明:
使用cglib[Code Generation Library]实现动态代理,并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码
pom.xml

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

代码示例

public class CglibProxy implements MethodInterceptor {

	private Object targetObject;

   /**
    * 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
    */
	public Object getInstance(Object target) {
		// 设置需要创建子类的类
		this.targetObject = target;
		Enhancer enhancer = new Enhancer();
		//创建虚拟子类
		enhancer.setSuperclass(target.getClass());
		enhancer.setCallback(this);
		return enhancer.create();
	}


  /**
   *  处理调用方法前后任务
   */
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("开启事物");
		Object result = proxy.invoke(targetObject, args);
		System.out.println("关闭事物");
		// 返回代理对象
		return result;
	}
}

使用:

	public static void main(String[] args) {
		CglibProxy cglibProxy = new CglibProxy();
		UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
		userDao.save();
	}

四、建造者模式

说明:
建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性
如:
1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
2、JAVA 中的 StringBuilder。
应用场景:
1、需要生成的对象具有复杂的内部结构。
2、需要生成的对象内部属性本身相互依赖。
与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

创建示例流程:
这里以游戏开发中人物的构造过程为例。在游戏中创建一个形象时,需要对每个部位进行创建。简化而言,需要创建头部,身体和腿部。
1、创建角色类 Role
2、创建建造者抽象接口,提供如图提供4个方法
3、创建具体建造者
4、创建指挥官
------- 1、在客户端 new 指挥官对象的时候需创建建造者对象( 默认构造器中new 建造者抽象接口())
------- 2、创建-建造拼装各身体部位任务方法
5、客户端创建建造者对象传入指挥官对象中,指挥官类中初始化建造者对象( 4.1), 并执行拼装任务

不会创建可参考:
http://c.biancheng.net/view/1354.html
设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

五、模板方法模式

说明:
处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用工厂方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤在父类中定义好,具体的实现延迟到子类中定义。
说白了,就是将一些相同操作的代码,封装成一个算法的骨架。核心的部分留在子类中操作,在父类中只把那些骨架做好。
应用场景:
1、数据库访问的封装
2、Junit单元测试
3、servlet中关于doGet/doPost方法的调用
4、Hibernate中模板程序
5、spring中JDBCTemplate,
6、HibernateTemplate等等
创建示例流程:
1、创建模板方法抽象类,并编写如图,1,2,3,4方法,其中1,3,4为具体方法,并在抽象类实现,2为抽象方法,由子类实现
2、创建子类,继承模板方法抽象类,并根据不同的对接方式实现不同的对接流程
3、业务层创建对象,new 具体实现类,抽象类对象接收,获得具体实现的对象
4、依次调用1,2,3,4方法,3,4,步骤都是在不同业务逻辑的后方,可以在抽象层在定义一个方法5,直接调用3,4 方法,依次调用1,2,5方法也是一样的
设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

六、适配器模式(adapter pattern)

说明:
在设计模式中,适配器模式(英语:adapter pattern)有时候也称包装样式或者包装(wrapper)。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
适配器分类:
类适配器、对象适配、接口适配方式
类适配器方式采用继承方式,对象适配方式使用构造函数传递
应用场景:
1、OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即:将一个字符流的输出对象变为字节流的输出对象。
2、InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即:将一个字节流的输入对象变为字符流的输入对象。
3、SpringMVC 适配器
创建示例流程:
1、创建安卓,苹果充电接口
2、创建impl 实现安卓,苹果充电接口
3、创建适配器类,继承苹果充电接口,并添加构造器,传入安卓充电接口对象使其支持安卓充电口(obj = new 安卓充电接口())
4、添加手机充电类,添加充电方法
---- 4.1、使用安卓充电口给苹果手机充电,安卓充电接口 obj = new 适配器(new 安卓充电实现())
---- 4.2、使用安卓充电口给安卓手机充电,安卓充电接口 obj = new 安卓充电实现()
---- 4.3、使用苹果充电口给苹果手机充电,苹果充电接口 obj = new 苹果充电实现()

设计模式 ---- 单列, 工厂, 代理, 建造者, 模板方法, 适配器,外观

外观模式