单例设计模式和工厂设计模式

单例设计模式和工厂设计模式

1 单例设计模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态

特点:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。

单例四大原则:
构造私有
以静态方法或者枚举返回实例
确保实例只有一个,尤其是多线程环境
确保反序列换时不会重新构建对象

实现单例模式的方式
(1) 饿汉式
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以是线程安全的。

public class Singleton {

    /**
     * 私有构造  避免了类在外部被实例化
     */
    private Singleton() {
        System.out.println("构造函数Singleton1");
    }

    /**
     * 初始值为实例对象
     */
    private static Singleton single = new Singleton();

    /**
     * 静态工厂方法  Singleton的唯一实例只能通过getInstance()方法访问。
     * 通过Java反射机制是能够实例化构造方法为private的类的,会使Java单例实现失效
     * @return 单例对象
     */
    public static Singleton getInstance() {
        System.out.println("getInstance");
        return single;
    }

    public static void main(String[] args){
        System.out.println("初始化");
        Singleton instance = Singleton.getInstance();
    }
}

(2)懒汉式
以下是懒汉式示例

public class Singleton2 {
      /**
        * 私有构造
        */
      private Singleton2() {
        System.out.println("构造函数Singleton2");
      }

      /**
         * 初始值为null
        */
      private static Singleton2 single = null;
        /**
	     * 静态工厂方法
	     * @return 单例对象
	     */
        public static Singleton2 getInstance() {
            if(single == null){
                System.out.println("getInstance");
                single = new Singleton2();
            }
            return single;
        }

        public static void main(String[] args){

            System.out.println("初始化");
            Singleton2 instance = Singleton2.getInstance();
        }
	}

示例用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个Singleton对象

(3)同步锁 — 加锁来解决线程安全的问题。

针对懒汉式的实例函数getInstance(),多线程时线程不安全,于是加锁,其他不变。

public synchronized  static Singleton3 getInstance() {  
      
            if(single == null){  
                single = new Singleton3();  
            }  
       
	      }
	        return single;  
	   }  

加上synchronized 关键字,能够实现线程安全,但是synchronized 是重量级锁,但是该方式运行效率却降低了。于是引入第四种

(4)双重检查锁 – 提高加锁时的效率

public class Singleton4 {

    /**
 	 * 私有构造
    */
    private Singleton4() {}

    /*** 初始值为null
     加 volatile关键字是为了防止 创建对象时的指令重排问题,导致其他线程使用对象时造成空指针问题。
     */
     private volatile static Singleton4 single = null;

     /**
	 * 双重检查锁
    * @return 单例对象
    */
     public static Singleton4 getInstance() {
           if (single == null) {
               synchronized (Singleton4.class) {
                   if (single == null) {
                       single = new Singleton4();
                   }
               }
             }
         return single;
     }
}

加锁的时候,只对需要加锁的代码加锁,而不是对整个方法加锁。

(5) 静态内部类

public class Singleton5 {

     /**
      * 私有构造
     */
     private Singleton5() {}

       /**
        * 静态内部类
         */
       private static class InnerObject{
        private static Singleton5 single = new Singleton5();
       }

       public static Singleton5 getInstance() {
             return InnerObject.single;
        }  
}

引入了一个内部静态类InnerObject,静态内部类只有在调用时才会加载,它保证了Singleton 实例的延迟初始化,又保证了实例的唯一性。它把singleton 的实例化操作放到一个静态内部类中,在第一次调用getInstance() 方法时,JVM才会去加载InnerObject类,同时初始化singleton 实例,所以能让getInstance() 方法线程安全。
特点是:即能延迟加载,也能保证线程安全。

应用场景:
以下是在java中使用到的单例设计模式
Java标准库中的Runtime类
日志记录框架中的日志对象:许多日志记录框架,如Log4j、SLF4J等,使用单例设计模式来管理日志对象。
数据库连接池:在使用数据库连接时,连接池常常使用单例设计模式来管理连接对象的创建和复用。连接池维护一个连接对象的池子,通过获取单例连接池对象,我们可以从池中获取数据库连接,而不需要每次都创建新的连接。

2 工厂设计模式
工厂设计模式旨在通过一个公共的接口或基类来创建对象,而不需要暴露对象的具体实现细节。它将对象的创建和使用分离,通过使用工厂类来封装对象的实例化过程。

(1)简单工厂
定义:
一个工厂方法,依据传入的参数,生成对应的产品对象;
角色:
1、抽象产品
2、具体产品
3、具体工厂
4、产品使用者
案例讲解: 苹果和橘子都属于水果,抽象出来一个水果类Fruit,苹果和橘子就是具体的产品类,然后创建一个水果工厂,分别用来创建苹果和橘子。

public interface Fruit {  // 水果接口
    void WhatFruit();
}

苹果类:

public class Apple implements Fruit{
    @Override
    public void WhatFruit() {
        System.out.println("苹果");
    }
}

橘子类

public class Orange implements Fruit{
    @Override
    public void WhatFruit() {
        System.out.println("橘子");
    }
}

水果工厂:

public class FruitFactory {

    public Fruit createFruit(String Category) {

        if (Category.equals("apple")) { //生产苹果
           return new Apple();
        } else if (Category.equals("orange")) { //生产橘子
             return new Orange();
        }
            return null;
       }
}

使用水果工厂生产产品:

public class FruitApp {

    public static void main(String[] args) {
        FruitFactory factory = new FruitFactory();
        Apple apple = (Apple) factory.createFruit("apple"); //获得苹果
        Orange orange = (Orange) factory.createFruit("orange");//获得梨
        apple.WhatFruit();
        orange.WhatFruit();
    }
}

将对象创建和使用分离,同时注意到,每当添加一种水果,就必然要修改工厂类,违反了开闭原则;
简单工厂适合于产品对象较少的情况。

(2)工厂方法
定义:
将工厂提取成一个接口或抽象类,具体生产什么产品由子类决定;
角色:
1、抽象产品
2、具体产品
3、抽象工厂
4、具体工厂
案例讲解:
和上例中一样,产品类抽象出来,这次把工厂类也抽象出来,生产什么样的产品由子类来决定。其中Fruit Apple Orange类和前例保持一致。
抽象工厂接口: – 水果工厂

public interface FruitFactory2 {

    Fruit createFruit(); //生产水果
}

苹果工厂

public class AppleFactory implements FruitFactory2{
    @Override
    public Fruit createFruit() {
        return new Apple();
    }
}

橘子工厂

public class OrangeFactory implements FruitFactory2{
    @Override
    public Fruit createFruit() {
        return new Orange();
    }
}

生产产品

public class FruitApp2 {

    public static void main(String[] args){
            AppleFactory appleFactory = new AppleFactory();
            OrangeFactory orangeFactory = new OrangeFactory();
            Apple apple = (Apple) appleFactory.createFruit(); //获得苹果
            Orange orange = (Orange) orangeFactory.createFruit(); //获得橘子
            apple.WhatFruit();
            orange.WhatFruit();
       }
}

以上这种方式,解耦,遵循了开闭原则,但是需要的产品很多的话,需要创建非常多的工厂。

(3) 抽象工厂
定义:
为创建一组相关或者是相互依赖的对象提供的一个接口,而不需要指定它们的具体类。
角色:
1、抽象产品
2、具体产品
3、抽象工厂
4、具体工厂
案例讲解:
假如生产小米手机,小米手机有很多系列,小米note、红米note等;假如小米note生产需要的配件有825的处理器,6英寸屏幕,而红米只需要650的处理器和5寸的屏幕就可以了。
cpu接口和实现类:

public interface Cpu {

    void run();

    class Cpu650 implements Cpu {
        @Override
        public void run() {
            System.out.println("650 也厉害");
        }
     }

     class Cpu825 implements Cpu {
        @Override
        public void run() {
            System.out.println("825 更强劲");
        }
   }
}

屏幕接口和实现类:

public interface Screen {

    void size();

    class Screen5 implements Screen {
        @Override
       public void size() {
          System.out.println("" + "5寸");
        }
    }

    class Screen6 implements Screen {
        @Override
      public void size() {
           System.out.println("6寸");
          }
   }
}

手机抽象工厂接口:

public interface PhoneFactory {

    Cpu getCpu();//使用的cpu

    Screen getScreen();//使用的屏幕
}

小米手机工厂:

public class XiaoMiFactory implements PhoneFactory{
    @Override
    public Cpu getCpu() {
        return new Cpu.Cpu825();//高性能处理器
    }

    @Override
    public Screen getScreen() {
        return new Screen.Screen6();//6寸大屏
    }
}

红米手机工厂:

public class HongMiFactory implements PhoneFactory{
    @Override
    public Cpu getCpu() {
        return new Cpu.Cpu650();//高效处理器
    }

    @Override
    public Screen getScreen() {
        return new Screen.Screen5();//小屏手机
    }
}

使用工厂生产产品:

public class PhoneApp {

    public static void main(String[] args) {
        HongMiFactory hongMiFactory = new HongMiFactory();
        XiaoMiFactory xiaoMiFactory = new XiaoMiFactory();
        Cpu.Cpu650 cpu650 = (Cpu.Cpu650) hongMiFactory.getCpu();
        Cpu.Cpu825 cpu825 = (Cpu.Cpu825) xiaoMiFactory.getCpu();
        cpu650.run();
        cpu825.run();

        Screen.Screen5 screen5 = (Screen.Screen5) hongMiFactory.getScreen();
        Screen.Screen6 screen6 = (Screen.Screen6) xiaoMiFactory.getScreen();
        screen5.size();
        screen6.size();

    }
}

抽象工厂可以解决一系列的产品生产的需求,对于大批量,多系列的产品,用抽象工厂可以更好的管理和扩展。

三种工厂方式总结
1、对于简单工厂和工厂方法来说,对于产品的分类和名称是确定的,数量是相对固定的,推荐使用简单工厂模式;
2、抽象工厂用来解决相对复杂的问题,适用于一系列、大批量的对象生产。

应用场景
以下是在java中使用到的工厂设计模式
Java标准库中的Calendar类:Calendar类是一个抽象的日历类,它提供了创建不同日历实现的静态工厂方法,如getInstance()。通过该方法获取的Calendar实例是根据不同地区和语言设置的日历对象。

Java集合框架中的Collection接口:Collection接口是表示集合的顶级接口,它定义了创建不同类型集合的静态工厂方法,如Collections.emptyList()、Collections.singleton()等。这些方法返回了不同的集合实例,如空集合、单元素集合等。

JDBC(Java数据库连接):在使用JDBC连接数据库时,我们通过DriverManager类的静态方法getConnection()来获取数据库连接对象。这里的DriverManager充当了一个工厂,根据不同的数据库驱动程序提供不同的数据库连接实现。

GUI编程中的窗口工厂:在图形用户界面(GUI)编程中,使用工厂设计模式来创建窗口对象是很常见的。例如,Swing框架中的JFrame类,它提供了一个工厂方法JFrame.createFrame()来创建JFrame对象,这样可以隐藏具体窗口的创建过程。

Spring框架中的依赖注入(DI):Spring框架广泛使用了工厂设计模式来实现依赖注入。通过配置文件或注解,Spring框架会创建和管理各种Bean对象,并在需要时将它们注入到其他对象中。这样可以实现解耦和灵活的对象创建和管理。

你可能感兴趣的:(java,单例模式,java,工厂设计模式)