设计模式-创建型
本章主要介绍有关对象创建的几种设计模式。
工厂模式
工厂模式:封装了对象的创建,使得获得对象更加符合实际逻辑
简单工厂
将所有对象的生产集中到一个工厂中
根据传参确定生产的对象类型
public abstract class Fruit { private final String name; public Fruit(String name){ this.name = name; } @Override public String toString() { return name+"@"+hashCode(); } } public class Apple extends Fruit{ public Apple() { super("苹果"); } } public class Orange extends Fruit{ public Orange() { super("橘子"); } } public class FruitFactory { public static Fruit getFruit(String type) { switch (type) { case "苹果": return new Apple(); case "橘子": return new Orange(); default: return null; } } }
缺点:
逻辑不符合:一个工厂一般只是单一生产
工厂设计不封闭:增加和减少生产对象需要对原来代码进行修改,不符合软件设计的开闭原则
工厂方法
将工厂划分成一个继承机构,基类工厂提供了生产对象虚函数接口,而派生类代表生产某种对象的工厂,重写基类提供的虚函数接口,返回生产的对象
public abstract class FruitFactory<T extends Fruit> { public abstract T getFruit(); } public class AppleFactory extends FruitFactory<Apple> { @Override public Apple getFruit() { return new Apple(); } }
这样就可以使用不同类型的工厂来生产不同类型的水果了,并且如果新增了水果类型,直接创建一个新的工厂类就行,不需要修改之前已经编写好的内容。
缺点:
一个系列产品一个工厂结构,一种基类只能派生具有关联关系的生产工厂,不同类的需要构建其他的继承工厂结构
优点:
- 符合实际逻辑
- 符合开闭原则
抽象工厂
对有一组关联关系的产品簇提供产品的统一创建,即一个工厂生产一组相关联的产品
建立一个抽象工厂,一个实例工厂可以生产同一个产品簇的所有产品
public class Router { } public class Table { } public class Phone { } public abstract class AbstractFactory { public abstract Phone getPhone(); public abstract Table getTable(); public abstract Router getRouter(); }
所有的派生类需要重写所有的基类提供的虚函数接口
- 总结:
- 简单工厂simple Factory :
优点:把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象,客户不用自己负责new对象,不用了解对象创建的详细过程
缺点:不符合实际生产逻辑,提供创建对象实例的接口函数不闭合,不能对修改关闭
- 工厂方法Factory Method:
优点:Factory基类,提供了一个纯虚函数(创建产品),定义派生类(具体产品的工厂)负责创建对应的产品(重写虚函数),可以做到不同的产品,在不同的工厂里面创建,能够对现有工厂,以及产品的修改关闭(符合软件开闭原则)
缺点:实际上,很多产品是有关联关系的,属于一个产品簇,不应该放在不同的工厂里面去创建,这样一是不符合实际的产品对象创建逻辑,二是工厂类太多了,不好维护
- 抽象工厂Abstract Factory:
把有关联关系的,属于一个产品簇的所有产品创建的接口函数,放在一个抽象工厂里面Abstract Factory,派生类(具体产品的工广)应该负责创建该产品簇里面所有的产品
所有派生类对基类提供的纯虚函数接口都要进行重写
建造者模式
有很多的框架都为我们提供了形如XXXBuilder
的类型,一般可以使用这些类来创建我们需要的对象。
例如StringBuiler
类:
public static void main(String[] args) { StringBuilder builder = new StringBuilder(); //创建一个StringBuilder来逐步构建一个字符串 builder.append(666); //拼接一个数字 builder.append("老铁"); //拼接一个字符串 builder.insert(2, '?'); //在第三个位置插入一个字符 System.out.println(builder.toString()); //转换为字符串 }
通过建造者来不断配置参数或是内容,当我们配置完所有内容后,最后再进行对象的构建。
相比直接去new一个新的对象,建造者模式的重心更加关注在如何完成每一步的配置,如果一个类的构造方法参数过多,通过建造者模式来创建这个对象,会更加优雅。
案例:
public class Student { ... //一律使用建造者来创建,不对外直接开放 private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) { ... } public static StudentBuilder builder(){ //通过builder方法直接获取建造者 return new StudentBuilder(); } public static class StudentBuilder{ //这里就直接创建一个内部类 //Builder也需要将所有的参数都进行暂时保存,所以Student怎么定义的这里就怎么定义 int id; int age; int grade; String name; String college; String profession; List<String> awards; public StudentBuilder id(int id){ //直接调用建造者对应的方法,为对应的属性赋值 this.id = id; return this; //为了支持链式调用,这里直接返回建造者本身,下同 } public StudentBuilder age(int age){ this.age = age; return this; } ... public StudentBuilder awards(String... awards){ this.awards = Arrays.asList(awards); return this; } public Student build(){ //最后我们只需要调用建造者提供的build方法即可根据我们的配置返回一个对象 return new Student(id, age, grade, name, college, profession, awards); } } }
使用建造者来为生成学生对象:
public static void main(String[] args) { Student student = Student.builder() //获取建造者 .id(1) //逐步配置各个参数 .age(18) .grade(3) .name("小明") .awards("ICPC", "LPL") .build(); //最后直接建造我们想要的对象 }
单例模式
单例模式即在我们的整个程序中,同一个类始终只会有一个对象来进行操作。
饿汉单例:在一开始类加载时就创建好了
public class Singleton { private final static Singleton INSTANCE = new Singleton(); //用于引用全局唯一的单例对象,在一开始就创建好 private Singleton() {} //不允许随便new,需要对象直接找getInstance public static Singleton getInstance(){ //获取全局唯一的单例对象 return INSTANCE; } }
懒汉单例:延迟加载,当我们需要获取对象时,才会进行检查并创建。
由于懒汉式是在方法中进行的初始化,在多线程环境下,可能会出现问题
public class Singleton { private static volatile Singleton INSTANCE; //volatile保证线程可见性 private Singleton() {} public static Singleton getInstance(){ if(INSTANCE == null) {//减低锁竞争 synchronized (Singleton.class) {//保证线程安全 if(INSTANCE == null) INSTANCE = new Singleton(); } } return INSTANCE; } }
不用加锁懒汉模式:
public class Singleton { private Singleton() {} private static class Holder { //由静态内部类持有单例对象,但是根据类加载特性,我们仅使用Singleton类时,不会对静态内部类进行初始化 private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ //只有真正使用内部类时,才会进行类初始化 return Holder.INSTANCE; //直接获取内部类中的 } }
原型模式
原型模式实际上与对象的拷贝息息相关,原型模式使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象
- **浅拷贝:**对于类中基本数据类型,会直接复制值给拷贝对象;对于引用类型,只会复制对象的地址。
- **深拷贝:**无论是基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也会进行拷贝。
在Java中,通过实现Cloneable接口来实现原型模式:
Java为我们提供的clone
方法对对象成员只会进行浅拷贝,要实现深拷贝需要进一步完成如何拷贝
public class Student implements Cloneable{ String name; public Student(String name){ this.name = name; } public String getName() { return name; } @Override public Object clone() throws CloneNotSupportedException { //针对成员变量也进行拷贝 Student student = (Student) super.clone(); student.name = new String(name); return student; } }