设计模式05-创建型模式(建造者/原型/单例模式/Java)

3.4 建造者模式

3.4.1 建造者模式的定义

动机:方便创建复杂对象(一个对象中包含多个成员变量)

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

3.4.2 建造者模式的分析与实现
  • 产品类:复杂对象,里面包含多个成员变量
java 复制代码
public class Product {
    private String part1;
    private String part2;

    public String getPart1() {
        return part1;
    }

    public void setPart1(String part1) {
        this.part1 = part1;
    }

    public String getPart2() {
        return part2;
    }

    public void setPart2(String part2) {
        this.part2 = part2;
    }
}
  • 建造者类:内含两类方法,一个是建造构件,一个是返回建造对象
java 复制代码
public abstract class Builder {
    protected Product product = new Product();
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract Product getResult();
}

public class ConcreteBuilderA extends Builder{
    @Override
    public void buildPartA() {
        product.setPart1("部件A");
    }

    @Override
    public void buildPartB() {
        product.setPart2("部件B");
    }

    @Override
    public Product getResult() {
        return product;
    }
}
  • 指挥者类:负责构建的建造次序,并返回产品类
java 复制代码
public class Director {
    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
    }
    public Product construct() {
        builder.buildPartA();
        builder.buildPartB();
        return builder.getResult();
    }
}
  • 客户端类:只需要指定具体的建造者即可,无需关心产品的具体组装过程
Java 复制代码
public class Main {
    public static void main(String[] args) {
        Director director = new Director(new ConcreteBuilderA());
        Product construct = director.construct();
        System.out.println(construct.getPart1());
        System.out.println(construct.getPart2());
    }
}
3.4.3 建造者模式案例

建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。

  • 产品类
java 复制代码
public class Meal {
    private String food;
    private String drink;

    public String getFood() {
        return food;
    }

    public void setFood(String food) {
        this.food = food;
    }

    public String getDrink() {
        return drink;
    }

    public void setDrink(String drink) {
        this.drink = drink;
    }
}
  • 建造者类
java 复制代码
public interface MealBuilder {

    public void buildFood();
    public void buildDrink();
    public Meal getMeal();
}

public class MealBuilder1 implements MealBuilder {
    private Meal meal = new Meal();
    @Override
    public void buildFood() {
        meal.setFood("汉堡");
    }

    @Override
    public void buildDrink() {
        meal.setDrink("可乐");
    }

    @Override
    public Meal getMeal() {
        return meal;
    }
}

public class MealBuilder2 implements MealBuilder {
    private Meal meal = new Meal();

    @Override
    public void buildFood() {
        meal.setFood("鸡肉卷");
    }

    @Override
    public void buildDrink() {
        meal.setDrink("果汁");
    }

    @Override
    public Meal getMeal() {
        return meal;
    }
}
  • 指挥者类
java 复制代码
public class KFCWaiter {
    private MealBuilder mealBuilder;

    public void setMealBuilder(MealBuilder mealBuilder) {
        this.mealBuilder = mealBuilder;
    }

    public Meal constructMeal() {
        mealBuilder.buildFood();
        mealBuilder.buildDrink();
        return mealBuilder.getMeal();
    }
}
  • 客户端
java 复制代码
public class Main {
    public static void main(String[] args) {
        MealBuilder2 mealBuilder= (MealBuilder2) XMLUtilMeal.getMealMethod();
        KFCWaiter kfcWaiter = new KFCWaiter();
        kfcWaiter.setMealBuilder(mealBuilder);
        Meal meal = kfcWaiter.constructMeal();
        System.out.println("套餐2");
        System.out.println("主食为:" + meal.getFood());
        System.out.println("饮品为:" + meal.getDrink());
    }
}
3.4.4 建造者模式的优缺点
优点 缺点
1.实现了产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,精细地控制产品的创建过程 1.同一个抽象构造者只适用构建一类型相同的产品,若产品间差别太大不适用
2.由于引入抽象建造者,因此很容易引入新的创建者,符合开闭原则 2.产品内部构造复杂,会引起系统庞大,难以构建
3.4.5 建造者模式的适用场景
  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员变量,且需要生成的产品对象的属性相互依赖,需要指定其生成顺序

  • 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中

3.5 原型模式

3.5.1 原型模式的定义

动机:有些对象的创建过程较为复杂,而且需要频繁创建。通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。

定义:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。原型模式允许通过一个原型对象创建一个或多个同类型的其他对象,而无须知道任何创建的细节

3.5.2 原型模式的分析与实现

将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程 。创建新对象(也称为克隆对象)的工厂 就是原型类 自身,工厂方法 由负责复制原型对象的克隆方法 来实现。通过克隆方法所创建的对象是全新的对象 ,它们在内存中拥有新的地址,每一个克隆对象都是独立

  • 浅克隆:只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制

在Java中可以直接使用Object提供的clone()方法来实现对象的克隆(浅克隆)

能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持复制

如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常

java 复制代码
public class ShallowPrototype implements Cloneable{
    public String name;
    @Override
    public ShallowPrototype clone() {
        try {
            return (ShallowPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将被复制

如果需要实现深克隆,可以通过序列化等方式来实现 。在Java语言中,序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。**通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,从而实现深克隆。**需要注意的是,能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

java 复制代码
public class DeepPrototype implements Serializable {
    public String name = "sda";
    public DeepPrototype clone() {
        try {
            //将对象写到输入流中
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bao);
            oos.writeObject(this);
            //将对象从输入流中取出
            ByteArrayInputStream bais = new ByteArrayInputStream(bao.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (DeepPrototype) ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  • 克隆的结果
java 复制代码
(1)对任何的对象x,都有x.clone()!=x,即克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
3.5.3 原型模式的案例

由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。使用原型模式设计该系统。

浅克隆:

java 复制代码
public class EmailPrototype implements Cloneable {
    private File file = null;

    public EmailPrototype() {
        this.file = new File();
    }

    public void display() {
        System.out.println("展示所下载的文件");
    }

    public File getFile() {
        return this.file;
    }

    public EmailPrototype close() {
        try {
            return (EmailPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

深克隆:

java 复制代码
public class EmailPrototype implements Serializable {
    private File file = null;

    public EmailPrototype() {
        this.file = new File();
    }

    public void display() {
        System.out.println("展示所下载的文件");
    }

    public File getFile() {
        return this.file;
    }

    public EmailPrototype deepClone() {
        try {
            //将对象转化写入到字节流中
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bao);
            oos.writeObject(this);

            //将对象从字节流中取出
            ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (EmailPrototype) ois.readObject();
        } catch (Exception e) {
           throw new RuntimeException(e);
        }
    }
}
3.5.4 原型模式的优缺点
优点 缺点
1.简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率 1.需要为每一个类配备一个克隆方法,当对已有的类进行改造时,需要修改源代码,违背了开闭原则。
2.可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,可辅助实现撤销操作
3.对于相似的对象,可以修改其属性使得各对象不同
3.5.5 原型模式的适用场景
  • 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改

  • 系统要保存对象的状态,而对象的状态变化很小

  • 需要避免使用分层次的工厂类来创建分层次的对象

3.5.6 原型模式的扩展

通过原型管理器,管理所有可以复制的对象

将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。

java 复制代码
public class PrototypeManager {
    private Hashtable prototypeTable=new Hashtable();  
                                                                    //Hashtable存储原型对象
    public PrototypeManager() {
        prototypeTable.put("A", new ConcretePrototypeA());
        prototypeTable.put("B", new ConcretePrototypeB());
    }   
    public void add(String key, Prototype prototype) {
        prototypeTable.put(key,prototype);
    } 
    public Prototype get(String key) {
        Prototype clone = null;
        clone = ((Prototype)prototypeTable.get(key)).clone(); //克隆方法创建新对象
        return clone;
    }    
}

3.6 单例模式

3.6.1 单例模式的定义

动机:如何确保一个类只有一个实例并且这个实例易于被访问

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类

3.6.2 单例模式的分析与实现
  • 唯一实例:为了防止客户端程序使用构造函数来创建多个对象,可以将构造函数声明为私有的,这样客户端程序就不可以使用它来创建任何对象。

  • 提供访问:通过创建一个全局变量和一个getInstance()静态方法,用来让外部客户端访问

  • 分类:

**懒汉式:**该模式只在你需要对象时才会生成单例对象(比如调用getInstance方法)

java 复制代码
public class Singleton1 {
    private static Singleton1 singleton = null;
    private Singleton1() {}
    public static Singleton1 get() {
        if (singleton == null) {
            singleton = new Singleton1();
        } else {
            System.out.println("此对象已经被创建过了");
        }
        return singleton;
    }
}

**饿汉式:**该模式在类被加载时就会实例化一个对象。

java 复制代码
public class Singleton2 {
    private static Singleton2 singleton = new Singleton2();
    private Singleton2() {}
    public static Singleton2 get() {
        return singleton;
    }
}
懒汉式 饿汉式
实例化 外部调用时 类加载时
线程安全 不安全 安全
执行效率 较低 较高
内存 不浪费 浪费
3.6.3 单例模式的案例

在操作系统中,打印池(Print Spooler)是一个用于管理打印任务的应用程序,通过打印池用户可以删除、中止或者改变打印任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。现使用单例模式来模拟实现打印池的设计。

java 复制代码
public class Printer {
    private static Printer printer = null;

    private Printer() {}

    public static Printer getInstance() {
        if (printer == null) {
            System.out.println("获取打印机资源");
            printer = new Printer();
        } else {
            try {
                System.out.println("打印机资源已被占用");
                int x = 1/0;
            } catch (Exception e) {
                throw new RuntimeException("等待打印机释放资源");
            }
        }
        return printer;
    }

    public void manage() {
        System.out.println("打印机正常工作");
    }

    public void revoke() {
        printer = null;
    }
}
3.6.4 单例模式的优缺点
优点 缺点
1.提供唯一实例的受控访问 1.扩展困难
2.节约资源 2.单例类的职责过重,违背"单一职责原则"
3.允许可变数目的实例 3.由于自动垃圾回收机制,可能会导致共享的单例对象的状态丢失
3.6.5 单例模式的适用场景
  • 系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象

  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例

相关推荐
学会沉淀。5 分钟前
Docker学习
java·开发语言·学习
如若1237 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
初晴~37 分钟前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark1 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding3 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
V+zmm101343 小时前
基于小程序宿舍报修系统的设计与实现ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·ssm
文大。3 小时前
2024年广西职工职业技能大赛-Spring
java·spring·网络安全
一只小小翠3 小时前
EasyExcel 模板+公式填充
java·easyexcel