3.4 建造者模式
3.4.1 建造者模式的定义
动机:方便创建复杂对象(一个对象中包含多个成员变量)
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
3.4.2 建造者模式的分析与实现
- 产品类:复杂对象,里面包含多个成员变量
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;}
}
- 建造者类:内含两类方法,一个是建造构件,一个是返回建造对象
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{@Overridepublic void buildPartA() {product.setPart1("部件A");}@Overridepublic void buildPartB() {product.setPart2("部件B");}@Overridepublic Product getResult() {return product;}
}
- 指挥者类:负责构建的建造次序,并返回产品类
public class Director {private Builder builder;public Director(Builder builder) {this.builder = builder;}public Product construct() {builder.buildPartA();builder.buildPartB();return builder.getResult();}
}
- 客户端类:只需要指定具体的建造者即可,无需关心产品的具体组装过程
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的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。
- 产品类
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;}
}
- 建造者类
public interface MealBuilder {public void buildFood();public void buildDrink();public Meal getMeal();
}public class MealBuilder1 implements MealBuilder {private Meal meal = new Meal();@Overridepublic void buildFood() {meal.setFood("汉堡");}@Overridepublic void buildDrink() {meal.setDrink("可乐");}@Overridepublic Meal getMeal() {return meal;}
}public class MealBuilder2 implements MealBuilder {private Meal meal = new Meal();@Overridepublic void buildFood() {meal.setFood("鸡肉卷");}@Overridepublic void buildDrink() {meal.setDrink("果汁");}@Overridepublic Meal getMeal() {return meal;}
}
- 指挥者类
public class KFCWaiter {private MealBuilder mealBuilder;public void setMealBuilder(MealBuilder mealBuilder) {this.mealBuilder = mealBuilder;}public Meal constructMeal() {mealBuilder.buildFood();mealBuilder.buildDrink();return mealBuilder.getMeal();}
}
- 客户端
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异常
public class ShallowPrototype implements Cloneable{public String name;@Overridepublic ShallowPrototype clone() {try {return (ShallowPrototype) super.clone();} catch (CloneNotSupportedException e) {throw new RuntimeException(e);}}
}
- 深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将被复制
如果需要实现深克隆,可以通过序列化等方式来实现。在Java语言中,序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。**通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,从而实现深克隆。**需要注意的是,能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
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);}}
}
- 克隆的结果
(1)对任何的对象x,都有x.clone()!=x,即克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
3.5.3 原型模式的案例
由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、日期、附件等),某系统中现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。使用原型模式设计该系统。
浅克隆:
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);}}
}
深克隆:
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 原型模式的扩展
通过原型管理器,管理所有可以复制的对象
将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。
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方法)
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;}
}
**饿汉式:**该模式在类被加载时就会实例化一个对象。
public class Singleton2 {private static Singleton2 singleton = new Singleton2();private Singleton2() {}public static Singleton2 get() {return singleton;}
}
懒汉式 | 饿汉式 | |
---|---|---|
实例化 | 外部调用时 | 类加载时 |
线程安全 | 不安全 | 安全 |
执行效率 | 较低 | 较高 |
内存 | 不浪费 | 浪费 |
3.6.3 单例模式的案例
在操作系统中,打印池(Print Spooler)是一个用于管理打印任务的应用程序,通过打印池用户可以删除、中止或者改变打印任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。现使用单例模式来模拟实现打印池的设计。
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 单例模式的适用场景
-
系统只需要一个实例对象,或者因为资源消耗太大而只允许创建一个对象
-
客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例