常见的设计模式(单例模式&工厂模式)

目录

一.为什么要学习设计模式?

二.单例模式

概念

优点

 缺点

1.饿汉模式

  1.1概念

  1.2示例

2.懒汉模式

2.1 概念

2.2 示例

三.工厂模式

1.概念

2.使用场景

3.工厂方法


一.为什么要学习设计模式?

  • 设计模式(Design pattern)代表了最佳的实践,是很多优秀的软件开发人员的经验总结,是解决特定问题的解决方案。它并不是语法规定,也不拘泥于特定语言。 恰当的使用设计模式可以代码的可复用性,可维护性,可扩展性,健壮性及安全性,这些都是系统非常重要的非功能性需求。
  • 设计模式的广泛使用起始于1995年,GOF(四人帮)出版的《设计模式:可复用面向对象软件基础》。

二.单例模式

概念

  •  保证在内存中只有一个实例。
  • 当类加载到内存的时候,不管有没有人用,都会实例出有一个对象。

优点

  • 在内存中只有一个对象,节省内存空间;
  • 避免频繁的创建销毁对象,可以提高性能;
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点。

 缺点

  • 不适用于变化频繁的对象;
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;

1.饿汉模式

  1.1概念

  • 当类加载到内存的时候,不管有没有人用,都会实例出有一个对象。

  1.2示例

package com.yjx.test;

import java.util.Iterator;

/**
 * 单例饥饿模式
 * @author zjjt
 *
 */
public class SingletonDemo01 {
	
	//什么是单例模式:保证在内存中只有一个实例
	//系统启动时,加载到内存,后续不需要加载。
	//每次new都要分配内存
	//我们一旦使用单例时,要考虑线程安全的问题。
	//饥饿模式:当类加载到内存的时候,不管有没有人用,都会实例出有一个对象
	
	//1.先私有一个构造函数,复制该类通过new的方式创建实例。
	//为什么设置私有化?因为要保证在内存中只有一个实例。
	private SingletonDemo01() {
		
	}
	
	//2.我们先生成一个实例,在本类当中可以实例
	//我们使用了静态的,static的在类初次被加载的时候
	//会按照static块的顺序来执行每个static块,并且只会执行一次。
	//所以我们使用static让他只会实例一次。
	private static final SingletonDemo01 dm01=new SingletonDemo01();
	
	
	//3.静态方法,用于获取已经生成的实例
	public static SingletonDemo01 getIstance() {
		return dm01;
		
	}
	
	//测试
	public static void main(String[] args) {
		for(int i=0;i<100;i++) {
			new Thread(()->{
				System.out.println(SingletonDemo01.dm01.hashCode());
			}).start();
		}
	}
	

}

2.懒汉模式

2.1 概念

  •  只有在你需要的时候,进行调用才会实例化。

2.2 示例

下面给大家看下五种不同懒汉模式他们之间的区别。

  • 第一种方法:在多线程访问时存在线程问题

 首先可以先把线程休眠哪里的代码去掉,去执行该代码是基本上没有出现线程问题,因为在绝对的速度面前线程是安全的。

 

 但是将线程睡眠时间加上,那么就会出现线程问题,所以我们就可以用到另外一种。

package com.yjx.test;
/**
 * 单例懒汉模式:有线程问题
 * @author zjjt
 *
 */
public class SingletonDemo03 {
	 
	 //懒汉模式
	
	
	//先私有化构造函数,无法实例化出来
	private SingletonDemo03() {
     //线程休眠
		try {
			Thread.sleep(100);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private static  SingletonDemo03 dm02=null;
	
	//我们解决这种问题就使用到synchronized
	//因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
	
	public static synchronized SingletonDemo03 getInstance() {
		//判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
		if(dm02==null) {
			dm02=new SingletonDemo03();
		}
		return dm02;
	}
	
	//测试:
	 public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			new Thread(()->{
				System.out.println(SingletonDemo03.getInstance().hashCode());
			}).start();
		}
	}

}

 线程问题:

 

  

  • 第二种方法:给它加上同步锁(synchronized)

   利:

      解决了多线程的安全问题。

   弊:

    降低了程序的性能。每个线程都要去判断锁机制,那么会增加程序运行的负担,同时只要做判断,CPU都要处理,那么也会消耗CPU的资源即就是加同步会降低程序的性能

 

package com.yjx.test;
/**
 * 单例懒汉模式
 * @author zjjt
 *
 */
public class SingletonDemo03 {
	 
	 //懒汉模式
	
	
	//先私有化构造函数,无法实例化出来
	private SingletonDemo03() {
		try {
			Thread.sleep(100);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private static  SingletonDemo03 dm03=null;
	
	//我们解决这种问题就使用到synchronized
	//因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
	
	public static synchronized SingletonDemo03 getInstance() {
		//判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
		if(dm03==null) {
			dm03=new SingletonDemo03();
		}
		return dm03;
	}
	
	//测试:
	 public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			new Thread(()->{
				System.out.println(SingletonDemo03.getInstance().hashCode());
			}).start();
		}
	}

}
  • 第三种方法:不在方法上使用同步锁,在方法里面使用同步锁  

   利:

   性能比第二种方法好。因为当一个线程进入同步锁里面,判断完以后,实例化,后面的线程之间返回该对象就可以啦!!!

 

  弊:

   也是最为致命的地方在于在我们判断是否为空的时候,假如第一个线程刚好走到判断是否为空和同步锁中间,我在下面代码标注//-----这块位置,突然被第二个线程抢了,于是第二个线程先进了同步锁,判断完了实例化了,然后由于第一个线程已经做完非空判断了,他也会走到同步锁里面,这样子又实例化了,所有这种方法也是存在多线程问题。

package com.yjx.test;
/**
 * 单例懒汉模式:有线程问题
 * @author zjjt
 *
 */
public class SingletonDemo04 {
	 
	 //懒汉模式
	
	
	//先私有化构造函数,无法实例化出来
	private SingletonDemo04() {
		try {
			Thread.sleep(100);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private static  SingletonDemo04 dm04=null;
	
	//我们解决这种问题就使用到synchronized(同步锁)
	//因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
	
	
	public static  SingletonDemo04 getInstance() {
		//判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
		if(dm04==null) {
           //--------
			synchronized (SingletonDemo04.class) {
				dm04=new SingletonDemo04();
			}
		}
		
		return dm04;
	}
	
	//测试:
	 public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			new Thread(()->{
				System.out.println(SingletonDemo04.getInstance().hashCode());
			}).start();
		}
	}

}
  • 第四种方法:双重判断

    既保证了性能更加好一些,又保证了多线程的安全。

 

   流程:

    第一个线程----->第一个判断是否为空---->第二个线程抢了----->进入同步锁---->判断完以后出来实例化完成---->第一个线程才在进入同步锁--->进入第二个判断----->而对象不为空了----->不会进入第二个判断里面,直接返回对象。

 

package com.yjx.test;
/**
 * 双重判断
 * @author zjjt
 *
 */
public class SingletonDemo05 {
	 
	
	
	//先私有化构造函数,无法实例化出来
	private SingletonDemo05() {
		try {
			Thread.sleep(100);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private static  SingletonDemo05 dm02=null;
	
	//我们解决这种问题就使用到synchronized(同步锁)
	//因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
	
     
	public static  SingletonDemo05 getInstance() {
		//判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
		if(dm02==null) {
			//双重判断,当线程走到这里停止,换成了另外一个线程,但是他往下走还是会在判断一遍,所以这种方法更加安全。
			synchronized (SingletonDemo05.class) {
				if(dm02==null) {
				dm02=new SingletonDemo05();
				}
			}
		}
		
		return dm02;
	}
	
	//测试:
	 public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			new Thread(()->{
				System.out.println(SingletonDemo05.getInstance().hashCode());
			}).start();
		}
	}

}
  • 第五种方法: 静态内部类方法 

  我们在该方法中创建一个静态内部类,然后在该静态内部类中实例化SingletonDemo07,

我们在测试的时候调用getInstance() 方法,就会执行静态内部类,而静态内部类哪里加上了static,所以只会实例一次。

 

package com.yjx.test;
/**
 * 静态内部类
 * @author zjjt
 *
 */
public class SingletonDemo07 {
	 
	
	
	//先私有化构造函数,无法实例化出来
	private SingletonDemo07() {
		try {
			Thread.sleep(100);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	

	
    //静态内部类,只有当我们调用他时,他才会执行。	

	public static class  SingletonDemoHolder{
		private  final static SingletonDemo07 dm07=new SingletonDemo07(); 
	}
	
     
	public static  SingletonDemo07 getInstance() {
		  return SingletonDemoHolder.dm07;
		}
		
		
	
	
	//测试:
	 public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			new Thread(()->{
				System.out.println(SingletonDemo07.getInstance().hashCode());
			}).start();
		}
	}

}

注:以上这五种方法都存在一个问题,就是虽然构造函数被私有化了,但是可以通过反射破话单例的私有化构造函数,所以我们可以使用第六种

  • 第六种方法:使用枚举型enum

    枚举型是由java之父创建的,枚举型里面压根没有构造函数,所以无法实例出该类。是jvm帮我们初始化的反射机制也无法破话,因为枚举型没有构造函数。

 

package com.yjx.test;
/**
 * 枚举类
 * @author zjjt
 *
 */
public enum SingletonDemo06 {
	 
     
	  //枚举型是由java之父创建的,枚举型里面压根没有构造函数,所以无法实例出该类。
	   //是jvm帮我们初始化的
	  //反射机制也无法,因为枚举型没有构造函数

	    INSTANCE;

	    public String hello(String name) {
	        return "hello " + name;
	    }
		
	
	    //测试
	   public static void main(String[] args) {
		   for (int i = 0; i < 100; i++) {
				new Thread(()->{
					System.out.println(SingletonDemo06.INSTANCE.hashCode());
				}).start();
			}
		}
	}    
	
	


三.工厂模式

1.概念

用于产生对象的方法或者式类,称之为工厂。 上面所讲到的单例模式也可以看作为一个特殊的工厂。

2.使用场景

为什么需要工作模式,原来使用new的方式感觉也很简单,且好懂?

使用工厂的原因是我们可以通过工厂模式,来集中控制对象的创建过程,这样可以给设计带来更多的灵活性。

比如:spring的IOC容器就是工厂模式的经典实现。

3.工厂方法

 示例

用于生产指定系列的对象。已鸭子为例,鸭子有真的鸭子,橡皮鸭,电子玩具鸭等。如何能方便的创建出各种鸭子,并将创建过程控制起来,以便于以后的维护和扩展?

类图:

  Duck是抽象的鸭子类,是所有类型鸭子的父类,每种类型的鸭子都需要继承父类并重写父类中的方法,这里体现了多态。 

  

1.Duck代码如下

 

package com.yjx.demo;
/**
 * 
 * @author zjjt
 *
 */
public abstract class Duck {
	      
	abstract public void quack();

}

 

2.RubberDuck代码如下

 

package com.yjx.demo;
/**
 * 橡皮鸭类
 * @author zjjt
 *
 */
public class RubberDuck extends Duck{
	
	@Override
	public void quack() {
		System.out.println("我是橡皮鸭");
		
	}

}

3.WildDuck代码如下

 

package com.yjx.demo;
/**
 * 真鸭子
 * @author zjjt
 *
 */
public class WildDuck extends Duck{
	
	@Override
	public void quack() {
		System.out.println("我是真鸭子");
		
	}

}

4.DonaldDuck代码如下

package com.yjx.demo;
/**
 * 唐老鸭
 * @author zjjt
 *
 */
public class DonaldDuck extends Duck{
   
	@Override
	public void quack() {
		System.out.println("我是唐老鸭");
		
	}
}

5.DuckFactory鸭子工厂

package com.yjx.demo;
/**
 * 鸭子工厂
 * @author zjjt
 *
 */
public class DuckFactory {
	 
	//先私有化构造函数
	private DuckFactory() {
		
	}
	
	//饿汉模式实例化出一个鸭子工厂
	private static  DuckFactory df=new DuckFactory();
	
	//定义图中的几种子类的鸭子类,类型用数字区分,可以手动增加
	public static final int RUBBER_DUCK=1;
	
	public static final int WILDRUBBER_DUCK=2;
	
	public static final int DONALD_DUCK=3;
	
	/**
	 * 依据鸭子类型得到鸭子实例的方法
	 */
	public static Duck getInstance(int duckType) {
		switch (duckType) {
		case RUBBER_DUCK:
			 return new RubberDuck();
		case WILDRUBBER_DUCK:
		    return new WildDuck();
		case DONALD_DUCK:
		    return new DonaldDuck();
		default:
			 return null;
		}
	}
	

}

  6.测试

 

package com.yjx.demo;
/**
 * 测试
 * @author zjjt
 *
 */
public class Test {
	public static void main(String[] args) {
		Duck rubberDuck=DuckFactory.getInstance(DuckFactory.RUBBER_DUCK);
		rubberDuck.quack();
		
		Duck wildDuck=DuckFactory.getInstance(DuckFactory.WILDRUBBER_DUCK);
		wildDuck.quack();
		
		Duck donaldDuck=DuckFactory.getInstance(DuckFactory.DONALD_DUCK);
		donaldDuck.quack();
	}
	
	

}

今天的学习到此结束啦!!!

 

猜你喜欢

转载自blog.csdn.net/m0_65725031/article/details/125403955