设计模式
目录:
一、单例模式
二、工厂模式
三、抽象工厂模式
四、原型模式
五、建造者模式
注:学习视频:黑马程序员Java设计模式
创建者模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
创建模式分为:
- 单例模式
- 工厂方法模式
- 抽象工厂模式
- 原型模式
- 建造者模式
一、单例设计模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1.1 单例设计模式结构
单例模式主要有以下角色:
- 单例类。只能创建一个实例的类
- 访问类。使用单单例类
1.2 单例模式的实现
单例模式设计模式分两种:
饿汉式:类加载就会导致该单例对象被创建。
懒汉式:类加载不会导致单例对象被创建,而是首次调用该对象时才会被创建
1.2.1 饿汉式
1. 饿汉式:方式1(静态变量方式):
静态成员变量方式可以实现,是因为静态变量只有在类加载的时候被初始化一次。
/**
* 饿汉式(静态成员变量)
*/
public class Singleton {
// 将构造器私有化
private Singleton(){}
// 创建静态成员变量
private static Singleton instance = new Singleton();
// 提供获取静态成员变量方法
public static Singleton getInstance(){
return instance;
}
}
说明:
该方式在成员位置声明
Singleton
类型的静态变量,并创建Singleton
类的对象instance
。instance
对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
2. 饿汉式:方式2(静态代码块):
/**
* 饿汉式(静态代码块)
*/
public class Singleton {
// 私有化构造方法
private Singleton(){}
// 声明静态实例对象
private static Singleton instance;
static {
instance = new Singleton();
}
// 提供获取静态成员变量方法
public static Singleton getInstance(){
return instance;
}
}
说明:
该方式在成员位置声明
Singleton
类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式1基本上一样,当然该方式也存在内存浪费问题。
1.2.2 懒汉式
1. 懒汉式-方式1(线程不安全)
/**
* 懒汉式(线程不完全)
*/
public class Singleton {
//私有化构造器
private Singleton(){}
// 声明静态变量
private static Singleton instance;
public static Singleton getInstance() {
if(instance == null){
// 线程1进入,又切换到了线程2 会导致两个线程进入,破坏了单例模式
instance = new Singleton();
}
return instance;
}
}
说明:
线程不完全问题,如果一个线程进入了判断是否为
null
,这个时候被切换到另一个进程,这样的话,有两个进入了判断,创建了两个实例,就不是单例模式了。
2.懒汉式-方式2(线程安全)
/**
* 懒汉式(线程不完全)
*/
public class Singleton {
//私有化构造器
private Singleton(){}
// 声明静态变量
private static Singleton instance;
public static synchronized Singleton getInstance() {
if(instance == null){
// 线程1进入,又切换到了线程2 会导致两个线程进入,破坏了单例模式
instance = new Singleton();
}
return instance;
}
}
说明:
线程安全的方式只是在线程不安全的方式基础上,在获取实例的方法上加上了
synchronized
关键字,保证了线程安全。
3.懒汉式-方式3(双重检查锁)
/**
* 懒汉式双重检查锁
*/
public class Singleton {
// 私有化构造方法
private Singleton(){}
// 声明静态变量
private static Singleton instance;
// 获取方法
public static Singleton getInstance() {
// 第一判断,如果instance不等于null,就不进入抢锁阶段,
// 这样的话可以节省抢锁的时间,提高性能
if(instance == null){
synchronized (Singleton.class){
// 抢到锁之后再进入判断是否为null 不是则直接返回instance
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
说明:
双重检查锁,实际上是通过线程安全锁的思想上来改进的,将抢锁时机改变,如果
instance
为null
,则不进入抢锁,直接返回,这样一来节省了抢锁的时间,所有性能也提高了。但是仍然存在一定的问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
改进:
可以使用
volatile
关键字可以保证可见性和有序性
/**
* 懒汉式双重检查锁
*/
public class Singleton {
// 私有化构造方法
private Singleton(){}
// 声明静态变量
private static volatile Singleton instance;
// 获取方法
public static Singleton getInstance() {
// 第一判断,如果instance不等于null,就不进入抢锁阶段,
// 这样的话可以节省抢锁的时间,提高性能
if(instance == null){
synchronized (Singleton.class){
// 抢到锁之后再进入判断是否为null 不是则直接返回instance
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
4.懒汉式-方式4(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于
JVM
在加载外部类的过程
中, 是不会加载静态内部类
的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
/**
* 懒汉式(静态内部类)
*/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 定义静态内部
public static class SingletonInter{
// 声明静态变量 防止外面修改 instance 可以加上final
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance(){
return SingletonInter.INSTANCE;
}
}
说明:
第一次加载的时候,由于
INSTANCE
是写在静态内部类里面的,所有不会加载该变量,这样就实现了饿汉式,因为SingletonInter
是静态内部类,在第一次调用的时候,对他进行加载,后面调用将不会再加载,所以是线程安全的。
1.2.3 枚举方式
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
/**
* 枚举方式
*/
public enum Singleton {
INSTANCE;
}
说明:
枚举方式属于饿汉式
1.3 存在问题及解决方案
破坏单例模式:使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。
有两种方式,分别是序列化和反射。
1.3.1 序列化和反序列化
采用静态内部类演示,其他单例模式实现方式也存在同样问题
- Singleton类
/**
* 懒汉式(静态内部类)
必须实现序列化接口
*/
public class Singleton implements Serializable {
// 私有构造方法
private Singleton(){}
// 定义静态内部
public static class SingletonInter{
// 声明静态变量 防止外面修改 instance 可以加上final
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance(){
return SingletonInter.INSTANCE;
}
}
- Test测试类
实现破坏单例模式的方法,将获取的单例对象
instance
写入到文件中,再从文件中读取对象,连续读取两次获取两个instance
,这两个对象不是同一个对象,就破坏了单例模式。
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
writeObjectToFile();
readObjectFromFile();
readObjectFromFile();
/**
* 输入如下,不是同一个对象,已经破坏了单例模式
*com.zcc.singleton.dome5.Singleton@312b1dae
* com.zcc.singleton.dome5.Singleton@7530d0a
*/
}
/**
*将对象写入文件
* @throws IOException
*/
public static void writeObjectToFile() throws IOException {
Singleton instance = Singleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\安逸i\\Desktop\\a.txt"));
oos.writeObject(instance);
}
/**
* 从文件中读取出对象
* @throws IOException
* @throws ClassNotFoundException
*/
public static void readObjectFromFile() throws IOException, ClassNotFoundException {
ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("C:\\Users\\安逸i\\Desktop\\a.txt"));
Singleton instance = (Singleton) ooi.readObject();
System.out.println(instance);
}
}
- 序列化、反序列方式破坏单例模式的解决方法
在Singleton类中添加
readResolve()
方法,在底层中反序列化的时候会检查该对象是否有readResolve()
,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。其实在底层原理中反序列化的时候会检查该对象。
- **修改Singleton类,添加readResolve方法 **
/**
* 懒汉式(静态内部类)
加上了 readResolve() 方法
*/
public class Singleton implements Serializable {
// 私有构造方法
private Singleton(){}
// 定义静态内部
public static class SingletonInter{
// 声明静态变量 防止外面修改 instance 可以加上final
private static final Singleton instance = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance(){
return SingletonInter.instance;
}
// 添加 readResolve() 方法,解决序列化和反序列化问题
private Object readResolve(){
return SingletonInter.instance;
}
}
- 再次执行测试类返回结果
com.zcc.singleton.dome5.Singleton@3764951d
com.zcc.singleton.dome5.Singleton@3764951d
1.3.2 反射
- Singleton类
/**
* 懒汉式(静态内部类)
*/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 定义静态内部
public static class SingletonInter{
// 声明静态变量 防止外面修改 instance 可以加上final
private static final Singleton instance = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance(){
return SingletonInter.instance;
}
}
- Test类
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 获取Singleton字节码对象
Class<Singleton> aClass = Singleton.class;
// 获取无参构造器
Constructor<Singleton> noArg = aClass.getDeclaredConstructor();
//由于无参构造器是私有的 所有要取消访问检查
noArg.setAccessible(true);
// 通过反射调用无参构造器,获得对象
Singleton instance = noArg.newInstance();
Singleton instance1 = noArg.newInstance();
// 判断是否为同一对象
System.out.println(instance == instance1); // 返回false 破坏了单例模式
}
}
- 反射方式破坏单例模式的解决方法
思路: 既然是通过调用无参构造器来创建的对象,那就可以从无参构造器下手,判断是否已经存在对象,为了解决线程安全的问题,采用
synchronized
来实现线程安全
- 改进后的Singletonl类
public class Singleton {
// 定义一个全局静态变量来判断是否已经有 Singleton对象
private static boolean flag = false;
// 私有构造方法
private Singleton(){
// 为了实现线程安全,给代码上锁
synchronized (Singleton.class){
// 如果已经有了 Singleton 类 则抛出异常
if(flag){
throw new RuntimeException("不能创建多个对象");
}
flag = true;
}
}
// 定义静态内部
public static class SingletonInter{
// 声明静态变量 防止外面修改 instance 可以加上final
private static final Singleton instance = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance(){
return SingletonInter.instance;
}
}
- 再次运行Test类
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.zcc.singleton.dome6.Test.main(Test.java:20)
Caused by: java.lang.RuntimeException: 不能创建多个对象
at com.zcc.singleton.dome6.Singleton.(Singleton.java:16)
… 5 more
二、 工厂模式
2.1 概述
需求:设计一个咖啡店点餐系统。
设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。
具体设计如下:
三种工厂模式:
简单工厂模式(不属于GOF的23种经典设计模式)
工厂模式
抽象工厂模式
2.2 简单工厂模式
简单工厂不是一张设计模式,像是一种变成习惯
2.2.1 结构
简单工厂包含如下角色:
- 抽象产品:定义产品的规范,描述产品的主要特性和功能
- 具体产品:实现或集成抽象产品子类
- 具体工厂:提供创建产品的方法,调用者通过该方法获取产品
2.2.2 实现
工厂类代码:
public class SimpleFactory {
// 传入咖啡类型,创建对应的咖啡
// 具体工厂创建具体产品,返回抽象产品
public Coffee createCoffee(String typeName){
Coffee coffee = null;
if(typeName.equals("americano")){
coffee = new AmericanoCoffee();
}else if(typeName.equals("latte")){
coffee = new LatteCoffee();
}else {
throw new RuntimeException("没有你点的咖啡");
}
return coffee;
}
}
说明:
创建了工厂之后,对于咖啡店来说,就可以通过名字来直接调用工厂的方法来获得对象,将创建过程封装了起来,降低了耦合度,但是也伴随的新的耦合产生,对于咖啡店和工厂对象之间的耦合,工厂对象和商品对象的耦合。
2.2.3 优缺点
- 优点:
封装了创建对象的过程,客户可以通过参数直接获得对象,这样的话,减少了客户代码修改的可能,不需要客户来直接创建对象。
- 缺点:
还是违背了“开闭原则”,如果需要增加一个具体产品,则需要修改的工厂类,违背了开闭原则。
2.3 工厂方法模式
针对于简单工厂模式,使用工厂方法模式就可以完美解决,完成遵循开闭原则。
2.3.1 概念
定义一个用于创建对象的接口,让子类决定实例化哪一个产品类都西昂,工厂方式使一个产品的实例化延迟到了子类。
2.3.2 结构
工厂方法模式的主要角色:
- 抽象工厂:提供创建产品接口,调用者通过它的是实现类,来创建具体工厂来创建具体产品
- 具体工厂:主要是是实现抽象工厂的方法,创建对应的具体产品
- 抽象产品:定义产品的规范,描述了产品的功能和特性
- 具体产品: 实现抽象产品,由具体工厂来创建,它和具体工厂是一对一的关系
2.3.4 实现
代码如下:
- 抽象工厂:
public interface CoffeeFactory {
Coffee createCoffee();
}
- 具体工厂
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
- 咖啡店(CoffeeStore)
public class CoffeeStore {
private CoffeeFactory factory;
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee() {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
- CoffeeStore
public class CoffeeStore {
AbstractCoffeeFactory abstractCoffeeFactory;
public CoffeeStore(AbstractCoffeeFactory coffeeFactory){
this.abstractCoffeeFactory = coffeeFactory;
}
public void orderCoffee(String typeName){
Coffee coffee = abstractCoffeeFactory.createCoffee();
coffee.drink();
coffee.addMilk();
coffee.addSugar();
}
}
说明:
可以看到,这样设计的话,如果需要增加一个产品类时,虽然使不会改变原来的代码,但是每增加一个产品,就要增加产品类,和对应的具体工厂类,如果类的数量比较大,就会造成类爆炸问题。
2.3.5 优缺点
优点:
- 用户只要只要知道具体的工厂类,就可以直接获得产品对象,无须关心产品的创建过程
- 在系统需要增加新的产品的时候,只需要增加产品和具体的工厂类,无须修改原来的代码。
缺点:
- 没增加一个产品,就要增加产品类和对应的产品具体工厂类,增加了系统的复杂度。
三、抽象工厂模式
工厂方法类所生产的都是同一级别/种类的产品,但是在实际生活中可能会出现需要生产同一品牌的产品,如果使用工厂方法,就需要创建多个抽象工厂,具体工厂,我们为什么不将同一品牌的产品聚合起来,放在一个工厂里面,这样可以满足一个工厂生产出多级别的产品。
3.1 概念
1、抽象工厂模式是一种提供一个创建一组相关或相互依赖对象接口,且访问类无需指定所要的产品的具体类就能访问得到同族的不等级的产品的模式结构
2、抽象工厂实际上工厂方法模式的升级版,工厂方法模式只生产一个等级的产品,而抽象工厂可生产多个级别的产品。
3.2 结构
抽象工厂模式主要角色:
- 抽象工厂:提供创建产品的接口,它包含多个创建产品的方法,可以创建不同级别的产品。
- 具体工厂:实现抽象工厂,创建对应不同级别的产品,并返回。
- 抽象产品:定义了产品的规范,描述了产品主要特性和功能。
- 具体产品:实现抽象产品,并且由具体工厂来创建对象,与具体工厂是一对一的的关系。
3.3 实现
现在咖啡店,不仅要生产咖啡,还要生产甜点,假设生产的甜点是提拉米苏、莫斯蛋糕,提拉秘书和属于同一族,莫斯蛋糕和美式咖啡属于同一族,我们就可以将同一族的两个产品放到同一工厂里面。
- 抽象工厂
/**
* 包含创建两个等级的产品的方法
*/
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
- 具体工厂
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
说明:
如果需要加一个产品族的话,只需要添加一个具体工厂实现抽象工厂即可,不需要修改其他类。
3.4 优缺点
优点:
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
3.5 使用场景
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
3.6模式扩展
3.6.1简单工厂+配置文件解除耦合
可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可
- 抽象产品类和具体产品类
// 抽象产品类
public abstract class Coffee {
public void drink(){};
}
// 两个具体产品类
public class AmericanCoffee extends Coffee{
public void drink(){
System.out.println("美式咖啡");
}
}
public class LatteCoffee extends Coffee {
public void drink(){
System.out.println("拿铁咖啡");
}
}
- bean.properties配置文件
american=com.itheima.pattern.factory.config_factory.AmericanCoffee
latte=com.itheima.pattern.factory.config_factory.LatteCoffee
- CoffeeFactory 工厂类
public class CoffeeFactory {
// 创建集合保存对象
private static Map<String, Coffee> map = new HashMap<>();
// 加载类的时候创建对象放入到集合中
static {
Properties properties = new Properties();
try {
// 加载配置文件
properties.load(new FileInputStream("bean.properties"));
Set<Object> objects = properties.keySet();
for (Object obj :objects) {
// 获取全类名
String className = (String) properties.get(obj);
Class<?> aClass = Class.forName(className);
Coffee coffee = (Coffee) aClass.newInstance();
map.put((String) obj,coffee);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 对外提供获取coffee的方法
public Coffee getInstance(String typeName){
return map.get(typeName);
}
}
- 测试类Client
public class Client {
public static void main(String[] args) {
CoffeeFactory coffeeFactory = new CoffeeFactory();
Coffee latte = coffeeFactory.getInstance("latte");
latte.drink(); // 拿铁咖啡
}
}
3.7JDK源码解析-Collection.iterator方法
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("令狐冲");
list.add("风清扬");
list.add("任我行");
//获取迭代器对象
Iterator<String> it = list.iterator();
//使用迭代器遍历
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
对上面的代码大家应该很熟,使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。我们看通过类图看看结构:
Collection接口是抽象工厂类,ArrayList是具体的工厂类;Iterator接口是抽象商品类,ArrayList类中的Iter内部类是具体的商品类。在具体的工厂类中iterator()方法创建具体的商品类的对象。
另:
1,DateForamt类中的getInstance()方法使用的是工厂模式;
2,Calendar类中的getInstance()方法使用的是工厂模式;
四、 原型模式
4.1 概述
用一个已经创建的实例作为原型,来创建一个与原型对象相同的新对象
4.2 结构
原型模式包含如下角色:
- 抽象原型类: 规定具体原型对象必须实现的
clone()
方法 - 具体原型类: 实现抽象圆形类的
clone()
方法。 - 访问类: 使用具体原型类中的
clone()
方法复制出新的对象
4.3 实现
原型模式分为浅克隆和深克隆
**浅克隆:**创建一个新的对象,新对象的属性和原型对象的完全相同,对于非基本属性类型,仍指向原来所指向的内存地址。
**深克隆:**创建一个新对象,新对象属性中引用的其他对象会被克隆,不再指向原来的地址。
- RealizeType类: 具体事项类
/**
* 具体原型类
*/
public class RealizeType implements Cloneable{
// 属性
private String name;
// 用来判断克隆是不是通过构造方法来创建的
public RealizeType() {
System.out.println("调用了无参构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 重写clone方法 返回当前具体原型类,实现克隆
@Override
public RealizeType clone() throws CloneNotSupportedException {
return (RealizeType) super.clone();
}
// 实现类方法
public void study(){
System.out.println(name + "正在学习");
}
}
- Client类: 测试访问类
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
RealizeType realizeType = new RealizeType();
realizeType.setName("张三");
RealizeType clone = realizeType.clone();
System.out.println(clone == realizeType);
clone.setName("李四");
realizeType.study();
clone.study();
}
}
// 输出如下
// 可以看出克隆不是通过调用构造方法创建
调用了无参构造方法
false // 可见不是同一对象
张三正在学习
李四正在学习
4.4 案例
一个公司中多个员工,采用其中一个员工作为原型类,通过原型模式创建多个员工
代码如下:
/**
* 员工类
*/
public class Employee implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 重写clone方法
@Override
public Employee clone() throws CloneNotSupportedException {
return (Employee) super.clone();
}
// 编写类方法
public void work(){
System.out.println(name + "正在上班...");
}
}
// 测试类
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Employee employee = new Employee();
employee.setName("张三");
Employee clone = employee.clone();
clone.setName("李四");
System.out.println(clone == employee);
clone.work();
employee.work();
}
}
// 输出如下
false // 两者是不同的对象
李四正在上班...
张三正在上班...
4.5 使用场景
- 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
- 性能和安全要求比较高。
4.6 深克隆
深克隆是为了解决克隆的原型对象中存在引用类型的数据,克隆后新对象引用类型属性仍然指向原来的地址,深拷贝可以它是新的地址
- 浅克隆引用类型属性
/**
* 学生类
*/
public class Student {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void study(){
System.out.println(name + "正在上课!!!");
}
}
/**
* 班级类
*/
public class Classes implements Cloneable{
// 引用了student类
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
// 重写clone方法
@Override
public Classes clone() throws CloneNotSupportedException {
return (Classes) super.clone();
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
// 原型对象
Classes classes = new Classes();
Student student = new Student();
classes.setStu(student);
classes.getStu().setName("张三");
// 克隆对象
Classes clone = classes.clone();
clone.getStu().setName("李四");
classes.getStu().study();
classes.getStu().study();
}
}
// 测试结果如下
李四正在上课!!!
李四正在上课!!! // 说明两个student是同一个对象
- 深拷贝引用类型属性
采用对象流来实现深克隆,只需修改测试类,和将原型对象序列化,引用类型数据序列化。
/**
* 测试类
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
// 获取原型对象
Classes classes = new Classes();
classes.setStu(new Student());
classes.getStu().setName("张三");
// 获取输出流对象,将原型对象写入文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\a.txt"));
oos.writeObject(classes);
oos.close();
// 获取输入对象流,读取创建新对象
ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("d:\\a.txt"));
Classes clone = (Classes) ooi.readObject();
clone.getStu().setName("李四");
classes.getStu().study();
clone.getStu().study();
}
}
// 输出结果如下
张三正在上课!!!
李四正在上课!!! // 可见两个student没有指向同一个地址
五、建造者模式
5.1 概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
- 分离了部件的构造(由Builder来负责)和装配(由Director负责),从而可以构造出复杂的对象。适用于:某个对象的构建过程非常复杂。
- 不同的构造器,相同的装配,装配值关注将不同的原件通过Buider创建,而装配器将原件装配起来
- 建造者模式可以将创建部件和装配部件分开,一步一步创建出一个复杂的对象。
5.2 结构
建造者模式包含如下角色:
- 抽象创建者类(Builder): 这个接口实现复杂对象的部件创建规范,并不涉及到具体部件的创建
- **具体创建者类(ConcreateBuilder):**实现Builder接口方法,完成具体产品的创建,并且由指挥者类来调用具体创建者类的方法按照一定的顺序来组装
- **产品类(Product):**要创建的复杂对象
- 指挥者类(Director): 调用具体创建者类创建产品的各个部分,并且按照一定的顺序将组件组装起来,返回完整的对象产品
类图图下:
5.3 实例
假设要创建一辆汽车,是一个非常复杂的对象,我们只写其中的两个部件,轮子(Wheel) 和 椅子(Chair)两个部件, 采用创建者方式。
- 代码如下:
/**
* 汽车类
*/
public class Car {
// 椅子部件,这里使用字符串作为部件,真正使用是传入对象作为部件
private String chair;
private String wheel;
public String getChair() {
return chair;
}
public void setChair(String chair) {
this.chair = chair;
}
public String getWheel() {
return wheel;
}
public void setWheel(String wheel) {
this.wheel = wheel;
}
@Override
public String toString() {
return "Car{" +
"chair='" + chair + '\'' +
", wheel='" + wheel + '\'' +
'}';
}
}
/**
* 抽象创建者
*/
public abstract class Builder {
// 将car对象创建在抽象创建者中,实现类就可以对这个类进行创建部件
Car car = new Car();
abstract void setWheel();
abstract void setChair();
abstract Car createCar();
}
/**
* 奔驰创造者类
*/
public class BCCarBuilder extends Builder{
@Override
void setWheel() {
car.setWheel("奔驰轮胎");
}
@Override
void setChair() {
car.setChair("奔驰座椅");
}
@Override
Car createCar() {
return car;
}
}
/**
* 宝马车创建者
*/
public class BmCarBuilder extends Builder{
@Override
void setWheel() {
car.setWheel("米其林轮胎");
}
@Override
void setChair() {
car.setChair("真皮座椅");
}
@Override
Car createCar() {
return car;
}
}
/**
* 指挥者类
*/
public class Director {
private Builder builder;
// 初始话创建者
public Director(Builder builder){
this.builder = builder;
}
// 构建方法,装配部件
public Car construct(){
builder.setChair();
builder.setWheel();
Car car = builder.createCar();
return car;
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
// 奔驰车创建
BCCarBuilder bcCarBuilder = new BCCarBuilder();
Director director = new Director(bcCarBuilder);
Car car = director.construct();
// 宝马车创建
BmCarBuilder bmCarBuilder = new BmCarBuilder();
Director director1 = new Director(bmCarBuilder);
Car car1 = director1.construct();
System.out.println(car);
System.out.println(car1);
}
}
// 测试结果
Car{chair='奔驰座椅', wheel='奔驰轮胎'}
Car{chair='真皮座椅', wheel='米其林轮胎'}
注意:
可以看出,指挥者类对于产品的创建有着至关重要的作用,Builder只是提供了部件的创建,而真正决定怎么创建的,需要由指挥者类(Director)来决定,当部件比较少的时候,可以将指挥者类合并到抽象创建者类里面
补充:
上诉代码中,对于Car这产品也可以抽取出一个抽象类,让具体类来实现的。可以将耦合度。
/**
* 抽象创建者
*/
public abstract class Builder {
// 将car对象创建在抽象创建者中,实现类就可以对这个类进行创建部件
Car car = new Car();
abstract void setWheel();
abstract void setChair();
abstract Car createCar();
public Car construct(){
this.setWheel();
this.setChair();
Car car = this.createCar();
return car;
}
}
5.4 优缺点
优点:
- 建造者模式封装型好,使用建造者模式可以有效的封装变化,在使用的建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此将业务逻辑封装到指挥者类中对整体来说稳定性更高。
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 建造者模式很容易进行扩展,如果需要增加一个同一创建流程的产品,就只需要一个类来继承Builder类,然后将具体创建者对象交给指挥者对象,就可以完成产品的创建。
缺点:
- 创建者模式要求添加的产品需要有很多的共同点,对应创建流程也需要大致相同,这样的话才可以通过指挥者对象按照一定的流程来完成产品的创建。
5.5 使用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
5.6 模式扩展
建造者模式还可以用来处理一个类的构造器传入很多参数时,可读性差,很容易记不住字段的顺序造成字段赋值出错的问题,可以采用的建造者模式来重构
重构前的代码:
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getMainboard() {
return mainboard;
}
public void setMainboard(String mainboard) {
this.mainboard = mainboard;
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
// 测试类
public class Client {
public static void main(String[] args) {
//构建Phone对象,这个地方很容易赋值错误
Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
System.out.println(phone);
}
}
// 测试结果
Phone{cpu='intel', screen='三星屏幕', memory='金士顿', mainboard='华硕'}
采用创建者模式改进:
/**
* 静态内部创建者类改进后
*/
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
// 将构造方法私有化,采用创建者实现构建
private Phone(Builder builder){
this.cpu = builder.cpu;
this.screen = builder.screen;
this.memory = builder.memory;
this.mainboard = builder.mainboard;
}
// 创建一个静态内部创建者类
public static class Builder{
private String cpu;
private String screen;
private String memory;
private String mainboard;
// 分别创建属性
public Builder setCpu(String val){
cpu =val;
// return this 将 builder 返回,后面可以连写
return this;
}
public Builder setScreen(String val){
screen = val;
return this;
}
public Builder setMemory(String val){
memory = val;
return this;
}
public Builder setMainboard(String val){
mainboard = val;
return this;
}
// 外部类的构造方式已经私有了,所以通过内部类方法返回一个Phone对象
public Phone build(){
// 内部类可以直接调用外部类的私有构造器
// 再将内部类这个对象传给私有构造器,让他来进行构造
return new Phone(this);
}
}
@Override
public String toString() {
return "Builder{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 通过改进后,我们就可以通过调用静态内部类的方法,来实现构造器方法
// 由于内部类每设置一个属性就返回 Builder 所有可以连接赋值
// 并且还可以选择性的构造,不易造成错误
Phone phone = new Phone.Builder()
.setCpu("Intel")
.setScreen("三星")
.setMemory("金士顿")
.setMainboard("华硕")
.build();
System.out.println(phone);
}
}
// 测试结果
Builder{cpu='Intel', screen='三星', memory='金士顿', mainboard='华硕'}
5.7 创建者模式对比
5.7.1 工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。
5.7.2 抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。
建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。