gpt4 book ai didi

design-patterns - 复制对象有哪些好的设计模式?

转载 作者:行者123 更新时间:2023-12-04 07:20:53 24 4
gpt4 key购买 nike

我需要复制一个具有很深的成员变量层次结构的对象(即该对象具有几个不同类型的成员,每个成员都有几个不同类型的成员,等等)。进行深层复制需要在许多类中实现clone()方法,这对于我的应用程序来说似乎有些过时了。

我想出的解决方案很简单,但是我想知道这是否是个坏主意。我的解决方案如下:


定义一个空接口Settings
定义一个名为IsCopyable的接口,该接口具有方法getSettings()applySettings(Settings s)
对于实现X的给定类IsCopyable,将编写实现接口Settings的类,其中包含必须复制到类X的对象的“设置”。 (我通常将此类嵌套在类X中,因此我具有X.Settings implements Settings,但这可以在其他地方完成。)


然后,复制类X的实例:

X myX = new X();
// Stuff happens to myX.
// Now we want to copy myX.
X copyOfX = new X();
copyOfX.applySettings(myX.getSettings());


即,要复制给定的对象,请创建该对象的新实例,然后在要复制的对象上调用 getSettings(),将生成的 Settings对象作为值传递给新实例上的 applySettings()。 (当然,复制可以包装在名为copy()的成员中。)

这对于我的特定问题非常有效,但是我在做蠢事吗?我(差一点)重新发明了已经存在的东西吗?

提前致谢。

克里斯

最佳答案

我想对您的最后一个问题回答“是”,只是为了好玩:-)但是我知道您想要一些论点,所以让我尝试:


如果一个概念(在这里复制)对于一个对象是唯一的,那么您可以考虑将两者合并在同一个类中(这将在类本身中实现该方法)。例如:


如果对于某些对象,有多种复制方法,具体取决于...,则复制确实值得在原始对象之外进行。
如果对于所有对象,都有一种独特的复制方法,那么创建一个不同的对象进行复制真的有很多价值吗? (例如,如果总的复杂性太大,难以阅读和理解,那么最好将其分解)。

我一直很难通过调用显式构造函数来创建副本。原因是构造函数是唯一不能继承的方法(不包括静态变量...),因此它们不能通用(不可能为所有可复制对象提供唯一的接口)。这意味着在您的所有应用程序中,没有通用代码可以复制对象。每次尝试时,总有一段时间我确实需要以一般方式进行复制。
显式调用构造函数还意味着将来我将无法替换子类。假设您有一个对变量B起作用的算法A。如果给A子类B的子类,则当A复制其B变量(其实际类型为C)时,将使用B构造函数创建该副本。它不会属于同一类,并且可能会改变行为。因此,通过调用构造函数进行复制受到极大限制。
显式调用构造函数意味着无法使用接口。您可以在很多地方阅读有关接口的值...例如,在我们的应用程序中,没有直接在我们的代码中实例化许多对象,而是向Locator / Factory请求一个接口(或类),其中包含许多可能的优点(如果您的应用需要这一天):


如果我想在特定的上下文中将B子类替换为每个A对象,例如在某些自动化测试中衡量代价高昂的操作的性能,这非常容易。我们还需要用一个子类代替HashMaps,以找到一个插入到Map中并随后在序列化期间导致错误的不可修复对象。
如果我有接口,则创建对象仅涉及我的常规代码中的接口(Factory除外)。因此,我对具体的类完全没有依赖,就象您所知道的那样好(对接口的依赖具有更少的传递性依赖,并且更易于模拟以进行测试)。
在我们的例子中,这个工厂实际上是Spring支持的,因此实例化是通过Spring完成的。根据需要采取了许多其他步骤(代理,拦截,初始化方法...)。





在我们的应用程序中,我们通常最终会创建一个(或几个)克隆器。给定一个顶级对象,他们知道如何对其进行深层复制。通用克隆器的优点是代码只编写一次,对于整个应用程序都是通用的。通常,它也可以在应用程序之间重用...

实现:例如,使用反射,您可以递归地获取每个成员。但是,有许多陷阱要避免:


循环:A引用B引用C引用A。因此,我保留了已复制的对象的Map,引用了副本。当我复制对象但发现它已经存在于地图中时,我不会复制它,而是替换它已经完成的复制。
特殊类型:枚举不应该被复制(还有一些其他静态对象)。某些库类也可能会出现问题,因此您可以保留不想复制的特殊类的Set或Map,或以特殊方式复制。
您可能在最后的领域遇到麻烦...


具体情况

通常,某些特定对象的默认方式不正确。我们既需要通用的实现,又需要根据需要对其进行重载的可能性。对于他们,我们使用以下代码:


如果可以修改对象,则可以让他们实现特定的CustomizedCopier接口,并且该方法中的代码负责根据需要进行复制。如果通用代码看到此界面,则它不会执行任何操作。
如果我们不能修改对象(JRE,第三方代码...),则我们有一个Map / Registry Map,用于存储特定的类以及我们想要的特定复印机。请注意,有时这种技巧有时也并非用于定制复制,而是仅用于某些特殊用例,因为它可能会使复制对象的方式过载,因此通常仅用于某些特殊用例。




实际上,我通常会得到几个克隆器。例如,克隆数据持久实体通常使用此知识来进行不同的克隆(例如,id和audit字段可以设置为null)。

我通常也有一个执行相同的依赖项搜索的类,但出于其他需要:


toString()一个复杂的对象,用于为其创建调试字符串。
equals()和hashCode()实现(如果需要)。
将所有属性的对象图重新初始化为其默认值(考虑以多选项卡的巨大形式实现“重置”按钮)。
检查对象图中某处是否存在对象
控制对象图的可序列性(在某些情况下使用HttpSession进行序列化的典型用例;在开发中,我们显式检查对象,检测无法序列化的对象,并向开发人员提供最佳错误消息) 。
...



请注意,多线程通常需要复制。理想情况下,在多线程环境中重用的对象是不可变的。如果没有,通常建议克隆以确保程序全局一致性。




性能

使用反射并不总是那么快。通常,对于大量且经常使用的复制,我们将在对象本身中实现复制。但是我们发现只有少数几个类需要复制并且数量很大,因此这只是一般机制的一个例外,我们随后才插入(我在前面的文章中介绍了如何使用寄存器)当它们变得有用时。

关于design-patterns - 复制对象有哪些好的设计模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1386448/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com