一、Java 中有几种设计模式?
Java 中一般认为有 23 种设计模式
分为三大类:
1. 创建型模式 5 种
① 工厂方法模式
② 抽象工厂模式
③ 单例模式
④ 建造者模式
⑤ 原型模式
2. 结构型模式 7 种
① 适配器模式
② 装饰器模式
③ 代理模式
④ 外观模式
⑤ 桥接模式
⑥ 组合模式
⑦ 享元模式
3. 行为型模式 11 种
① 策略模式
② 模板方法模式
③ 观察者模式
④ 迭代子模式
⑤ 责任链模式
⑥ 命令模式
⑦ 备忘录模式
⑧ 状态模式
⑨ 访问者模式
⑩ 中介者模式
⑪ 解释器模式
二、什么是单例设计模式?
1. 单例模式定义
单例模式确保某个类只有一个实例,而
且自行实例化并向整个系统提供这个实
例
在计算机系统中,线程池、缓存、日志
对象、对话框、打印机、显卡的驱动程
序对象常被设计成单例,选择单例模式
就是为了避免不一致状态
2. 单例模式的特点
① 单例类只能有一个实例
② 单例类必须自己创建自己的唯一实例
③ 单例类必须给所有其他对象提供这一
实例
④ 单例模式保证了全局对象的唯一性,
比如系统启动读取配置文件就需要单
例保证配置的一致性
3. 单例的四大原则
① 构造器私有化
② 以静态方法或者枚举返回实例
③ 确保实例只有一个,尤其是多线程
环境
④ 确保反序列化时不会重新构建对象
4. 实现单例模式的方式
(1) 饿汉式 (立即加载):
饿汉式单例在类加载初始化时就创建好
一个静态的对象供外部使用,除非系统
重启,这个对象不会改变,所以本身就
是线程安全的
Singleton 通过将构造方法限定为 private避免了类在外部被实例化,在同一个虚拟
机范围内,Singleton 的唯一实例只能通
过 getInstance() 方法访问 (事实上,通过
Java 反射机制是能够实例化构造方法为
private 的类的,会使 Java 单例实现失效)
java
/**
* 饿汉式(立即加载)
*/
public class Singleton1 {
/**
* 私有构造
*/
private Singleton1() {
System.out.println("构造函数Singleton1");
}
/**
* 初始值为实例对象
*/
private static Singleton1 single = new Singleton1();
/**
* 静态工厂方法
* @return 单例对象
*/
public static Singleton1 getInstance() {
System.out.println("getInstance");
return single;
}
public static void main(String[] args){
System.out.println("初始化");
Singleton1 instance = Singleton1.getInstance();
}
}
(2) 懒汉式 (延迟加载):
该示例虽然用延迟加载方式实现了懒汉
式单例,但在多线程环境下会产生多个
Singleton 对象
java
/**
* 懒汉式(延迟加载)
*/
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();
}
}
(3) 同步锁 (解决线程安全问题):
在方法上加 synchronized 同步锁或是
用同步代码块对类加同步锁,此种方
式虽然解决了多个实例对象问题,但
是该方式运行效率却很低下,下一个
线程想要获取对象,就必须等待上一
个线程释放锁之后,才可以继续运行
java
/**
*
* 同步锁(解决线程安全问题)
*/
public class Singleton3 {
/**
* 私有构造
*/
private Singleton3() {}
/**
* 初始值为null
*/
private static Singleton3 single = null;
public static Singleton3 getInstance() {
// 等同于 synchronized public static Singleton3 getInstance()
synchronized(Singleton3.class){
// 注意:里面的判断是一定要加的,否则出现线程安全问题
if(single == null){
single = new Singleton3();
}
}
return single;
}
}
(4) 双重检查锁 (提高同步锁的效率):
使用双重检查锁进一步做了优化,可
以避免整个方法被锁,只对需要锁的
代码部分加锁,可以提高执行效率
java
/**
* 双重检查锁(提高同步锁的效率)
*/
public class Singleton4 {
/**
* 私有构造
*/
private Singleton4() {}
/**
* 初始值为null
*/
private static Singleton4 single = null;
/**
* 双重检查锁
* @return 单例对象
*/
public static Singleton4 getInstance() {
if (single == null) {
synchronized (Singleton4.class) {
if (single == null) {
single = new Singleton4();
}
}
}
return single;
}
}
(5) 静态内部类:
引入了一个内部静态类 (static class),静
态内部类只有在调用时才会加载,它保证
了 Singleton 实例的延迟初始化,又保证
了实例的唯一性
它把 singleton 的实例化操作放到一个静
态内部类中,在第一次调用 getInstance()
方法时,JVM 才会去加载 InnerObject 类,
同时初始化 singleton 实例,所以能让
getInstance() 方法线程安全
特点:即能延迟加载,也能保证线程安全
静态内部类虽然保证了单例在多线程并发
下的线程安全性,但是在遇到序列化对象
时,默认的方式运行得到的结果就是多例
的
java
/**
*
* 静态内部类(延迟加载,线程安全)
*/
public class Singleton5 {
/**
* 私有构造
*/
private Singleton5() {}
/**
* 静态内部类
*/
private static class InnerObject{
private static Singleton5 single = new Singleton5();
}
public static Singleton5 getInstance() {
return InnerObject.single;
}
}
(6) 内部枚举类实现 (防止反射攻击):
事实上,通过 Java 反射机制是能够实例
化构造方法为 private 的类的,这也就是
我们现在需要引入的枚举单例模式
java
public class SingletonFactory {
/**
* 内部枚举类
*/
private enum EnumSingleton{
Singleton;
private Singleton6 singleton;
//枚举类的构造方法在类加载是被实例化
private EnumSingleton(){
singleton = new Singleton6();
}
public Singleton6 getInstance(){
return singleton;
}
}
public static Singleton6 getInstance() {
return EnumSingleton.Singleton.getInstance();
}
}
class Singleton6 {
public Singleton6(){}
}
三、什么是工厂设计模式?
工厂设计模式就是用来生产对象的,在
java 中,万物皆对象,这些对象都需要
创建,如果创建的时候直接 new 该对象,
就会对该对象耦合严重,假如我们要更
换对象,所有 new 对象的地方都需要修
改一遍,这显然违背了软件设计的开闭
原则,如果我们使用工厂来生产对象,
我们就只和工厂打交道就可以了,彻底
和对象解耦,如果要更换对象,直接在
工厂里更换该对象即可,达到了与对象
解耦的目的;所以说,工厂模式最大的
优点就是:解耦
1. 简单工厂 (Simple Factory)
定义:
一个工厂方法,依据传入的参数,生成对
应的产品对象;
角色:
① 抽象产品
② 具体产品
③ 具体工厂
④ 产品使用者
使用说明:
先将产品类抽象出来,比如,苹果和梨都属
于水果,抽象出来一个水果类 Fruit,苹果和
梨就是具体的产品类,然后创建一个水果工
厂,分别用来创建苹果和梨
代码如下:
java
// 水果接口:
public interface Fruit {
void whatIm();
}
// 苹果类:
public class Apple implements Fruit {
@Override
public void whatIm() {
System.out.println("苹果");
}
}
// 梨类:
public class Pear implements Fruit {
@Override
public void whatIm() {
System.out.println("梨");
}
}
//水果工厂:
public class FruitFactory {
public Fruit createFruit(String type) {
if (type.equals("apple")) {//生产苹果
return new Apple();
} else if (type.equals("pear")) {//生产梨
return new Pear();
}
return null;
}
}
// 使用工厂生产产品:
public class FruitApp {
public static void main(String[] args) {
FruitFactory mFactory = new FruitFactory();
Apple apple = (Apple) mFactory.createFruit("apple");//获得苹果
Pear pear = (Pear) mFactory.createFruit("pear");//获得梨
apple.whatIm();
pear.whatIm();
}
}
以上的这种方式,每当添加一种水果,就必
然要修改工厂类,违反了开闭原则;
所以简单工厂只适合于产品对象较少,且产
品固定的需求,对于产品变化无常的需求来
说显然不合适
2. 工厂方法 (Factory Method)
定义:
将工厂提取成一个接口或抽象类,具体生
产什么产品由子类决定;
角色:
① 抽象产品
② 具体产品
③ 抽象工厂
④ 具体工厂
使用说明:
和上例中一样,产品类抽象出来,这次我们
把工厂类也抽象出来,生产什么样的产品由
子类来决定
代码如下:
java
// 水果接口、苹果类和梨类:代码和上例一样
// 抽象工厂接口:
public interface FruitFactory {
Fruit createFruit();//生产水果
}
// 苹果工厂:
public class AppleFactory implements FruitFactory {
@Override
public Apple createFruit() {
return new Apple();
}
}
// 梨工厂:
public class PearFactory implements FruitFactory {
@Override
public Pear createFruit() {
return new Pear();
}
}
// 使用工厂生产产品:
public class FruitApp {
public static void main(String[] args){
AppleFactory appleFactory = new AppleFactory();
PearFactory pearFactory = new PearFactory();
Apple apple = appleFactory.createFruit();//获得苹果
Pear pear = pearFactory.createFruit();//获得梨
apple.whatIm();
pear.whatIm();
}
}
以上这种方式,虽然解耦了,也遵循了开闭
原则,但是如果我需要的产品很多的话,需
要创建非常多的工厂,所以这种方式的缺点
也很明显
- 抽象工厂 (Abstract Factory)
定义:
为创建一组相关或者是相互依赖的对象提供
的一个接口,而不需要指定它们的具体类
角色:
① 抽象产品
② 具体产品
③ 抽象工厂
④ 具体工厂
使用说明:
抽象工厂和工厂方法的模式基本一样,区别
在于,工厂方法是生产一个具体的产品,而
抽象工厂可以用来生产一组相同,有相对关
系的产品
用抽象工厂来实现:
java
// 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.Cpu825 getCpu() {
return new Cpu.Cpu825();//高性能处理器
}
@Override
public Screen.Screen6 getScreen() {
return new Screen.Screen6();//6寸大屏
}
}
//红米手机工厂:
public class HongMiFactory implements PhoneFactory {
@Override
public Cpu.Cpu650 getCpu() {
return new Cpu.Cpu650();//高效处理器
}
@Override
public Screen.Screen5 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 = hongMiFactory.getCpu();
Cpu.Cpu825 cpu825 = xiaoMiFactory.getCpu();
cpu650.run();
cpu825.run();
Screen.Screen5 screen5 = hongMiFactory.getScreen();
Screen.Screen6 screen6 = xiaoMiFactory.getScreen();
screen5.size();
screen6.size();
}
}
以上例子可以看出,抽象工厂可以解决一
系列的产品生产的需求,对于大批量,多
系列的产品,用抽象工厂可以更好地管理
和扩展
4. 三种工厂方式总结
① 对于简单工厂和工厂方法来说,两者的
使用方式实际上是一样的,如果对于产
品的分类和名称是确定的,数量是相对
固定的,推荐使用简单工厂模式;
② 抽象工厂用来解决相对复杂的问题,适用于
一系列、大批量的对象生产