首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >对象反序列化是在Java中实现原型模式的正确方法吗?

对象反序列化是在Java中实现原型模式的正确方法吗?
EN

Stack Overflow用户
提问于 2014-05-27 07:10:06
回答 4查看 1.7K关注 0票数 7

TL;DR

我是否可以使用Serializable接口、ObjectOutputStreamObjectInputStream类使用Java序列化/反序列化,并可能在实现Serializable的类中添加readObjectwriteObject作为原型模式的有效实现?

备注

这个问题不是来讨论是否使用复制构造函数比序列化/反序列化更好。

我知道原型模式的概念(来自维基百科,强调我的):

原型模式是软件开发中的一种创造性设计模式。当要创建的对象类型由原型实例确定时使用,该实例被克隆以生成新对象。此模式用于:

  • 避免客户机应用程序中对象创建者的子类,就像抽象工厂模式所做的那样。
  • 避免了以标准方式创建新对象的内在成本(例如,在给定的应用程序花费过高的情况下使用'new‘关键字)

从这个Q/A:Java核心库中的GoF设计模式示例中,BalusC解释了只有当类实现Cloneable接口(标记接口类似于用于序列化/反序列化对象的Serializable )时,Java中的原型模式才是由Object#clone实现的。使用这种方法的问题在博客文章/相关Q/中有如下的说明:

因此,另一种选择是使用复制构造函数克隆对象( DIY方式),但这不能实现我前面强调的文本的原型模式:

避免了以标准方式创建新对象的固有成本(例如,使用'new‘关键字)

在不调用对象构造函数的情况下创建对象的唯一方法是反序列化,正如这个问题的公认答案的示例所指出的:在序列化和反序列化期间如何调用构造函数?

因此,我只是问通过ObjectOutputStream使用对象反序列化(并且知道您在做什么,将必要的字段标记为transient并理解这个过程的所有含义)还是类似方法将是原型模式的适当实现。

注意:我不认为解组XML文档是这种模式的正确实现,因为它调用了类构造函数。在解组JSON内容时,可能也会发生这种情况。

人们会建议使用对象构造函数,当使用简单对象时,我会介意这个选项。这个问题更适合于深度复制复杂对象,其中我可能有5个级别的对象要克隆。例如:

代码语言:javascript
运行
AI代码解释
复制
//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实例。我不认为复制构造函数会适用于这种情况。在这种情况下,拥有大量的复制构造函数似乎不是一个好的设计。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 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实例的唯一方法是实际分析文本,这将是愚蠢的,特别是当您出于某种原因需要复制该实例时。只需给它一个构造函数,其中包含描述实例的整个状态并将它们分配给它的字段的参数,或者一个复制构造函数。

因此,除非您正在使用隐藏其合理构造函数的第三方库,否则请忘记性能考证。原型的要点是:

  • 它允许客户端在不知道对象实例类的情况下复制对象实例,并且
  • 它在没有创建工厂层次结构的情况下实现了这个目标,就像用AbstractFactory模式实现相同的目标一样。
票数 14
EN

Stack Overflow用户

发布于 2014-06-03 07:19:00

我对你这一部分的要求感到困惑:

注意:我不认为解组XML文档是这种模式的正确实现,因为它调用了类构造函数。在解组JSON内容时,可能也会发生这种情况。

我知道您可能不希望实现复制构造函数,但是您将始终有一个常规构造函数。如果这个构造函数是由一个库调用的,那么有什么关系呢?此外,在Java中创建对象很便宜。我使用Jackson来编组/解编组Java对象,并取得了很大的成功。它具有很强的表现力,并且有许多令人敬畏的特性,可能对您的情况非常有帮助。您可以按以下方式实现深度复印机:

代码语言:javascript
运行
AI代码解释
复制
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的工具:

  1. 联机JSON格式化程序
  2. Veiw JSON作为基于HTML的网页

然而,Java序列化工具并不是很好。

这种方法的一个缺点是,在默认情况下,原始对象中的重复引用在默认情况下将在复制的对象中唯一。下面是一个示例:

代码语言:javascript
运行
AI代码解释
复制
 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的方法。

票数 1
EN

Stack Overflow用户

发布于 2014-06-04 14:19:08

你的问题真的很有趣,Luiggi (我投票赞成它是因为这个想法很棒),这是一个可怜的,你没有说出你真正关心的。所以我试着回答我所知道的,让你选择你认为有争议的:

  • 优势:
代码语言:javascript
运行
AI代码解释
复制
- 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. 

  • 缺点:
代码语言:javascript
运行
AI代码解释
复制
- 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)

我想这就是我现在所想到的,如果有什么新的方面突然出现,我会加一句评论:)

当然,与调用构造函数相比,我不知道以字节形式编写对象的速度有多快。答案应该是大规模的写/读测试。

但这个问题值得思考。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23892962

复制
相关文章
Java中类注释规范
如上图所示,点击右侧的+,新建Live Template,然后编辑如上图,将应用范围设为Java,如果只设comment,无法智能提示,且必须/*再按tab才行。如果有一些 $var$,可以 编辑变量 在IntelliJ IDEA中,打出的部分就会智能提醒,Enter后恩Tab即可。
用户8983410
2021/10/07
1.8K0
Kotlin中的常用类及其使用
代码的初始化工作由它负责,在调用主构造函数之前执行,这部分理论上可以进行任何工作,但建议类的初始化赋值可以放在这,其余的最好由其他专门的地方处理,采用init关键字
wresource
2023/01/31
1.1K0
【Kotlin】常用的 Kotlin 类 ② ( 枚举类 | 枚举类定义函数 | 密封类 )
Kotlin 中使用 枚举类 定义常量 , 枚举类定义格式如下 : 枚举常量 都是 枚举类 的 实例对象 ;
韩曙亮
2023/03/30
1.1K0
Java 中枚举类的使用
在日常写项目时,很多数据字典常量都需要定义和使用,同时在 Java 面试中,枚举也是一个绕不开的话题,这篇文章就来详细介绍一下枚举的定义以及使用。 01  【什么是枚举类?】 枚举类型在 C# 或 C++ 、 java 、 VB 等一些编程语言中是一种基本数据类型而不是构造数据类型。 而在C语言中则是一种构造数据类型。它用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。 枚举类的定义就是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内,使用枚举可以很方便地定义数据常量
老九君
2022/09/02
1.7K0
Java 中枚举类的使用
Java中Scanner类的使用
设A级为85分以上(包括85);B级为70分以上(包括70分);C级为60分以上(包括60分);D级为60分以下。
算法与编程之美
2023/01/03
8550
Java中Scanner类的使用
Java中的Reference类使用
Java 2 平台引入了 java.lang.ref 包,这个包下面包含了几个Reference相关的类,Reference相关类将Java中的引用也映射成一个对象,这些类还提供了与垃圾收集器(garbage collector)之间有限的交互。
huofo
2022/03/17
7220
Kotlin中级(9)- - - Kotlin类之数据类、密封类、内部类.md
上面的代码我们可以看到结构出来的变脸可以直接拿来用,比如数据体Leaf中的size属性,componentN函数群会按照数据体Leaf中属性声明的顺序,从component1到component4和size、color、shape、及vein一一对应。
Hankkin
2018/09/30
1.2K0
Java的类/方法/字段注释详解
一个程序的可读性,关键取决于注释。如果一个程序想二次开发,要读懂前面的程序代码,就必须在程序中有大量的注释文档,所以对于一个优秀的程序员来说,学会在程序中适当地添加注释是非常重要的。
JavaEdge
2020/05/26
3.2K0
匿名类中在Json中使用
匿名类 1. 第一步:定义一个类,类中有三个属性Id。Name.Height 属性类型根据“=”右边的值来推断 2. 第二步:创建这个类的对象,然后,用变量p1去指向它 3. var 表示根据右边的类型去推断var的类型
静心物语313
2020/03/24
3.1K0
匿名类中在Json中使用
【Kotlin】Kotlin Sealed 密封类 ( 密封类声明 | 密封类子类定义 | 密封类特点 | 代码示例 )
1 . 密封类作用 : 定义一个密封类 , 该类只能有有限个指定的子类 , 不能在其它文件定义其它类型子类 ;
韩曙亮
2023/03/27
1.5K0
Java中时间类中的Data类与Time类
上面我们了解了Date类,我们知道,他是一个比较老的类,且不是线程安全的,所以,我们目前基本上是使用他的升级版LocalDate。
JanYork_简昀
2022/04/11
1.8K0
Java中时间类中的Data类与Time类
About Kotlin-Kotlin中的类1About Kotlin(1)
因为是从Java的角度来学习Kotlin,在Java中,类作为第一等公民。故学习Kotlin,也先从其的类开始。
deep_sadness
2018/08/30
1.3K0
About Kotlin-Kotlin中的类2About Kotlin(2)
使用sealed修饰符修饰。其实是一组类的集合。可以用来表示受限的类的继承结构。 其也可以有子类,所有子类也必须在相同的文件中声明。 密封类从某种意义上说,它们是枚举类的扩展:枚举类型的值集也受到限制,但每个枚举常量仅作为单个实例存在,而密封类的子类可以包含多个实例并包含状态。这样又具备了枚举不具备的灵活性。
deep_sadness
2018/08/30
2.6K0
转向Kotlin——数据类和封闭类
数据类是Kotlin的一个语法糖。Kotlin编译器会自动为数据类生成一些成员函数,以提高开发效率。
蜻蜓队长
2018/08/03
9630
java中indexOf()类的基本使用
String s1 = new String("Good Good Study ,Day Day up !");
用户7886150
2021/04/06
1.3K0
关于Java中Stack类的使用
为什么不用Stack类?\n\n《Java编程思想》第四版一书中明确不建议我们使用java.util.Stack类,一直保留只是为了兼容以前的版本,在17.13.3中提到了原因。主要是因为:\n\nStack类是继承自Vector类,而不是使用Vector来实现Stack,这就产生了一个问题,Vector上可以使用的方法Stack类都可以使用,所以很容易破坏栈应有的规则。在本书的11.8中提到建议使用LinkedList实现栈。\n\nPS:Stack是为了专门实现栈而创建的类,作者在文中也提到“竟然不是用Vector来构建Stack,而是继承Vector”,可见作者也认为额外的操作是使用Stack类所不能容忍的。但这和建议使用LInkedList不能同一看待,因为一个是专用类,而另外一个是建议实现Stack的一种手段(不能因为可以实现Stack而不能有其他的操作,LinkedList毕竟不是为了Stack而生)。\n\n- 为什么不用Vector类?\nVector由于是线程安全的,所以在单线程的时候效率会叫ArrayList更低。在Java 1.2 出现ArrayList之后基本上就使用起来代替Vector。在多线程中ArrayList可以使用Collectiuons.synchronized方法来保证多线程环境下的安全使用。在本书17.13.1中提到另一个原因就是又长又难记的方法名。
用户1148830
2018/01/03
1.5K0
java 中对 BigDecimal 类使用详解
因为不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。
一写代码就开心
2022/06/26
1.2K0
java 中对 BigDecimal 类使用详解
Java中Date类与Calendar类
Java中有两个与时间相关的常用类:Date类与Calendar类,开始在做题目的时候一无所知,通过查阅网上的资料有了一些基本的了解.(其实也可以查看Java的API,这是十分有效的学习方法,以后要加强这种意识).
用户8224910
2021/01/26
6440
在Android开发中怎样使用Application类
自己独立开发项目才发现以前对Application类并不是十分了解,现在开始直接搭建一个新项目的框架才重新踩过这个坑。
1025645
2018/08/23
2.2K0
在Android开发中怎样使用Application类
点击加载更多

相似问题

从java类中检索kotlin注释

115

如何使用类注释Kotlin

10

Jackson @JsonProperty注释在kotlin数据类中的使用

75

Java注释处理器-注释Kotlin类单元测试

12

生成Kotlin方法/类注释

20
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文