深圳幻海软件技术有限公司 欢迎您!

设计模式——创建型设计模式

2023-03-13

创建型设计模式争对对象/类创建时的优化工厂方法模式(了解)通过定义顶层抽象工厂类,通过继承的方式,针对于每一个产品都提供一个工厂类用于创建。情况:只适用于简单对象,当我们需要生产许多个产品族的时候,这种模式就有点乏力了创建对象不再使用传统的new,而是创建一个工厂类,作为all实体类创建对象的一个封

创建型设计模式

争对对象/类创建时的优化

工厂方法模式(了解)

通过定义顶层抽象工厂类,通过继承的方式,针对于每一个产品都提供一个工厂类用于创建。

情况:只适用于简单对象,当我们需要生产许多个产品族的时候,这种模式就有点乏力了

创建对象不再使用传统的new,而是创建一个工厂类,作为all实体类创建对象的一个封装类。(避免了更改类名、构造方法时,需要修改大量的代码)

简单工厂模式:(不灵活,不建议)

不符合开闭原则。如果输入没有提前写好的水果则就需要再添加每个类里的代码

copy
//水果抽象类 public abstract class Fruit { private final String name; public Fruit(String name){ this.name = name; } @Override public String toString() { return name+"@"+hashCode(); //打印一下当前水果名称,还有对象的hashCode } } //水果实体类 public class Apple extends Fruit{ //苹果,继承自水果 public Apple() { super("苹果"); } } public class Orange extends Fruit{ //橘子,也是继承自水果 public Orange() { super("橘子"); } } //水果工厂 public class FruitFactory { /** * 这里就直接来一个静态方法根据指定类型进行创建 * @param type 水果类型 * @return 对应的水果对象 */ public static Fruit getFruit(String type) { switch (type) { case "苹果": return new Apple(); case "橘子": return new Orange(); default: return null; } } } //主方法 public class Main { public static void main(String[] args) { Fruit fruit = FruitFactory.getFruit("橘子"); //直接问工厂要,而不是我们自己去创建 System.out.println(fruit); } }

工厂方法模式:通过范型灵活实现

如果新增了水果类型,直接创建一个新的工厂类就行,不需要修改之前已经编写好的内容。

缺点:一种水果就有一种新的工厂类,太多工厂类了

copy
//水果抽象类 public abstract class Fruit { private final String name; public Fruit(String name){ this.name = name; } @Override public String toString() { return name+"@"+hashCode(); //打印一下当前水果名称,还有对象的hashCode } } //水果工厂 public abstract class FruitFactory<T extends Fruit> { //将水果工厂抽象为抽象类,添加泛型T由子类指定水果类型 public abstract T getFruit(); //不同的水果工厂,通过此方法生产不同的水果 } //Apple工厂 public class AppleFactory extends FruitFactory<Apple> { //苹果工厂,直接返回Apple,一步到位 @Override public Apple getFruit() { return new Apple(); } } //主方法 public class Main { public static void main(String[] args) { test(new AppleFactory()::getFruit); //比如我们现在要吃一个苹果,那么就直接通过苹果工厂来获取苹果 } //此方法模拟吃掉一个水果 private static void test(Supplier<Fruit> supplier){ System.out.println(supplier.get()+" 被吃掉了,真好吃。"); } }

抽象工厂模式

情况:适用于有一系列产品的公司。

缺点:容易违背开闭原则。一旦增加了一种产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变

举例:

实际上这些产品都是成族出现的,比如小米的产品线上有小米12,小米平板等,华为的产品线上也有华为手机、华为平板,但是如果按照我们之前工厂方法模式来进行设计,那就需要单独设计9个工厂来生产上面这些产品,显然这样就比较浪费时间的。

我们就可以使用抽象工厂模式,我们可以将多个产品,都放在一个工厂中进行生成,按不同的产品族进行划分,比如小米,那么我就可以安排一个小米工厂,而这个工厂里面就可以生产整条产品线上的内容,包括小米手机、小米平板、小米路由等。

copy
//工厂抽象类 public abstract class AbstractFactory { public abstract Phone getPhone(); public abstract Table getTable(); public abstract Router getRouter(); } //工厂实现类 public class AbstractFactoryImpl extends AbstractFactory{ @Override public Phone getPhone() { return new ProductPhone(); } @Override public Table getTable() { return new ProductTable(); } @Override public Router getRouter() { return new ProductRouter(); } } //产品抽象类 public abstract class AbRouter{ public abstract Router getRouter(); } ... //产品实体类 public class Router extends AbRouter{ @Override public Router getRouter(){ return new Router(); } }

建造者模式

当构造对象时参数较多,可以通过建造者模式使用链式方法创建对象,保证参数填写正确。

可以去看看StringBuilder的源码,有很多的框架都为我们提供了形如XXXBuilder的类型,我们一般也是使用这些类来创建我们需要的对象。

建造者模式创建对象其实和StringBuilder一样:实际上我们是通过建造者来不断配置参数或是内容,当我们配置完所有内容后,最后再进行对象的构建。

copy
public static void main(String[] args) { StringBuilder builder = new StringBuilder(); //创建一个StringBuilder来逐步构建一个字符串 builder.append(666); //拼接一个数字 builder.append("老铁"); //拼接一个字符串 builder.insert(2, '?'); //在第三个位置插入一个字符 System.out.println(builder.toString()); //差不多成形了,最后转换为字符串 }

举例:

copy
//实体类的编写 public class Student { int id; int age; int grade; String name; String college; String profession; List<String> awards; //一律使用建造者来创建,不对外直接开放 private Student(int id, int age, int grade, String name, String college, String profession, List<String> awards) { this.id = id; this.age = age; this.grade = grade; this.name = name; this.college = college; this.profession = profession; this.awards = 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); } } }
copy
//主方法 public static void main(String[] args) { Student student = Student.builder() //获取建造者 .id(1) //逐步配置各个参数 .age(18) .grade(3) .name("小明") .awards("ICPC-ACM 区域赛 金牌", "LPL 2022春季赛 冠军") .build(); //最后直接建造我们想要的对象 }

单例模式

单例模式:在计算机进程中,同一个类始终只会有一个对象来进行操作。

多例模式:在计算机进程中,对一个实体类创建一次对象就是对当个对象操作,若是创建多个对象则是分别对对应的对象操作。

单例模式的三种写法:

  1. 饿汉式单例(不建议)

    在最开始就创建了对象(太饥渴了,一开始就需要对象)

    copy
    public class Singleton { private final static Singleton INSTANCE = new Singleton(); //用于引用全局唯一的单例对象,在一开始就创建好 private Singleton() {} //禁用了构造方法Singleton()来创建对象。不允许随便new,需要对象直接找getInstance public static Singleton getInstance(){ //获取全局唯一的单例对象 return INSTANCE; } }
  2. 加锁的懒汉式单例(不建议,没有第三种方法好)

    懒汉:在要用的时候才创建对象。但又得防多线程就上了锁

    copy
    public class Singleton { private static volatile Singleton INSTANCE; //在一开始先不进行对象创建。volatile关键字是多线程的时候,这个变量更改了,别的线程可以立马检测到 private Singleton() {} //禁用了构造方法Singleton()来创建对象。不允许随便new,需要对象直接找getInstance public static Singleton getInstance(){ if(INSTANCE == null) {//这层判断是便于第一次外访问时不用在走锁 synchronized (Singleton.class) {//加锁是为了防止多线程创建了多个对象 if(INSTANCE == null) INSTANCE = new Singleton(); //由于加了锁,所以当一个进程进来创建了对象,其他线程需要再判断一次有没有人已经创建了这个类对象,有就不创建了。内层还要进行一次检查,双重检查锁定 } } return INSTANCE; } }
  3. 静态内部类的半懒、半饿式单例(建议)

    静态内部类该开始不会加载,需要的时候才会加载,由于这个类一加载就会创建对象。

    所以实现了懒汉的资源不滥用,饿汉的防止多线程

    copy
    public class Singleton { private Singleton() {}//禁用了构造方法Singleton()来创建对象。不允许随便new,需要对象直接找getInstance private static class Holder { //由静态内部类持有单例对象,但是根据类加载特性,我们仅使用Singleton类时,不会对静态内部类进行初始化。一旦类初始化之后值将不会改变,有点饿汉式的味道。 private final static Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ //只有真正使用内部类时,才会进行类初始化 return Holder.INSTANCE; //直接获取内部类中的 } }

原型模式

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。(说白了就是复制)

  • 浅拷贝:①对于类中基本数据类型,会直接复制值给拷贝对象;②对于引用类型(对象类型),只会复制对象的地址,而实际上指向的还是原来的那个对象,拷贝个基莫。
copy
public static void main(String[] args) { int a = 10; int b = a; //基本类型浅拷贝 System.out.println(a == b);//true Object o = new Object(); Object k = o; //引用类型浅拷贝,拷贝的仅仅是对上面对象的引用 System.out.println(o == k);//true }
  • 深拷贝:无论是基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也会进行拷贝。

使用Cloneable接口提供的拷贝机制,来实现原型模式:操作完会发现Object的clone默认还是浅复制

copy
protected class Student implements Cloneable{ //注意需要实现Cloneable接口 ... //Cloneable中的方法,下面代码复制Object的clone源码 @Override public Object clone() throws CloneNotSupportedException { //提升clone方法的访问权限 return super.clone(); } } //主方法 public static void main(String[] args) throws CloneNotSupportedException { Student student0 = new Student(); Student student1 = (Student) student0.clone(); System.out.println(student0); System.out.println(student1); //两个结果不同,就是地址不同 Student student0 = new Student("小明"); Student student1 = (Student) student0.clone(); System.out.println(student0.getName() == student1.getName()); //true }

深拷贝:在student实现接口Cloneable后重写clone方法

copy
@Override public Object clone() throws CloneNotSupportedException { //这里我们改进一下,针对成员变量也进行拷贝 Student student = (Student) super.clone(); student.name = new String(name); return student; //成员拷贝完成后,再返回 }