我可以使用Serializable
接口,ObjectOutputStream
和ObjectInputStream
类来使用Java序列化/反序列化,并且可能在实现{的类中添加readObject
和writeObject
{1}}是否是原型模式的有效实现?
这个问题不是来讨论使用复制构造函数是否优于序列化/反序列化。
我知道Prototype Pattern概念(来自维基百科,强调我的):
原型模式是软件开发中的创新设计模式。当要创建的对象类型由原型实例确定时使用它,该实例被克隆以生成新对象。此模式用于:
避免客户端应用程序中的对象创建者的子类,就像抽象工厂模式那样。
避免以标准方式创建新对象的固有成本(例如,使用'new'关键字),当它对于给定的应用程序来说过于昂贵时。
从这个Q / A:Examples of GoF Design Patterns in Java's core libraries,BalusC解释说,只有当类实现Object#clone
接口(标记接口类似于{{1)时,Java中的原型模式才由Cloneable
实现。序列化/反序列化对象)。使用这种方法的问题在博客文章/相关问答中注明:
所以,另一个选择是使用复制构造函数来克隆你的对象(DIY方式),但这无法实现我上面强调的文本的原型模式:
避免以标准方式创建新对象的固有成本(例如,使用'new'关键字)
AFAIK创建对象而不调用其构造函数的唯一方法是通过反序列化,如此问题的接受答案示例中所述:How are constructors called during serialization and deserialization?
所以,我只是询问是否通过Serializable
使用对象反序列化(并知道你正在做什么,将必要的字段标记为Serializable
并理解这个过程的所有含义)或类似的方法将是原型模式的正确实现。
注意:我不认为解组XML文档是这种模式的正确实现,因为它调用了类构造函数。也可能在解组JSON内容时也会发生这种情况。
人们会建议使用对象构造函数,在使用简单对象时我会介意这个选项。这个问题更倾向于深度复制复杂的对象,我可能有5个级别的对象要克隆。例如:
ObjectOutputStream
假设我想要/需要复制transient
的实例。在这种情况下,我不认为复制构造函数会适用。在这种情况下,拥有大量复制构造函数的IMO似乎不是一个好的设计。
答案 0 :(得分:13)
直接使用Java对象序列化并不是Prototype模式,但可以使用序列化来实现模式。
Prototype模式将复制的责任放在要复制的对象上。如果直接使用序列化,则客户端需要提供反序列化和序列化代码。如果您拥有或计划编写所有要复制的类,则可以轻松将责任移交给这些类:
Prototype
接口,扩展Serializable
并添加实例方法copy
PrototypeUtility
定义具体类copy
,该方法在一个位置实现序列化和反序列化AbstractPrototype
的抽象类Prototype
。将其copy
方法委托给PrototypeUtility.copy
。需要Prototype
的类可以实现Prototype
本身并使用PrototypeUtility
来完成工作,也可以只扩展AbstractPrototype
。通过这样做,它还宣称它是安全的Serializable
。
如果您不拥有要复制其实例的类,则无法完全遵循Prototype模式,因为您无法将复制的责任转移到这些类。但是,如果这些类实现Serializable
,您仍然可以通过直接使用序列化来完成工作。
关于复制构造函数,这些是复制其知道类的Java对象的好方法,但是它们不符合Prototype模式的要求,客户端不应该知道它的对象实例的类正在复制。不知道实例类但希望使用其复制构造函数的客户端必须使用反射来查找构造函数,该构造函数的唯一参数与其所属的类具有相同的类。这很难看,客户端无法确定它找到的构造函数是一个复制构造函数。实现接口可以干净地解决这些问题。
维基百科评论Prototype模式避免了创建新对象的成本似乎误导了我。 (我在“四人帮”的描述中没有看到任何内容。)维基百科创建的对象的示例是一个对象,它列出了文本中单词的出现,当然这些对象的查找成本很高。但是设计程序是愚蠢的,因此获取WordOccurrences实例的唯一方法是实际分析文本,特别是如果您因某种原因需要复制该实例。只需给它一个构造函数,其中包含描述实例整个状态的参数,并将它们分配给它的字段或复制构造函数。
因此,除非您正在使用隐藏其合理构造函数的第三方库,否则请忘记该性能。原型的重点是
答案 1 :(得分:1)
我对这部分要求感到困惑:
注意:我不认为解组XML文档是正确的 因为调用类构造函数,所以实现了这种模式。 也可能在解组JSON内容时也会发生这种情况。
我知道您可能不想实现复制构造函数,但是您将始终拥有常规构造函数。如果这个构造函数由库调用那么它有什么关系呢?此外,Java中的对象创建很便宜。我已经使用Jackson来编组/解组Java对象并取得了巨大的成功。它具有高性能,并且具有许多可能对您的案例非常有用的强大功能。您可以按如下方式实施深度复印机:
import com.fasterxml.jackson.databind.ObjectMapper;
public class MyCloner {
private ObjectMapper cloner; // with getter and setter
public <T> clone(T toClone){
String stringCopy = mapper.writeValueAsString(toClone);
T deepClone = mapper.readValue(stringCopy, toClone.getClass());
return deepClone;
}
}
请注意,Jackson将自动使用Beans(getter + setter对,no-arg构造函数)。对于破坏该模式的类,它需要额外的配置。关于此配置的一个好处是它不需要您编辑现有的类,因此您可以使用JSON进行克隆,而无需知道正在使用JSON的代码的任何其他部分。
我喜欢这种方法与序列化的另一个原因是它更人性化可调试(只需查看字符串以查看数据是什么)。此外,还有大量工具可供使用JSON:
而Java序列化的工具并不是很好。
此方法的一个缺点是,默认情况下,默认情况下,原始对象中的重复引用在复制的对象中将是唯一的。这是一个例子:
public class CloneTest {
public class MyObject { }
public class MyObjectContainer {
MyObject refA;
MyObject refB;
// Getters and Setters omitted
}
public static void runTest(){
MyCloner cloner = new MyCloner();
cloner.setCloner(new ObjectMapper());
MyObjectContainer container = new MyObjectContainer();
MyObject duplicateReference = new MyObject();
MyObjectContainer.setRefA(duplicateReference);
MyObjectContainer.setRefB(duplicateReference);
MyObjectContainer cloned = cloner.clone(container);
System.out.println(cloned.getRefA() == cloned.getRefB()); // Will print false
System.out.println(container.getRefA() == container.getRefB()); // Will print true
}
}
鉴于有几种方法可以解决这个问题,每种方法各有利弊,我认为没有一种“正确”的方式来实现Java中的原型模式。正确的方法在很大程度上取决于您自己编写的环境。如果您的构造函数执行繁重的计算(并且无法绕过它们),那么我认为您没有太多选择,只能使用反序列化。否则,我更喜欢JSON / XML方法。如果不允许使用外部库并且我可以修改我的bean,那么我将使用Dave的方法。
答案 2 :(得分:1)
你的问题非常有趣Luiggi(我之所以投票赞成,因为这个想法很棒),你不会说出你真正关心的事情。所以我会尝试回答我所知道的事情并让你选择你认为有争议的东西:
优点:
缺点:
我认为这就是我现在想到的所有内容,如果突然出现任何新方面,我会添加评论:)
当然,与调用构造函数相比,我不知道将对象写为字节的速度有多快。答案应该是批量写/读测试
但这个问题值得思考。
答案 3 :(得分:-1)
有些情况下,使用复制构造函数创建新对象不同于以标准方式创建新对象&#34;&#34;。您的问题中的维基百科链接中解释了一个示例。在该示例中,要使用构造函数WordOccurrences(text,word)创建新的WordOccurrence,我们需要执行重量级计算。如果我们使用复制构造函数WordOccurrences(wordOccurences),我们可以立即得到该计算的结果(在维基百科中,使用克隆方法,但原理是相同的。)