单例模型
饿汉式
静态方法创建对象
public class Singleton {// 私有构造方法private Singleton(){}private static Singleton instance = new Singleton();// 提供一个外界获取的方法public static Singleton getInstance(){return instance;}
}
静态代码块创建对象
public class Singleton {private Singleton(){}private static Singleton instance;static {instance = new Singleton();}public static Singleton getInstance(){return instance;}
}
懒汉式
synchronized关键字
public class Singleton {private Singleton(){}private static Singleton instance;public static Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}
【
问题
】上边的代码是存在线程不安全的情况的,当线程1进来,判读instance==null成立,准备创建对象;但是线程1还没创建对象完毕时,线程2来了,线程2也判断成立,也去创建对象,此时就会创建两个不同的对象。
【解决
】:给getInstance方法上添加synchronized关键字
public class Singleton {private Singleton(){}private static Singleton instance;public static synchronized Singleton getInstance(){if(instance == null){instance = new Singleton(); // 下边简称:“写操作”}return instance; // 下边简称:“读操作”}
}
双重检查锁
【问题
】:对于getInstance()方法,其实大部分的操作都是读操作,读操作是线程安全的,如果直接给getInstance方法上加锁,其实会造成大量的线程等待。
【解决
】:调整加锁的时机,双重检查锁
public class Singleton {private Singleton(){}private static volatile Singleton instance; // volatile:保证指令的有序性和可见性public static Singleton getInstance(){// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象if(instance == null){synchronized (Singleton.class){// 第二次判断if(instance == null){instance = new Singleton();}}}return instance;}
}
静态内部类
public class Singleton {private Singleton(){}// 定义一个静态内部类private static class SingletonHolder{// 在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return SingletonHolder.INSTANCE;}
}
JVM加载外部类的过程是不会加载静态内部类的,只有内部类的属性、方法被调用时才会加载并初始化静态属性。静态属性被static修饰,所以只会被实例化一次。
枚举类
枚举类也是线程安全的,只会被加载一次,也是所有单例实现中唯一一种不会被破坏的单例模式
public enum Singleton {INSTANCE
}
枚举方式是属于饿汉式的方式,在不考虑浪费内存空间的情况下,首选枚举方式
存在的问题
【问题
】:破坏单例模式(让单例模式可以创建多个对象,枚举方式除外)
通过序列化和反序列化破坏单例模式
public class Client {public static void main(String[] args) throws Exception {writeObject2File();readObjectFromFile(); // Singleton@27bc2616readObjectFromFile(); // Singleton@3941a79c}// 从文件中读取对象private static void readObjectFromFile() throws Exception {// 1. 创建输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Desktop\\a.txt"));// 2. 读取对象Singleton instance = (Singleton) ois.readObject();System.out.println(instance);// 3. 释放资源ois.close();}// 从文件中写对象public static void writeObject2File() throws Exception {// 1. 获取Singleton对象Singleton instance = Singleton.getInstance();// 2. 将对象写入文件ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Desktop\\a.txt"));oos.writeObject(instance);// 3. 释放资源oos.close();}
}
上面的代码生成的两个对象不是同一个对象,破坏了单例模式
通过反射破坏单例模式
public class Client {public static void main(String[] args) throws Exception {// 1. 获取Singleton的字节码对象Class clazz = Singleton.class;// 2. 获取无参构造方法对象Constructor cons = clazz.getDeclaredConstructor();// 3. 取消访问检查(暴力反射)cons.setAccessible(true);// 4. 创建对象Singleton s1 = (Singleton) cons.newInstance();Singleton s2 = (Singleton) cons.newInstance();System.out.println(s1 == s2); // false - 破坏单例模式}
}
上边代码返回的是false,说明s1和s2不是同一个对象,破坏了单例模式
问题解决
序列化、反序列化破坏单例模式解决方法
在Singleton类种添加readResolve()方法,在反序列化时被反射调用。如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回new出来的对象。
public class Singleton implements Serializable {private Singleton(){}// 定义一个静态内部类private static class SingletonHolder{// 在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return SingletonHolder.INSTANCE;}// 当进行反序列化时,自动调用该方法,将该方法的返回值直接返回public Object readResolve(){return SingletonHolder.INSTANCE;}
}
反射方式破坏单例模式解决方法
其实反射破坏的原理是:通过反射获取Singleton的私有构造方法,然后通过这个私有的构造方法去创建对象。
因此我们只需要在构造方法里添加一个判断即可
public class Singleton implements Serializable {private static boolean flag = false;private Singleton(){synchronized (Singleton.class){if(flag) {throw new RuntimeException("不能创建多个对象");}flag = true;}}// 定义一个静态内部类private static class SingletonHolder{// 在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return SingletonHolder.INSTANCE;}
}
JDK源码解析 - Runtime类
饿汉式:
工厂模式
引例:点咖啡
现在有美式咖啡、拿铁咖啡,顾客可以选择咖啡的种类,咖啡都需要进行加糖加奶。
原本的写法:
咖啡类:
public abstract class Coffee {public abstract String getName();// 加糖public void addsugar() {System.out.println("加糖");}// 加奶public void addmilk() {System.out.println("加奶");}
}
美式咖啡:
public class AmericanCoffee extends Coffee {@Overridepublic String getName() {return "美式咖啡";}
}
拿铁咖啡:
public class LatteCoffee extends Coffee {@Overridepublic String getName() {return "拿铁咖啡";}
}
咖啡店:
public class CoffeeStore {public Coffee orderCoffee(String coffeeType) {// 声明Coffee类型的变量,根据不同的类型创建不同的子类对象Coffee coffee = null;if("american".equals(coffeeType)) {coffee = new AmericanCoffee();}else if("latte".equals(coffeeType)) {coffee = new LatteCoffee();}else {throw new RuntimeException("对不起的,您所点的咖啡没有");}// 加配料coffee.addmilk();coffee.addsugar();return coffee;}
}
测试方法:
public class Client {public static void main(String[] args) {// 创建咖啡店类CoffeeStore store = new CoffeeStore();// 点咖啡Coffee coffee = store.orderCoffee("latte");System.out.println(coffee.getName());}
}
【存在问题
】:如果需要更换对象,那么所有new对象的地方都要修改一遍,这就违背了软件设计的开闭原则。
【解决
】:工厂模式
简单工厂模式
角色:
- 抽象产品:定义了产品的规范(咖啡类)
- 具体产品:是现货集成抽象产品的子类(美式咖啡、拿铁咖啡)
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
简单咖啡工厂类,用来生产咖啡:
public class SimpleCoffeeFactory {public Coffee createCoffee(String coffeeType) {// 声明Coffee类型的变量,根据不同的类型创建不同的子类对象Coffee coffee = null;if("american".equals(coffeeType)) {coffee = new AmericanCoffee();}else if("latte".equals(coffeeType)) {coffee = new LatteCoffee();}else {throw new RuntimeException("对不起的,您所点的咖啡没有");}return coffee;}
}
咖啡店:
public class CoffeeStore {public Coffee orderCoffee(String coffeeType) {SimpleCoffeeFactory factory = new SimpleCoffeeFactory();// 调用生产咖啡的方法Coffee coffee = factory.createCoffee(coffeeType);// 加配料coffee.addmilk();coffee.addsugar();return coffee;}
}
解除了CoffeeStore和具体的咖啡的耦合
【优势
】:工厂类的客户端可能有很多,这样只需要去修改SimpleCoffeeFactory的代码,可以省去其他的修改操作。
【劣势
】:如果要再加新的品种的咖啡,就必须要修改SimpleCoffeeFactory的代码,这违反了开闭原则。
工厂方法模式
角色:
- 抽象产品:定义了产品的规范(咖啡类)
- 具体产品:是现货集成抽象产品的子类(美式咖啡、拿铁咖啡)
- 抽象工厂:提供创建产品的接口,调用者通过访问它具体工厂的工厂方法来创建产品
- 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
抽象工厂:
public interface CoffeeFactory {// 创建咖啡对象的方法Coffee createCoffee();
}
具体工厂:
- 拿铁咖啡工厂对象 - 用来生产拿铁咖啡
public class LatteCoffeeFactory implements CoffeeFactory {@Overridepublic Coffee createCoffee() {return new LatteCoffee();}
}
- 美式咖啡工厂对象 - 用来生产美式咖啡
public class AmericanCoffeeFactory implements CoffeeFactory {@Overridepublic Coffee createCoffee() {return new AmericanCoffee();}
}
咖啡店:
public class CoffeeStore {private CoffeeFactory factory;public void setFactory(CoffeeFactory factory) {this.factory = factory;}// 点咖啡public Coffee orderCoffee() {// 创建咖啡Coffee coffee = factory.createCoffee();// 加配料coffee.addmilk();coffee.addmilk();return coffee;}
}
测试方法:
public class Client {public static void main(String[] args) {// 创建咖啡店类CoffeeStore store = new CoffeeStore();store.setFactory(new AmericanCoffeeFactory()); // 生产美式咖啡// 点咖啡Coffee coffee = store.orderCoffee();System.out.println(coffee.getName());}
}
【
优势
】:用户只要知道具体工程的类名就可以得到产品;系统增加新的产品只需要新增具体产品类和对应的具体工厂类即可。
【劣势
】:每增加一个产品就需要增加一个具体产品类和具体工厂类, 增加了系统的复杂度
抽象工厂模式
抽象工厂模式和工厂方法模式的区别:
- 工厂方法模式:只生产一个等级的产品
- 抽象工厂模式:可以创建多个不同等级的产品
【需求变更
】:现在咖啡店不仅需要生产咖啡,还需要生产甜品
甜品抽象类:
public abstract class Dessert {abstract void show();
}
提拉米苏类:
public class Trimisu extends Dessert{@Overridevoid show() {System.out.println("提拉米苏");}
}
抹茶慕斯类:
public class MatchaMousse extends Dessert{@Overridevoid show() {System.out.println("抹茶慕斯");}
}
甜品抽象工厂:
public interface DessertFactory {// 生产咖啡的功能Coffee createCoffee();// 生产甜品的功能Dessert createDessert();
}
意大利风味甜品工厂(生产拿铁咖啡和提拉米苏甜品):
public class ItaltyDessertFactory implements DessertFactory{ // 意大利风味甜品工厂(生产拿铁咖啡和提拉米苏甜品)@Overridepublic Coffee createCoffee() {return new LatteCoffee(); // 拿铁咖啡}@Overridepublic Dessert createDessert() {return new Trimisu(); // 提拉米苏}
}
美式咖啡的甜品工厂(生产美式咖啡和抹茶慕斯):
public class AmericanDessertFactory implements DessertFactory{ // 美式咖啡的甜品工厂 - 生产美式咖啡和抹茶慕斯@Overridepublic Coffee createCoffee() {return new AmericanCoffee(); // 美式咖啡}@Overridepublic Dessert createDessert() {return new MatchaMousse(); // 抹茶慕斯}
}
测试类:
public class Client {public static void main(String[] args) {// 创建意大利风味的工厂ItaltyDessertFactory it = new ItaltyDessertFactory();Coffee coffee = it.createCoffee(); // 拿铁咖啡Dessert dessert = it.createDessert(); // 提拉米苏System.out.println(coffee.getName());dessert.show();}
}
如果要加一个产品族,只需要再加一个对应的工厂类,不需要修改其他类
【优点
】:客户端只能使用同一个产品族中的对象
【缺点
】:产品族种需要新增一个新的产品,所有的工厂类都需要进行修改。
适用场景:
- 需要创建的对象是一系列相互关联或相互依赖的产品族(电器工厂中的电视机、洗衣机、空调)
- 系统种有多个产品族每次只用其中一种产品(有人只喜欢穿一个品牌的衣服和裤子)
- 系统提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构
- 如:搜狗输入法换皮肤(一套一起换)
模式扩展(Spring框架底层)
bean.properties文件:
american = com.itheima.pattern02factory.factory_04_config.AmericanCoffee
latte = com.itheima.pattern02factory.factory_04_config.LatteCoffee
工厂类:
public class CoffeeFactory {// 1. 定义容器对象存储咖啡对象private static HashMap<String, Coffee> map = new HashMap<>();// 2. 加载配置文件,并创建该配置文件里类的对象并进行存储(只需要加载一次)static {// 2.1 创建Properties对象Properties p = new Properties();// 2.2 调用p对象中的load方法进行配置文件的加载InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");try {p.load(is);// 2.3 从p集合中获取全类名并创建对象Set<Object> keys = p.keySet();for (Object key : keys) {String className = p.getProperty((String) key);// 2.4 通过反射技术创建对象Class clazz = Class.forName(className);Coffee coffee = (Coffee) clazz.newInstance();// 2.5 将名称和对象存储到容器中map.put((String) key, coffee);}} catch (Exception e) {throw new RuntimeException(e);}}// 根据名称获取对象public static Coffee createCoffee(String name) {return map.get(name);}
}
JDK源码解析 - Collection.iterator()
Collection是抽象工厂;ArrayList是具体工厂
【补】:DateForamt类中的getInstance()、Calendar类中的getInstance()也是工厂模式
原型模式
用一个已经创建的对象作为原型,复制这个原型对象来创建一个和原型对象相同的新对象。
包含的角色:
- 抽象原型类:规定了具体原型对象必须实现的clone()方法
- 具体原型类:实现抽象原型类中的clone()方法,他是可以被复制的对象
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同。对于非基本类型属性,克隆对象和源对象指向的是同一块内存空间
深克隆:创建一个新对象,属性中的引用的其他对象也会被克隆,不会指向原有对象地址。
引例1:克隆对象
具体原型对象
:
public class Relizetype implements Cloneable { // 必须实现Cloneable接口:否则调用clone()会抛出CloneNotSupportedExceptionpublic Relizetype() {System.out.println("具体的原型类创建成功");}@Overridepublic Relizetype clone() throws CloneNotSupportedException { // clone()方法是在Object类里的System.out.println("具体原型复制成功");return (Relizetype) super.clone();}
}
测试类
:
public class Client {public static void main(String[] args) throws CloneNotSupportedException {// 创建原型对象Relizetype relizetype = new Relizetype();// 调用原型类中的clone()方法进行对象的克隆Relizetype clone = relizetype.clone();System.out.println(relizetype == clone); // false}
}
必须实现Cloneable接口:否则调用clone()会抛出CloneNotSupportedException。
重写clone()方法:通常需将其改为public访问权限,并返回具体类型。
clone() 方法创建了一个新的对象,而不是返回原对象的引用,因为java的Object.clone()方法是一个native方法,不会调用构造方法,而是直接分配内存并复制数据
引例2.1:三好学生奖状分发(浅克隆)
奖状类:
@Data
public class Citation implements Cloneable {// 三好学生上的姓名private String name;@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();}public void show() {System.out.println(name + "同学被评为三好学生");}
}
测试类:
public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {// 1. 创建原型对象Citation citation = new Citation();// 2. 克隆奖状对象Citation citation1 = citation.clone();citation.setName("张三");citation1.setName("李四");citation.show(); // 张三同学被评为三好学生citation1.show(); // 李四同学被评为三好学生}
}
使用场景
- 对象的创建非常复杂,可以使用原型模式快捷创建对象(原型对象的所属类必须实现clone()方法)
- 性能和安全的要求比较高
引例2.2:三好学生奖状分发(深克隆)
学生类:
@Data
@Accessors(chain = true)
public class Student {// 学生姓名private String name;
}
奖状类:
@Data
public class Citation implements Cloneable {private Student student;@Overridepublic Citation clone() throws CloneNotSupportedException {return (Citation) super.clone();}public void show() {System.out.println(student.getName() + "同学被评为三好学生");}
}
测试类:
public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {// 1. 创建原型对象Citation citation = new Citation();Student stu = new Student().setName("张三");citation.setStudent(stu);// 2. 克隆奖状对象Citation citation1 = citation.clone();Student stu1 = citation1.getStudent();stu1.setName("李四");citation.show(); // 李四同学被评为三好学生citation1.show(); // 李四同学被评为三好学生}
}
【
问题
】由上边测试结果可知,修改了citation1成员变量的值的同时,也修改了citation对象的值
【产生原因
】这是因为stu和stu1此时是一个对象(这就是浅克隆),此时将stu1的属性改成“李四”,导致stu的属性也变成“李四”
【修改
】:把浅克隆变成深克隆(使用序列化和反序列化实现)
public class CitationTest1 {public static void main(String[] args) throws Exception {// 1. 创建原型对象Citation citation = new Citation();Student stu = new Student().setName("张三");citation.setStudent(stu);// 把对象写入文件中ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); // 对象输出流对象oos.writeObject(citation); // 写对象oos.close(); // 释放资源// 从文件中读取对象 (2. 克隆奖状对象)ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));Citation citation1 = (Citation) ois.readObject();citation1.getStudent().setName("李四");ois.close();citation.show(); // 张三同学被评为三好学生citation1.show(); // 李四同学被评为三好学生}
}
Citation类和Student类都必须实现Serializable接口,否则会抛NotSerializableException异常
【修改
】:把浅克隆变成深克隆(在重写clone()方法的时候调用属性的clone()方法)
奖状类:
@Data
public class Citation implements Cloneable, Serializable {private Student student;@Overridepublic Citation clone() throws CloneNotSupportedException {Citation clone = (Citation) super.clone();clone.setStudent(student.clone()); // 深拷贝return clone;}public void show() {System.out.println(student.getName() + "同学被评为三好学生");}
}
学生类:
@Data
@Accessors(chain = true)
public class Student implements Serializable, Cloneable {// 学生姓名private String name;@Overridepublic Student clone() throws CloneNotSupportedException {return (Student) super.clone();}
}
测试类:
public class CitationTest {public static void main(String[] args) throws CloneNotSupportedException {// 1. 创建原型对象Citation citation = new Citation();Student stu = new Student().setName("张三");citation.setStudent(stu);// 2. 克隆奖状对象Citation citation1 = citation.clone();citation1.getStudent().setName("李四");citation.show(); // 张三同学被评为三好学生citation1.show(); // 李四同学被评为三好学生}
}
建造者模式
将复杂对象的构建和表示分离,使同样的构建过程可以创建不同的表示。
建造者建造的产品一般需要有较多的共同点,组成部分需要相似(如果产品之间差异比较大,不适合使用建造者模式)
- 产品类(Product):要创建的复杂对象
- 抽象建造者类(Builder):这个接口规定要实现复杂对象那部分的创建,不涉及具体的对象创建
- 具体建造者类(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法(强调装配的过程)
- 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,不涉及具体产品信息,只保证对象各个部分完整创建或按照某种顺序创建
引例:创建共享单车
【需求
】:自行车包含了车架、车座等组件的生产;车架又有碳纤维,铝合金等材质;车座有橡胶、真皮的材质
Bike类:产品类(车架、车座组件)
Builder:抽象建造者(MobikeBuilder、OfoBuilder是具体的建造者)
Director:指挥者
产品类:
@Data
public class Bike {/*车架*/private String frame;/*车座*/private String seat;
}
抽象构建者:
public abstract class Builder {/*Bike对象*/protected Bike bike = new Bike(); // 目前还没有组装组件(指挥者做)/*构建车架*/public abstract void buildFrame();/*构建车座*/public abstract void buildSeat();/*构建自行车*/public abstract Bike createBike();
}
具体构建者1(摩拜单车):
public class MobileBuilder extends Builder{@Overridepublic void buildFrame() {bike.setFrame("碳纤维车架");}@Overridepublic void buildSeat() {bike.setSeat("真皮车座");}@Overridepublic Bike createBike() {return bike;}
}
具体构建者2(ofo单车):
public class OfoBuilder extends Builder{@Overridepublic void buildFrame() {bike.setFrame("铝合金车架");}@Overridepublic void buildSeat() {bike.setSeat("橡胶车座");}@Overridepublic Bike createBike() {return bike;}
}
指挥者类:
public class Director {private Builder builder;public Director(Builder builder) {this.builder = builder;}/*组装自行车*/public Bike construct() {builder.buildFrame();builder.buildSeat();return builder.createBike();}
}
测试类:
public class Client {public static void main(String[] args) {// 1. 创建指挥者对象Director director = new Director(new MobileBuilder());// 2. 让指挥者进行自行车的组装Bike bike = director.construct();System.out.println(bike.getFrame());System.out.println(bike.getSeat());}
}
Director指挥者类在建造者模式中很重要,是由指挥者类来指导具体的建造者应该如何构建产品,控制调用的先后顺序,向调用者返回完整的产品类。
【改进
】:指挥者类也可以和抽象建造者进行结合:
public abstract class Builder {/*Bike对象*/protected Bike bike = new Bike(); // 目前还没有组装组件(指挥者做)/*构建车架*/public abstract void buildFrame();/*构建车座*/public abstract void buildSeat();/*构建自行车*/public abstract Bike createBike();/*组装自行车*/public Bike construct() {this.buildFrame();this.buildSeat();return this.createBike();}
}
这样做虽然可以不用写指挥者类,但是也加重了建造者类的职责,也不符合单一职责原则,如果construct()过于复杂,还是建议封装到Director中。
模式扩展
当一个类的构造方法需要传入很多参数,如果创建这个类的实例,代码的可读性就会很差,就可以使用建造者模式进行重构。
手机类:
@Data
public class Phone {private String cpu;private String screen;private String memory;private String mainboard;/*私有构造方法*/private Phone(Builder builder) {this.cpu = builder.cpu;this.screen = builder.screen;this.memory = builder.memory;this.mainboard = builder.mainboard;}public static final class Builder {private String cpu;private String screen;private String memory;private String mainboard;public Builder cpu(String cpu) {this.cpu = cpu;return this; // 为了链式编程}public Builder screen(String screen) {this.screen = screen;return this;}public Builder memory(String memory) {this.memory = memory;return this;}public Builder mainboard(String mainboard) {this.mainboard = mainboard;return this;}/*使用构建者创建Phone对象*/public Phone build() {return new Phone(this);}}
}
测试类:
public class Client {public static void main(String[] args) {/*创建手机对象 - 通过构建者对象获取手机对象*/Phone phone = new Phone.Builder().cpu("intel").screen("三星").memory("金士顿内存条").mainboard("华硕").build();System.out.println(phone); // Phone(cpu=intel, screen=三星, memory=金士顿内存条, mainboard=华硕)}
}
将构建的顺序交给客户,这个相当于lombok里的
@Builder
注解
构建者模式对比
工厂方法模式 vs 建造者模式
- 工厂方法模式:整体对象的创建方式
- 建造者模式:部件构建的过程
抽象工厂模式 vs 建造者模式
- 抽象工厂模式:实现对产品家族的创建,不需要关心建造过程,只关心什么产品由什么工厂生产
- 建造者模式:按照指定的蓝图建造产品,通过组装零件而产生一个新产品
抽象工厂模式:汽车配件生产工厂(生产一个产品族的产品)
建造者模式:骑车组装工厂(通过对配件的组装可以返回一个完整的骑车)