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 单例模式的适用场景
-
系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象
-
客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例