栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

设计模式 学习笔记(一)之 五大创建型模式 | Java 语言实现 | 工厂方法设计模式 | 抽象工厂模式 |建造者模式 | 单例模式 | 原型模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式 学习笔记(一)之 五大创建型模式 | Java 语言实现 | 工厂方法设计模式 | 抽象工厂模式 |建造者模式 | 单例模式 | 原型模式

文章目录
  • 参考资料
  • 一、工厂方法设计模式
  • 二、抽象工厂模式
  • 三、建造者模式
  • 四、单例模式
    • 4.1 饿汉式
    • 4.2 懒汉式
    • 4.3 懒汉式的多线程定义
      • 4.3.1 synchronized 同步代码块
      • 4.3.2 synchronized 同步创建实例
      • 4.3.3【最优策略】静态内部类实现懒汉式
  • 五、原型模式
    • 5.1 对象的浅拷贝和深拷贝
    • 5.2 内部属性的深拷贝

参考资料

视频资料:查看视频

在使用设计模式前,我们先用常见的方法来实现封装水果类,以及水果的继承类苹果和橘子。

  1. 定义一个水果抽象类
public abstract class Fruit {    // 水果抽象类
    private final String name;

    public Fruit(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "@" + hashCode(); // 打印当前水果名称和 哈希值
    }
}
  1. 定义实现的子类
// 苹果
class Apple extends Fruit{
    public Apple(){
        super("苹果");
    }
}
// 橘子
class Origin extends Fruit{
    public Origin(){
        super("橘子");
    }
}

  1. 运行测试
public class Demo {
    public static void main(String[] args) {
        System.out.println(new Apple());
        System.out.println(new Origin());
    }
}
// 输出结果
// 苹果@685325104
// 橘子@460141958

接下来我们通过一些设计模式来改写上面的例子,以便体会不同设计模式的特点。

一、工厂方法设计模式

简单的工厂模式

基于之前的例子,我们创建一个工厂类,依此来获取创建实例

这里需注意获取实例的方法是static静态方法

public class FruitFactory {
    public static Fruit getFruit(String type){
        switch (type){
            case "苹果":
                return new Apple();
            case "橘子":
                return new Origin();
            default:
                return null;
        }
    }
}

通过简单工厂创建对象:

public class Demo {
    public static void main(String[] args) {
        Fruit apple = FruitFactory.getFruit("苹果");
        System.out.println(apple);
    }
}
// 输出结果
// 苹果@685325104

这种简单的工厂模式带来的弊端是:

  • 不符合面向对象的开闭原则 OCP,即对扩展开发,对修改关闭。当有新的Fruit子类时,需要改写getFruit的方法

  • getFruit需要进行字符串的判断,比较鸡肋

优化: 使用抽象类+泛型,将水果工厂类进一步划分成具体的水果工厂类

public abstract class FruitFactory {
    public abstract T getFruit();
}

class AppleFactory extends FruitFactory{
    @Override
    public Apple getFruit() {
        return new Apple();
    }
}
class OriginFactory extends FruitFactory{

    @Override
    public Origin getFruit() {
        return new Origin();
    }
}
public class Demo {
    public static void main(String[] args) {
        System.out.println(new AppleFactory().getFruit());
        System.out.println(new OriginFactory().getFruit());
    }
}
// 输出结果
// 苹果@685325104
// 橘子@460141958
二、抽象工厂模式

之前介绍的工厂方法模式,通过定义顶层抽象工厂类,以及继承的方式,针对每一种水果产品都提供一个工厂类进行创建,这里没有涉及更复杂的概念,比如工厂不仅生产水果,而且还生产饮料之类的。

这里我们可以定义水果、饮品类,然后抽象到一个工厂里,按品牌分类。

  1. 定义表示水果的类,和之前一样
public abstract class Fruit {    // 水果抽象类
    private final String name;

    public Fruit(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "@" + hashCode(); // 打印当前水果名称和 哈希值
    }
}
// 苹果
class Apple extends Fruit{
    public Apple(){
        super("苹果");
    }
}
// 橘子
class Origin extends Fruit{
    public Origin(){
        super("橘子");
    }
}
  1. 定义表示饮品的类,和之前表示水果类似
abstract class Drink {    // 饮品抽象类
    private final String name;

    public Drink(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "@" + hashCode(); // 打印当前水果名称和 哈希值
    }
}
// 苹果汁
class AppleDrink extends Drink{
    public AppleDrink(){
        super("苹果汁");
    }
}
// 橘子汁
class OriginDrink extends Drink{
    public OriginDrink(){
        super("橘子汁");
    }
}

  1. 定义抽象工厂类
abstract class AbstractFactory{
    public abstract Fruit getFruit();
    public abstract Drink getDrink();
}
class FactoryA extends AbstractFactory{

    @Override
    public Fruit getFruit() {
        return new Apple();
    }

    @Override
    public Drink getDrink() {
        return new AppleDrink();
    }
}

class FactoryB extends AbstractFactory{

    @Override
    public Fruit getFruit() {
        return new Origin();
    }

    @Override
    public Drink getDrink() {
        return new OriginDrink();
    }
}
  1. 测试
public class Demo {
    public static void main(String[] args) {
        FactoryA factoryA = new FactoryA();
        FactoryB factoryB = new FactoryB();
        System.out.println("品牌A-> 水果: "+ factoryA.getFruit() +
                ", 饮品: " + factoryA.getDrink());
        System.out.println("品牌B-> 水果: "+ factoryB.getFruit() +
                ", 饮品: " + factoryB.getDrink());
    }
}
// 运行结果
// 品牌A-> 水果: 苹果@685325104, 饮品: 苹果汁@460141958
// 品牌B-> 水果: 橘子@1163157884, 饮品: 橘子汁@1956725890

优点: 比工厂方法模式更清晰,能处理更加复杂的情况

缺点: 不得不为每一个产品组的工厂都去添加新产品的生产方法,违背了开闭原则。

三、建造者模式

许多框架里用到的一种设计模式,JDK8本身有用的一种设计模式,比如 StringBuilder

这里我们先创建一个StringBuilder对象,然后就可以链式的添加元素。

public class Demo {
    public static void main(String[] args) {
       StringBuilder builder = new StringBuilder();
       builder.append("Hello, ").append("World!");
        System.out.println(builder);
    }
}

接下来先定义一个学生实体类,然后通过之前的方法来创建学生实例。

import java.util.List;

public class Student {
    private String name;
    private int age;
    private List hobbies;

    public Student(){}
    public Student(String name, int age, List hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = hobbies;
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List getHobbies() {
        return hobbies;
    }

    public void setHobbies(List hobbies) {
        this.hobbies = hobbies;
    }


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", hobbies=" + hobbies +
                '}';
    }
}

在创建学生实例的时候,主要有两种情况:

  • 创建具有所有属性的学生对象
  • 创建只有部分属性的学生对象
import java.util.ArrayList;
import java.util.Arrays;

public class Demo {
    public static void main(String[] args) {
        // 创建所有属性
        Student a = new Student("uni", 22, Arrays.asList("吃饭", "睡觉"));
        // 创建部分属性
        Student b = new Student();
        b.setName("uni");
        b.setAge(22);
        System.out.println("a: " + a);
        System.out.println("b: " + b);
    }
}
// 运行结果
// a: Student{name='uni', age=22, hobbies=[吃饭, 睡觉]}
// b: Student{name='uni', age=22, hobbies=null}

可以看到无论是所有属性还是部分属性,都不太方便。

  • 所有属性构造,需要通过构造函数,填入对应的参数,参数位置不能改变。
  • 部分属性,需要先构造空的对象,然后逐一的setXXX,这样使得代码看起来十分不美观,最好的策略就是在创建对象的时候,就直接能将我们需要填入的参数赋值给这个对象。接下来我们通过建造者模式来解决这个问题。
    这里由于 getter、setter方法比较多,有些冗余,所以使用 lombok 的注解来简化代码。
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.List;

@Getter
@Setter
@ToString
public class Student {
    private String name;
    private int age;
    private List hobbies;

    public Student(){}
    private Student(String name, int age, List hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = hobbies;
    }

    public static StudentBuilder builder(){
        return new StudentBuilder();
    }
    @Getter
    public static class StudentBuilder{
        private String name;
        private int age;
        private List hobbies;

        public StudentBuilder name(String name) {
            this.name = name;
            return this;
        }

        public StudentBuilder age(int age) {
            this.age = age;
            return this;
        }

        public StudentBuilder hobbies(List hobbies) {
            this.hobbies = hobbies;
            return this;
        }
        public Student build(){
            return new Student(name, age, hobbies);
        }
    }
}

通过上述代码可以看到,Student的全参构造设置为了 private 私有级别,这使得在创建Student实例时只能通过其内部的静态类 StudentBuilder,该内部类存储了 Student对象的所有属性,方便在构造的时候进行赋值。
静态内部类 StudentBuilder提供返回实例对象的set方法,以及 build 方法返回一个Student对象,这里的build方法则固定了参数的位置,所以在调用的时候无需关系参数的顺序。

import java.util.Arrays;

public class Demo {
    public static void main(String[] args) {
        // 创建所有属性
        Student a = new Student.StudentBuilder()
                .name("uni")
                .age(22)
                .hobbies(Arrays.asList("吃饭", "睡觉"))
                .build();
        // 创建部分属性
        Student b = new Student.StudentBuilder()
                .name("uni")
                .age(22)
                .build();
        System.out.println("a: " + a);
        System.out.println("b: " + b);
    }
}
// 运行结果
// a: Student(name=uni, age=22, hobbies=[吃饭, 睡觉])
// b: Student(name=uni, age=22, hobbies=null)
四、单例模式

单例模式是指只有一个实例对象,同一个类始终只会有一个对象来进行操作,比如数据库连接类,这种情况下只需要创建一个对象或者直接使用静态方法,不需要创建多个对象。
单例模式通常分为两种,饿汉式和懒汉式。

4.1 饿汉式
public class Singleton {
    // 饿汉式: 定义静态对象,并创建实例
    private final static Singleton INSTANCE = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return INSTANCE;
    }
}

创建饿汉式的实例:

class Demo{
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton);
    }
}
// 运行结果
// Singleton@4554617c
4.2 懒汉式
public class Singleton {
    // 懒汉式: 先定义静态对象,但不创建实例
    private static Singleton instance;
    private Singleton(){};  // 构造方法私有化
    public static Singleton getInstance(){
        if(instance == null)
            instance = new Singleton();
        return instance;
    }
}
4.3 懒汉式的多线程定义

之前的定义是对于单线程情景的,如果我们是使用多线程来创建实例,那么就需要考虑并发带来的问题。假如同时有两个线程判断了 if(instance == null) ,那么 instance就将被这两个线程实例化,这样依赖就实例化了两次,在高并发场景,有多个线程,那么instance可能会被实例化很多次。
比如:我们使用三个线程来创建实例,如果对三个线程分别设置 1s 、2s 、3s 的延迟,达到延迟后就创建懒汉式的实例,这种情况下,Singleton被实例化了一次。

class Demo{
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                Singleton singleton = Singleton.getInstance();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread thread2 = new Thread(() -> {
            try{
                Thread.sleep(2000);
                Singleton singleton = Singleton.getInstance();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread thread3 = new Thread(() -> {
            try{
                Thread.sleep(3000);
                Singleton singleton = Singleton.getInstance();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

但我们将线程的延迟时间都调整到100s,这时候Singleton就被每一个线程实例化了一次,即共实例化了三次。

Thread.sleep(100);

所以我们需要用锁来预防这种情况,防止对象被多次实例化,浪费资源。

4.3.1 synchronized 同步代码块

通过添加 synchronized 关键字同步创建实例

public class Singleton {
    private static Singleton instance;
    private Singleton(){};  // 构造方法私有化
    public static synchronized Singleton getInstance(){
        if(instance == null) {
            System.out.println("Singleton被实例化了.");
            instance = new Singleton();
        }
        return instance;
    }
}

给getInstance()方法添加synchronized关键字之后,多个线程就无法并发的执行这个方法了,这样就有效地避免了多次创建实例的情况,这里可以继续使用之前的多线程代码进行测试,可以将3个线程sleep的时间设置为相同的100,然后同时启动3个线程,结果肯定是只实例化了一次。

这种方式是最简单粗暴的,直接锁整个方法,但是也有一个缺点那就是效率比较低,因为在其他线程执行到这个方法的时候,会等待锁释放,然后才执行方法,这意味着getInstance() 方法还是被其他的线程执行了,只是它们没有创建实例而已。

4.3.2 synchronized 同步创建实例

根据之前的问题,synchronized 同步整个方法,会导致其他线程等待锁的释放,同时还会执行这个方法,比较浪费资源,这里将synchronized 定义到判断实例为空的内部,相当于只锁住创建实例的部分,因为执行时间更短,故性能会稍微高一些。

public class Singleton {
    private static Singleton instance;
    private Singleton(){};  // 构造方法私有化
    public static Singleton getInstance(){
        if(instance == null) {
            synchronized(Singleton.class){
                instance = new Singleton();
                System.out.println("Singleton被实例化了.");
            }
        }
        return instance;
    }
}

不过这里依然有之前的问题,多个线程在判断 instance == null 成立后,有可能同时进入同步代码块,这就跟之前一样,出现了创建多次实例的情况,这里的优化思路是在同步代码块里再进行一次判断,这样避免重复创建实例。

public class Singleton {
    private static Singleton instance;
    private Singleton(){};  // 构造方法私有化
    public static Singleton getInstance(){
        if(instance == null) {
            synchronized(Singleton.class){
                if(instance == null) {
                    instance = new Singleton();
                    System.out.println("Singleton被实例化了.");
                }
            }
        }
        return instance;
    }
}

最后这里还可以进行优化,那就是将 instance 加一个关键字 volatile,这个关键字可保证 instance 在多个线程之间可见,这样当其他线程进入同步代码块后,会拿其他线程更新的最新值去判断(这里理解不是很深入,大概先这么认为)

private static volatile Singleton instance;
4.3.3【最优策略】静态内部类实现懒汉式

这里是根据Java类的加载特性,静态内部类在调用之前是不会进行初始化的,这样我们只要在静态内部类里先初始化一个静态实例,之后在获取的时候直接运行然后返回,这样就保证获取的实例是同一个而且仅有一个了。

public class Singleton {
    private Singleton(){} // 私有化无参构造

    private static class Holder{ // 静态内部类
        // 根据类加载特性, 仅使用Singleton类时,不会对静态内部类进行初始化
        private final static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return Holder.INSTANCE; // 直接获取内部类的静态实例
    }
}

接下来我们通过一个简单的多线程Demo来验证这种懒汉式的正确性:

class Demo{
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(100);
                Singleton singleton = Singleton.getInstance();
                System.out.println(singleton);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread thread2 = new Thread(() -> {
            try{
                Thread.sleep(100);
                Singleton singleton = Singleton.getInstance();
                System.out.println(singleton);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        thread1.start();
        thread2.start();
    }
}
// 运行结果
// Singleton@2b139240
// Singleton@2b139240

在多线程中获取实例,然后输出其哈希的部分值,这样可以看到输出的哈希结果是一样的,所以指向的就是同一个对象实例。
而且没有任何加锁的操作,同时也保证了线程的安全。

五、原型模式

原型模式实际上与对象拷贝息息相关,原型模式使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
即,原型对象作为模板,通过克隆操作,来产生更多的对象,就像细胞复制。

5.1 对象的浅拷贝和深拷贝
  • 浅拷贝
    • 对于类中的基本数据类型,赋值时会直接复制值给拷贝的对象;
    • 对于引用类型,只会复制对象的地址,而实际上还是原来的那个对象,相当于变脸术。
public class Copy {
    public static void main(String[] args) {
        int a = 10;
        int b = a;  // 基本类型 浅拷贝
        System.out.println(a == b);		 // 结果为: true

        Object obja = new Object();
        Object objb = obja; // 引用类型 浅拷贝引用
        System.out.println(obja == objb); // 结果为: true
    }
}
  • 深拷贝
    • 无论是基本类型还是引用类型,深拷贝会将引用类型的所有内容,全部拷贝为一个新的对象,包括对象内部的所有成员变量,也进行拷贝。

在 Java 中,我们可以通过 Cloneable 接口提供的拷贝机制,来实现原型模式:

public class Student implements Cloneable{	// 必须实现 Cloneable 接口
    @Override
    public Student clone() {
        try {
            return (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

测试看看克隆的对象是否为原来的对象:

class Demo{
    public static void main(String[] args) {
        Student a = new Student();
        Student b = a.clone();
        System.out.println(a);
        System.out.println(b);
    }
}
// 运行结果
// Student@4554617c
// Student@74a14482
可以看到,输出的哈希值不同,所以是不同的对象,克隆成功,但不能确认这就是深拷贝,因为深拷贝要保证其内部的引用类型都各不相同。
5.2 内部属性的深拷贝

基于上一个例子,我们在 Student 类里定义引用类型 String,然后通过克隆创建不同的Student 实例,对它们的 Name 进行比较,看看是否为同一个 String。

public class Student implements Cloneable{
    private String name;
    public Student(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    @Override
    public Student clone() {
        try {
            return (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

查看内部属性是否克隆成功

class Demo{
    public static void main(String[] args) {
        Student a = new Student("a");
        Student b = a.clone();
        System.out.println(a.getName() == b.getName()); // 结果为: true
    }
}
由于返回的结果为true,所以内部属性Name克隆失败了,引用类型还是之前的克隆对象。

所以Java提供的clone方法实际上是一种浅拷贝,其内部的引用对象并不会进行拷贝。
要实现真正的深拷贝,在clone()方法里,我们需要拷贝每一个引用类型,这里需要拷贝 name。

@Override
public Student clone() {
    try {
        Student student = (Student) super.clone();
        student.name = new String(name);
        return student;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1014169.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号