设计模式-原型模式的实现[JAVA]
The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. This pattern is used to:
- avoid subclasses of an object creator in the client application, like the factory method pattern does.
- avoid the inherent cost of creating a new object in the standard way (e.g., using the ‘new’ keyword) when it is prohibitively expensive for a given application.简单来说,原型模式是通过已有的实例来构建相同的实例的一种创建型的设计模式,主要解决如果对象创建成功高时,节约创建的时间和资源,同时是运行时通过实例本身创建相同对象的一种手段。
引言
通常我们创建对象的时候是通过new
关键字进行对象创建,但是如果对象的创建的过程比较复杂,例如要通过数据库/文件/其他系统等获取对应的配置信息。对于这种情况我们就可以考虑原型模式,通过已有的对象在运行时克隆一个对象处理。
原型prototype模式
在学习原型模式前,我们需要了解一下什么浅拷贝、什么是深拷贝。
浅拷贝
在JAVA语言中,除了基本数据类型外,对象实例都是引用类型。浅拷贝就是对于引用对象直接引用(拷贝原有对象内存的地址),在拷贝的过程中共同引用了一块内存,对于基本类型拷贝的是其值。
深拷贝
深拷贝除了对基础类型使用值拷贝外,对于对象实例也是直接复制了一份,复制的对象有自己的内存空间,与原有对象没有直接联系。
- 示例1 分别演示浅拷贝和深拷贝
以下例子是通过获取数据库配置获取配置文件后,生成相关配置对象。模拟配置对象创建比较复杂的场景。
/**
* 原型设计demo
* @Author: Shiraki
* @Date: 2020/9/23 13:39
*/
public class DemoPrototype {
/**
* 展示浅拷贝
*/
public static void showShallowCopy () {
System.out.println("===show shallowCopy");
Config config = Config.createConfig("jdbc:oracle:thin:@10.0.0.1:1521:it");
config.disConfig();
Config config1 = (Config) config.clone();
// 修改config 但是config1也发生了变化
config.mdfConfig("AAAA", "111111111111111111");
config1.disConfig();
}
/**
* 展示深拷贝
*/
public static void showDeepCopy () {
System.out.println("===show deepCopy");
Config config = Config.createConfig("jdbc:oracle:thin:@10.0.0.1:1521:it");
config.disConfig();
Config config1 = (Config) config.DeepClone();
// 修改config 但是config1也发生了变化
config.mdfConfig("AAAA", "111111111111111111");
config1.disConfig();
}
public static void main(String[] args) {
showShallowCopy();
showDeepCopy();
}
}
class Config implements Cloneable{
// 配置
private Map<String, String> items = new HashMap<>();
private Config() {
}
/**
* 创建配置
* @param dbUrl
* @return
*/
public static Config createConfig(String dbUrl) {
Config config = new Config();
config.initConfig(dbUrl);
return config;
}
/**
* 初始化配置
* @param dbUrl
* @return
*/
private void initConfig(String dbUrl) {
{
// 模拟从其他系统获取配置 只能30分钟获取一次配置
items.put("AAAA", "1111");
items.put("BBBB", "2222");
}
}
/**
* 修改配置
* @param k
* @param v
*/
public void mdfConfig(String k, String v) {
items.replace(k, v);
}
/**
* 展示配置
*/
public void disConfig() {
items.forEach((k, v) -> System.out.println("[k = " + k + ", v = " + v + "]"));
}
/**
* 通过Cloneable实现浅copy
* @return
*/
@Override
public Object clone() {
Config config = new Config();
config.items = items;
return config;
}
/**
* 深copy实现
* @return
*/
public Object DeepClone() {
Config config = new Config();
items.forEach((k, v) -> config.items.put(k, v));
return config;
}
}
通过以上例子可以看出,对于Map来说浅拷贝是拷贝Map的引用,深拷贝是需要重新创建map对象,并通过已有的键值对来初始化新建的map对象。
类图
其实在示例1中已经已经演示了通过
clone
方法来创建了对象,因为在JAVA中Object
对象有clone
方法(一般为浅拷贝)。如果需要进行深拷贝,新增一个DeepClone
即可。
- 示例2 演示原型模式应用的场景
试想这么一个场景,对于应用系统来说,加载配置,或者修改配置的时候,经常需要整个配置的修改同时生效,而不是一个一个修改后一个一个的生效。有这样需求的原因是往往存在相互关联的配置,要么都生效要么都不生效,
如何解决这种需求呢?
这时候需要引入主从两套配置。修改从配置完成后,再切换主从。由于读取配置信息比较麻烦,这时候就可以考虑使用原型模式,通过已有的配置对象克隆一个从对象后,修改从对象。
-
示例类图
-
代码示例如下:
/**
* 原型设计demo
* @Author: Shiraki
* @Date: 2020/9/23 13:39
*/
public class DemoPrototype {
/**
* 模拟切换配置
*/
public static void showSwitchConfig() {
// 初始化配置
Config masterConfig = Config.createConfig("jdbc:oracle:thin:@10.0.0.1:1521:it");
// 备份(copy)一份配置
Config slaveConfig = (Config) masterConfig.DeepClone();
// 先修改配置从配置
slaveConfig.mdfConfig("AAAA", "slave");
System.out.println("===show master");
masterConfig.disConfig();
System.out.println("===show slave");
slaveConfig.disConfig();
// 进行主备切换
masterConfig = slaveConfig;
slaveConfig.mdfConfig("AAAA", "slave");
System.out.println("===show master");
}
public static void main(String[] args) {
showSwitchConfig();
}
}
class Config implements Cloneable{
// 配置
private Map<String, String> items = new HashMap<>();
private Config() {
}
/**
* 创建配置
* @param dbUrl
* @return
*/
public static Config createConfig(String dbUrl) {
Config config = new Config();
config.initConfig(dbUrl);
return config;
}
/**
* 初始化配置
* @param dbUrl
* @return
*/
private void initConfig(String dbUrl) {
{
// 模拟从其他系统获取配置 只能30分钟获取一次配置
items.put("AAAA", "1111");
items.put("BBBB", "2222");
}
}
/**
* 修改配置
* @param k
* @param v
*/
public void mdfConfig(String k, String v) {
items.replace(k, v);
}
/**
* 展示配置
*/
public void disConfig() {
items.forEach((k, v) -> System.out.println("[k = " + k + ", v = " + v + "]"));
}
/**
* 通过Cloneable实现浅copy
* @return
*/
@Override
public Object clone() {
Config config = new Config();
config.items = items;
return config;
}
/**
* 深copy实现
* @return
*/
public Object DeepClone() {
Config config = new Config();
items.forEach((k, v) -> config.items.put(k, v));
return config;
}
}
以上是通过使用深拷贝来完成模拟应用的配置切换的功能演示。
总结
对于原型模式的实现只需要增加一个 clone
或者copy
的方法来实现运行时的对象克隆,可以使用浅拷贝,也使用深拷贝,这取决具体的业务场景。
需要关注点是对于深拷贝消耗的资源往往是大于浅拷贝,因此需要在实际编码过程中进行权衡。