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


    目录

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

    二.单例模式

    概念

    优点

     缺点

    1.饿汉模式

      1.1概念

      1.2示例

    2.懒汉模式

    2.1 概念

    2.2 示例

    三.工厂模式

    1.概念

    2.使用场景

    3.工厂方法


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

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

    二.单例模式

    概念

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

    优点

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

     缺点

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

    1.饿汉模式

      1.1概念

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

      1.2示例

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

    2.懒汉模式

    2.1 概念

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

    2.2 示例

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

     

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

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

     

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

    1. package com.yjx.test;
    2. /**
    3. * 单例懒汉模式:有线程问题
    4. * @author zjjt
    5. *
    6. */
    7. public class SingletonDemo03 {
    8. //懒汉模式
    9. //先私有化构造函数,无法实例化出来
    10. private SingletonDemo03() {
    11. //线程休眠
    12. try {
    13. Thread.sleep(100);
    14. } catch (Exception e) {
    15. e.printStackTrace();
    16. }
    17. }
    18. private static SingletonDemo03 dm02=null;
    19. //我们解决这种问题就使用到synchronized
    20. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
    21. public static synchronized SingletonDemo03 getInstance() {
    22. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
    23. if(dm02==null) {
    24. dm02=new SingletonDemo03();
    25. }
    26. return dm02;
    27. }
    28. //测试:
    29. public static void main(String[] args) {
    30. for (int i = 0; i < 100; i++) {
    31. new Thread(()->{
    32. System.out.println(SingletonDemo03.getInstance().hashCode());
    33. }).start();
    34. }
    35. }
    36. }

     线程问题:

     

      

     

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

       利:

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

       弊:

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

     

    1. package com.yjx.test;
    2. /**
    3. * 单例懒汉模式
    4. * @author zjjt
    5. *
    6. */
    7. public class SingletonDemo03 {
    8. //懒汉模式
    9. //先私有化构造函数,无法实例化出来
    10. private SingletonDemo03() {
    11. try {
    12. Thread.sleep(100);
    13. } catch (Exception e) {
    14. e.printStackTrace();
    15. }
    16. }
    17. private static SingletonDemo03 dm03=null;
    18. //我们解决这种问题就使用到synchronized
    19. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
    20. public static synchronized SingletonDemo03 getInstance() {
    21. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
    22. if(dm03==null) {
    23. dm03=new SingletonDemo03();
    24. }
    25. return dm03;
    26. }
    27. //测试:
    28. public static void main(String[] args) {
    29. for (int i = 0; i < 100; i++) {
    30. new Thread(()->{
    31. System.out.println(SingletonDemo03.getInstance().hashCode());
    32. }).start();
    33. }
    34. }
    35. }

     

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

       利:

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

     

      弊:

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

    1. package com.yjx.test;
    2. /**
    3. * 单例懒汉模式:有线程问题
    4. * @author zjjt
    5. *
    6. */
    7. public class SingletonDemo04 {
    8. //懒汉模式
    9. //先私有化构造函数,无法实例化出来
    10. private SingletonDemo04() {
    11. try {
    12. Thread.sleep(100);
    13. } catch (Exception e) {
    14. e.printStackTrace();
    15. }
    16. }
    17. private static SingletonDemo04 dm04=null;
    18. //我们解决这种问题就使用到synchronized(同步锁)
    19. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
    20. public static SingletonDemo04 getInstance() {
    21. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
    22. if(dm04==null) {
    23. //--------
    24. synchronized (SingletonDemo04.class) {
    25. dm04=new SingletonDemo04();
    26. }
    27. }
    28. return dm04;
    29. }
    30. //测试:
    31. public static void main(String[] args) {
    32. for (int i = 0; i < 100; i++) {
    33. new Thread(()->{
    34. System.out.println(SingletonDemo04.getInstance().hashCode());
    35. }).start();
    36. }
    37. }
    38. }

     

    • 第四种方法:双重判断

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

     

       流程:

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

     

    1. package com.yjx.test;
    2. /**
    3. * 双重判断
    4. * @author zjjt
    5. *
    6. */
    7. public class SingletonDemo05 {
    8. //先私有化构造函数,无法实例化出来
    9. private SingletonDemo05() {
    10. try {
    11. Thread.sleep(100);
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. private static SingletonDemo05 dm02=null;
    17. //我们解决这种问题就使用到synchronized(同步锁)
    18. //因为他只允许一个线程先执行,必须要等该线程执行完,后面的才能执行。
    19. public static SingletonDemo05 getInstance() {
    20. //判断如果该类还为null,那么就实例化,如果不为空,就直接返回该。
    21. if(dm02==null) {
    22. //双重判断,当线程走到这里停止,换成了另外一个线程,但是他往下走还是会在判断一遍,所以这种方法更加安全。
    23. synchronized (SingletonDemo05.class) {
    24. if(dm02==null) {
    25. dm02=new SingletonDemo05();
    26. }
    27. }
    28. }
    29. return dm02;
    30. }
    31. //测试:
    32. public static void main(String[] args) {
    33. for (int i = 0; i < 100; i++) {
    34. new Thread(()->{
    35. System.out.println(SingletonDemo05.getInstance().hashCode());
    36. }).start();
    37. }
    38. }
    39. }

     

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

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

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

     

    1. package com.yjx.test;
    2. /**
    3. * 静态内部类
    4. * @author zjjt
    5. *
    6. */
    7. public class SingletonDemo07 {
    8. //先私有化构造函数,无法实例化出来
    9. private SingletonDemo07() {
    10. try {
    11. Thread.sleep(100);
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. //静态内部类,只有当我们调用他时,他才会执行。
    17. public static class SingletonDemoHolder{
    18. private final static SingletonDemo07 dm07=new SingletonDemo07();
    19. }
    20. public static SingletonDemo07 getInstance() {
    21. return SingletonDemoHolder.dm07;
    22. }
    23. //测试:
    24. public static void main(String[] args) {
    25. for (int i = 0; i < 100; i++) {
    26. new Thread(()->{
    27. System.out.println(SingletonDemo07.getInstance().hashCode());
    28. }).start();
    29. }
    30. }
    31. }

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

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

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

     

    1. package com.yjx.test;
    2. /**
    3. * 枚举类
    4. * @author zjjt
    5. *
    6. */
    7. public enum SingletonDemo06 {
    8. //枚举型是由java之父创建的,枚举型里面压根没有构造函数,所以无法实例出该类。
    9. //是jvm帮我们初始化的
    10. //反射机制也无法,因为枚举型没有构造函数
    11. INSTANCE;
    12. public String hello(String name) {
    13. return "hello " + name;
    14. }
    15. //测试
    16. public static void main(String[] args) {
    17. for (int i = 0; i < 100; i++) {
    18. new Thread(()->{
    19. System.out.println(SingletonDemo06.INSTANCE.hashCode());
    20. }).start();
    21. }
    22. }
    23. }

    三.工厂模式

    1.概念

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

    2.使用场景

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

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

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

    3.工厂方法

     示例

     

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

     

    类图:

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

     

      

    1.Duck代码如下

     

    1. package com.yjx.demo;
    2. /**
    3. *
    4. * @author zjjt
    5. *
    6. */
    7. public abstract class Duck {
    8. abstract public void quack();
    9. }

     

    2.RubberDuck代码如下

     

    1. package com.yjx.demo;
    2. /**
    3. * 橡皮鸭类
    4. * @author zjjt
    5. *
    6. */
    7. public class RubberDuck extends Duck{
    8. @Override
    9. public void quack() {
    10. System.out.println("我是橡皮鸭");
    11. }
    12. }

     

    3.WildDuck代码如下

     

    1. package com.yjx.demo;
    2. /**
    3. * 真鸭子
    4. * @author zjjt
    5. *
    6. */
    7. public class WildDuck extends Duck{
    8. @Override
    9. public void quack() {
    10. System.out.println("我是真鸭子");
    11. }
    12. }

     

    4.DonaldDuck代码如下

    1. package com.yjx.demo;
    2. /**
    3. * 唐老鸭
    4. * @author zjjt
    5. *
    6. */
    7. public class DonaldDuck extends Duck{
    8. @Override
    9. public void quack() {
    10. System.out.println("我是唐老鸭");
    11. }
    12. }

    5.DuckFactory鸭子工厂

    1. package com.yjx.demo;
    2. /**
    3. * 鸭子工厂
    4. * @author zjjt
    5. *
    6. */
    7. public class DuckFactory {
    8. //先私有化构造函数
    9. private DuckFactory() {
    10. }
    11. //饿汉模式实例化出一个鸭子工厂
    12. private static DuckFactory df=new DuckFactory();
    13. //定义图中的几种子类的鸭子类,类型用数字区分,可以手动增加
    14. public static final int RUBBER_DUCK=1;
    15. public static final int WILDRUBBER_DUCK=2;
    16. public static final int DONALD_DUCK=3;
    17. /**
    18. * 依据鸭子类型得到鸭子实例的方法
    19. */
    20. public static Duck getInstance(int duckType) {
    21. switch (duckType) {
    22. case RUBBER_DUCK:
    23. return new RubberDuck();
    24. case WILDRUBBER_DUCK:
    25. return new WildDuck();
    26. case DONALD_DUCK:
    27. return new DonaldDuck();
    28. default:
    29. return null;
    30. }
    31. }
    32. }

      6.测试

     

    1. package com.yjx.demo;
    2. /**
    3. * 测试
    4. * @author zjjt
    5. *
    6. */
    7. public class Test {
    8. public static void main(String[] args) {
    9. Duck rubberDuck=DuckFactory.getInstance(DuckFactory.RUBBER_DUCK);
    10. rubberDuck.quack();
    11. Duck wildDuck=DuckFactory.getInstance(DuckFactory.WILDRUBBER_DUCK);
    12. wildDuck.quack();
    13. Duck donaldDuck=DuckFactory.getInstance(DuckFactory.DONALD_DUCK);
    14. donaldDuck.quack();
    15. }
    16. }

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

     

  • 相关阅读:
    什么是持续集成的自动化测试?
    淘宝如何选词打造黄金标题?构词规则是什么?
    企业应用架构研究系列一:业务拆分
    吹爆这款制作电子图册的工具,真是太绝了
    数据管理能力成熟度评估模型_企业如何实施DCMM
    expo + react native项目隐藏状态栏踩坑
    第67步 时间序列建模实战:ARIMA建模(Stata)
    排序算法-----归并排序
    Go语言 Interface(接口)
    qemu-kvm下的vcuda虚拟化
  • 原文地址:https://blog.csdn.net/m0_65725031/article/details/125403955