设计模式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 单例模式的适用场景
  • 系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象

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

相关推荐
言慢行善8 分钟前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星14 分钟前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟32 分钟前
操作系统之虚拟内存
java·服务器·网络
Tong Z34 分钟前
常见的限流算法和实现原理
java·开发语言
凭君语未可37 分钟前
Java 中的实现类是什么
java·开发语言
He少年39 分钟前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新1 小时前
myeclipse的pojie
java·ide·myeclipse
迷藏4941 小时前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏4941 小时前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
qq_433502181 小时前
Codex cli 飞书文档创建进阶实用命令 + Skill 创建&使用 小白完整教程
java·前端·飞书