设计模式之原型模式

本文详细介绍了Java中原型模式的概念和实现,包括浅拷贝和深拷贝的区别,以及Spring框架中原型Bean的创建。通过代码示例展示了如何使用原型模式创建和克隆对象,以及如何在实际开发中提高代码的扩展性和效率。
摘要由CSDN通过智能技术生成

问题

现在又一只羊,姓名为tom,年龄1,颜色是白色。请编写程序创建和tom羊属性完全相同的10只羊。

最直观的实现方式

  1. UML类图
    在这里插入图片描述
  2. 代码实现
    Sheep代码:
    public class Sheep {
    
        private String name;
        private String color;
        private int age;
    
        public Sheep(String name, String color, int age) {
            this.name = name;
            this.color = color;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getColor() {
            return color;
        }
        public void setColor(String color) {
            this.color = color;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    Client代码:
    public class Client {
    
        public static void main(String[] args) {
            Sheep sheep = new Sheep("tom", "white", 1);
            Sheep sheep2 = new Sheep(sheep.getName(), sheep.getColor(), sheep.getAge());
            System.out.println(sheep);
            System.out.println(sheep2);
        }
    }
    
  3. 以上方式的优缺点
    1. 优点是毕竟好理解,简单易操作。
    2. 在创建新的对象的时候,总是需要重新获取原始对象的属性。如果创建的对象毕竟复杂,效率会低下。
    3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态,不够灵活。
  4. 改进思路:Java 中 Object 类是所有类的根类。Object 类提供了一个 clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现Cloneable接口。该接口表示该类能够复制且具有复制的能力。

原型模式

基本介绍
  1. 原型模式(Prototype)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
  2. 原型模式是一种创建型设计模式,允许一个对象在创建另一个可定制的对象时,无需知道如何创建的细节。
  3. 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()。
代码实现

Sheep 类:

public class Sheep implements Cloneable {

    private String name;
    private String color;
    private int age;

    public Sheep(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public Object clone() {

        Sheep sheep = null;
        try {
            sheep = (Sheep)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

Sheep类要实现Cloneable接口,这样才能实现对象的克隆。
Client 类:

public class Client {

    public static void main(String[] args) {

        Sheep sheep = new Sheep("tom", "white", 1);
        Sheep sheep1 = (Sheep) sheep.clone();
        System.out.println("sheep " + sheep + sheep.hashCode());
        System.out.println("sheep1" + sheep1 + sheep1.hashCode());
    }
}

用 new 创建的对象作为原型。每次调用调用对象的clone方法,即可克隆出一个新的对象。这就原型模式。

原型模式的优点

比如,当Sheep类的属性增加或者减少时,调用clone()方法的代码不用修改,即克隆对象的代码不用修改。需要修改的是用 new创建对象时的代码。明显会比我们开始使用的代码,有了更好的扩展性。

原型模式在Spring框架中的应用

  1. Spring中原型Bean的创建,就是原型模式的应用。
  2. AbstractBeanFactory 源码:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    ...
	/**
	 * Return an instance, which may be shared or independent, of the specified bean.
	 * @param name the name of the bean to retrieve
	 * @param requiredType the required type of the bean to retrieve
	 * @param args arguments to use when creating a bean instance using explicit arguments
	 * (only applied when creating a new instance as opposed to retrieving an existing one)
	 * @param typeCheckOnly whether the instance is obtained for a type check,
	 * not for actual use
	 * @return an instance of the bean
	 * @throws BeansException if the bean could not be created
	 */
	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}

			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
					.tag("beanName", name);
			try {
				if (requiredType != null) {
					beanCreation.tag("beanType", requiredType::toString);
				}
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

				// Create bean instance.
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new ScopeNotActiveException(beanName, scopeName, ex);
					}
				}
			}
			catch (BeansException ex) {
				beanCreation.tag("exception", ex.getClass().toString());
				beanCreation.tag("message", String.valueOf(ex.getMessage()));
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
			finally {
				beanCreation.end();
			}
		}

		return adaptBeanInstance(name, beanInstance, requiredType);
	}
	...
}

从doGetBean方法中,我们学习到,创建Bean的时候,用到了原型模式。如果这个条件mbd.isPrototype()成立,那么我们创建出来的Bean就是利用原型设计模式创建出来的Bean。

深拷贝和浅拷贝

浅拷贝的介绍
  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值的传递,也就是将该属性值赋值一份给新的对象属性。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递。也就是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量的值。
  3. 浅拷贝是使用默认的clone()方法来实现的。
深拷贝的介绍
  1. 复制对象的所有基本数据类型的成员变量值。
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
代码实现深拷贝

DeepCloneableTarget 类:

import java.io.*;

public class DeepCloneableTarget implements Serializable, Cloneable {

    private String cloneName;
    private String cloneClass;

    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    // 因为该类的属性,都是String,因此我们使用默认的clone方法就可以完成
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

DeepPrototype 类:

import java.io.*;

public class DeepPrototype implements Serializable, Cloneable {

    public String name;
    public DeepCloneableTarget deepCloneableTarget; //引用类型

    public DeepPrototype() {
        super();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        // 这里完成基本数据类型和string的克隆
        deep = super.clone();
        // 处理一下引用类型
        DeepPrototype deepPrototype = (DeepPrototype)deep;
        deepPrototype.deepCloneableTarget = (DeepCloneableTarget)this.deepCloneableTarget.clone();
        return deepPrototype;
    }

    // 深拷贝
    public Object deepClone() {
        // 创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this); // 当前这个对象以对象流的方式输出

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            DeepPrototype copyObject = (DeepPrototype)ois.readObject();
            return copyObject;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return null;
    }
}

DeepPrototype 类实现了两种类型的深拷贝。

  1. 第一种是重写clone方法,让对象属性的类也实现clone方法。这样,调用DeepPrototype 类的clone方法时,也会调用对象属性的clone方法。如果对象属性类的成员是非基本属性类型,那么还需要实现Cloneable接口。
  2. 第二种方式是,对象序列化,然后反序列化,实现对象的深拷贝,相对来说更简单一些。

原型模式的注意事项和细节

  1. 创建新的对象毕竟复制时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态。
  3. 如果原始对象发生变化(增加或减少属性),其它克隆对象也会发生相应的变化,但是无需修改代码。
  4. 在事项深度克隆时,可能需要比较复杂的代码。
  5. 缺点:需要为每一个类都配备一个克隆方法,这对全新的类来说不难。但是对已有类进行改造的时候,需要修改其源代码,违背了OCP原则。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值