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对象,并在需要时将它们注入到其他对象中。这样可以实现解耦和灵活的对象创建和管理。