TL;DR
我是否可以使用Serializable
接口、ObjectOutputStream
和ObjectInputStream
类使用Java序列化/反序列化,并可能在实现Serializable
的类中添加readObject
和writeObject
作为原型模式的有效实现?
备注
这个问题不是来讨论是否使用复制构造函数比序列化/反序列化更好。
我知道原型模式的概念(来自维基百科,强调我的):
原型模式是软件开发中的一种创造性设计模式。当要创建的对象类型由原型实例确定时使用,该实例被克隆以生成新对象。此模式用于:
从这个Q/A:Java核心库中的GoF设计模式示例中,BalusC解释了只有当类实现Cloneable
接口(标记接口类似于用于序列化/反序列化对象的Serializable
)时,Java中的原型模式才是由Object#clone
实现的。使用这种方法的问题在博客文章/相关Q/中有如下的说明:
因此,另一种选择是使用复制构造函数克隆对象( DIY方式),但这不能实现我前面强调的文本的原型模式:
避免了以标准方式创建新对象的固有成本(例如,使用'new‘关键字)
在不调用对象构造函数的情况下创建对象的唯一方法是反序列化,正如这个问题的公认答案的示例所指出的:在序列化和反序列化期间如何调用构造函数?
因此,我只是问通过ObjectOutputStream
使用对象反序列化(并且知道您在做什么,将必要的字段标记为transient
并理解这个过程的所有含义)还是类似方法将是原型模式的适当实现。
注意:我不认为解组XML文档是这种模式的正确实现,因为它调用了类构造函数。在解组JSON内容时,可能也会发生这种情况。
人们会建议使用对象构造函数,当使用简单对象时,我会介意这个选项。这个问题更适合于深度复制复杂对象,其中我可能有5个级别的对象要克隆。例如:
//fields is an abbreviation for primitive type and String type fields
//that can vary between 1 and 20 (or more) declared fields in the class
//and all of them will be filled during application execution
class CustomerType {
//fields...
}
class Customer {
CustomerType customerType;
//fields
}
class Product {
//fields
}
class Order {
List<Product> productList;
Customer customer;
//fields
}
class InvoiceStatus {
//fields
}
class Invoice {
List<Order> orderList;
InvoiceStatus invoiceStatus;
//fields
}
//class to communicate invoice data for external systems
class InvoiceOutboundMessage {
List<Invoice> invoice;
//fields
}
比方说,我希望/需要复制一个InvoiceOutboundMessage
实例。我不认为复制构造函数会适用于这种情况。在这种情况下,拥有大量的复制构造函数似乎不是一个好的设计。
发布于 2014-05-29 11:47:36
直接使用Java对象序列化并不完全是原型模式,但是可以使用序列化来实现该模式。
原型模式将复制的责任放在要复制的对象上。如果直接使用序列化,则客户端需要提供反序列化和序列化代码。如果您拥有或计划编写要复制的所有类,则很容易将责任转移到这些类上:
Prototype
接口,它扩展Serializable
并添加实例方法copy
PrototypeUtility
定义具体的类copy
,该方法在一个地方实现序列化和反序列化AbstractPrototype
的抽象类Prototype
。将其copy
方法委托给PrototypeUtility.copy
。需要是Prototype
的类可以实现Prototype
本身并使用PrototypeUtility
来完成工作,也可以只是扩展AbstractPrototype
。通过这样做,它还宣传说它是安全的Serializable
。
如果您不拥有要复制实例的类,则不能完全遵循原型模式,因为您不能将复制的责任转移到这些类。但是,如果这些类实现了Serializable
,那么仍然可以通过直接使用序列化来完成任务。
关于复制构造函数,这是复制您知道的类的Java对象的一种很好的方法,但是它们不满足原型模式的要求,即客户机不需要知道它正在复制的对象实例的类。如果客户端不知道实例的类,但希望使用它的复制构造函数,则必须使用反射找到一个构造函数,该构造函数的唯一参数与它所属的类具有相同的类。这很难看,客户端无法确定它找到的构造函数是一个复制构造函数。实现一个接口可以清晰地解决这些问题。
维基百科评论说,原型模式避免了创建新对象的成本,对我来说似乎是错误的。(我在“四人帮”描述中没有看到这一点。)维基百科关于创建一个对象的例子是一个对象,它列出了文本中出现的单词,当然,要找到这个单词是很昂贵的。但是,如果设计程序使获得WordOccurrences实例的唯一方法是实际分析文本,这将是愚蠢的,特别是当您出于某种原因需要复制该实例时。只需给它一个构造函数,其中包含描述实例的整个状态并将它们分配给它的字段的参数,或者一个复制构造函数。
因此,除非您正在使用隐藏其合理构造函数的第三方库,否则请忘记性能考证。原型的要点是:
发布于 2014-06-03 07:19:00
我对你这一部分的要求感到困惑:
注意:我不认为解组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的方法。
发布于 2014-06-04 14:19:08
你的问题真的很有趣,Luiggi (我投票赞成它是因为这个想法很棒),这是一个可怜的,你没有说出你真正关心的。所以我试着回答我所知道的,让你选择你认为有争议的:
- In terms of memory use, you will get a very good memory consumption by using serialization since it serializes your objects in binary format (and not in text as json or worse: xml). You may have to choose a strategy to keep your objects "pattern" in memory as long as you need it, and persist it in a "less used first persisted" strategy, or "first used first persisted"
- Coding it is pretty direct. There are some rules to respect, but it you don't have many complex structures, this remains maintainable
- No need for external libraries, this is pretty an advantage in institutions with strict security/legal rules (validations for each library to be used in a program)
- If you don't need to maintain your objects between versions of the program/ versions of the JVM. You can profit from each JVM update as speed is a real concern for java programs, and it's very related to io operations (JMX, memory read/writes, nio, etc...). So there are big chances that new versions will have optimized io/memory usage/serialization algos and you will find you're writing/reading faster with no code change.
- You loose all your prototypes if you change any object in the tree. Serialization works only with the same object definition
- You need to deserialize an object to see what is inside it: as opposed to the prototype pattern that is 'self documenting' if you take it from a Spring / Guice configuration file. The binary objects saved to disk are pretty opaque
- If you're planning to do a reusable library, you're imposing to your library users a pretty strict pattern (implementing Serializable on each object, or using transient for dields that are not serializable). In addition this constraints cannot be checked by the compiler, you have to run the program to see if there's something wrong (which might not be visible immediately if an object in the tree is null for the tests). Naturally, I'm comparing it to other prototyping technologies (Guice for example had the main feature of being compile time checked, Spring did it lately too)
我想这就是我现在所想到的,如果有什么新的方面突然出现,我会加一句评论:)
当然,与调用构造函数相比,我不知道以字节形式编写对象的速度有多快。答案应该是大规模的写/读测试。
但这个问题值得思考。
https://stackoverflow.com/questions/23892962
复制