3

A conceptual question... I have a XML configuration file used to set various behaviours in an application (colours, supressing help prompts etc). On starting the application, the XML is deserialized into memory, in a class whose structure matches the XML file's structure.

For info, the application is a C# WinForms GUI application - though this question I think is really applicable to any OO language.

I'd like to implement a set of GUI forms to edit this config file, and just looking for some 'best-practice' guidance.

I've laid out the config editor forms using a tree view and panel forms that dynamically show/hide as you navigate through the tree (just like Visual Studio's Tools > Options menu).

The pages contain tick boxes, path text boxes etc.

So the crux of the question - if a user starts editing the options, unchecking boxes, changing file paths etc - I don't want to edit the actual config object yet, because the user might select 'Cancel', instead of 'Ok' on the window.

As such, I'm trying to decide on the appropriate design for this - is this a sensible use case to take a deep copy of the config upon loading (call this the 'local' copy), then if the user hits 'Ok' on the window, the local copy is written back into the main config, else it's just discarded.

Or, is there a better solution to this sort of use case?

2
  • I can think of 3 main things that make deep copies tricky: 1) cyclic references within your object graph, 2) references to external state, 3) other non-copyable resources (such as locks, file handles, etc.). A configuration object would a "pure value" that's a tree (thus acyclic), self-contained, and not contain locks and other non-copyable resources. So you're golden, deep copy to your heart's content! Perhaps even keep an undo-stack of multiple states of the config object, so you can undo step-by-step.
    – Alexander
    Commented Nov 19, 2023 at 21:07
  • In this particular case , you will want to write all the changes at once (apply), or none (cancel). Thus it does make sense to modify a copy and replace the original in a single go.
    – S.D.
    Commented Nov 20, 2023 at 8:23

4 Answers 4

5

Yes.

(1.) Working with a deep copied object, prior to the user committing to changes, is a good fit for your requirements.

(2.) Suppose your requirements mention that when user selects Dark / Light theme, the screen colors shall immediately update to illustrate that choice. Then you'd want to make a deep copy which you hang onto in the background, while current config remains live. Later the user clicks Apply and you discard the no longer needed copy, or clicks Cancel and you deep copy the old config back onto the live one.

(3.) A fancier datastructure would be a log of actions. For example, user ticked 2nd box, unticked 3rd box, replaced old value in 5th field with a new string, that sort of thing. This supports individual Undo using ctrl-Z. And undoing enough actions would satisfy the intent of Cancel.

3
  • Thanks @J_H, being relatively new to OO languages I get the feeling that deep copies are somewhat taboo based on what I've read, so it's good to have somebody agree this is an appropriate use case! Commented Nov 19, 2023 at 19:34
  • To make this design somewhat user friendly, I've create a copy constructor in my configuration class, and an 'overwrite' method that takes an instance of it's own class in as an argument, and copies from this too. So other than the 'pain' of ensuring this constructor and method contain all the members, it works nicely. Commented Nov 19, 2023 at 19:36
  • 2
    @jwilo - "I get the feeling that deep copies are somewhat taboo based on what I've read" - nah, not a taboo; if the objects just form a simple hierarchical data structure that branches in a relatively straightforward way and doesn't have any tricky joins (two objects having the same child) or loops (single or multi-level circular references) within itself, or references to some large chunk of data that you don't want copied, then it's not really a problem. It's with those tricky cases where you have to be careful what you want to copy, where to stop when traversing the graph, etc. Commented Nov 19, 2023 at 19:49
3

There is nothing inherently wrong in making a copy of some object when you need one. If the object is a complex, hierarchical one, with mutable child objects, this copy operation has to be a "deep copy", otherwise it would become faulty. Note the whole necessity of making copies vanishes when the objects involved are designed as immutable ones; when only the child objects are immutable, shallow copies are sufficient. Still, there might be reasons why one chose a mutable design, so let's assume your case is subject to this conditions.

But even with a complex mutable configuration object in mind, one may not need deep copies for this specific use case - at least not for the parts described in the question.

The way you described the config editor, I think the whole state of the configuration object is fully loaded into related UI controls - boolean values are mapped to the Checked field of some check box, strings and numbers to the Text field of a text box, other values may be mapped to the state of some radio buttons or combo boxes. As a result, the state of the UI controls reflect the config's state.

When users "edit the configuration", they manipulate the state of the UI controls, not the state of the configuration object. And when they hit 'Ok', a new config object will be created from the UI state and replace the old one, which can be persisted to XML. So the data flow looks like

[XML file] --(loaded from disk)--> 
[complex config object] --(loaded into UI control's state) --> 
[UI state] --(edited by user) -->
[changed UI state] --(user presses Ok)-->
[new complex config object] --(saved to disk)-->
[changed XML file]

... and as fas as I can see, there is no need in this data flow to make a direct copy (deep or not) of the [config object].

Of course, one might say loading an object into UI controls and back is a form of (deep) copying, but it is not one where you need to create a copy constructor or "deep copy operation" which operates directly on the configuration object.

Note this has nothing to do where and when validation happens. You can still have validations on certain pages in the UI (maybe ones which block the "Ok" button), validations in the object which may be called after the user presses Ok, but before, and maybe some functional overlap between both if necessary.

The need for implementing a full copy can arise, for example, when you want to compare the original config object with the new one (maybe for detecting if all the UI operations done by the user changed effectively nothing). Then one can make a copy of the original object before the editing starts, and compare it to the newly created object afterwards.

8
  • This is an interesting view, would be great to hear more of your thoughts on it - I would have thought coupling UI controls directly to config state would generally be thought of as poor design. An example I can think of for why - is I have a method that validates a configuration as OK, for example options that should not be permitted together. This is necessary for example as the XML file could in theory just be edited in a text editor, even though the GUI shouldn't allow it to happen. If I weren't to use an intermediate config object, this becomes solely the UIs responsibility. 1/2 Commented Nov 19, 2023 at 20:58
  • I've previously spent months working on a GUI wrapper around a SQL database however, and I was religiously encouraged by co-workers not to enforce data validity through the UI, but through the database stored procedures instead, which made perfect sense to me as the database could then far more easily (and robustly) be ported to another/wrapped behind a different GUI and data integrity would still be ensured. This is a similar case, though perhaps subtly different as it would be poor UI design if the UI allowed a user to set an invalid config that would be rejected in the first place... 2/2 Commented Nov 19, 2023 at 21:01
  • @jwilo: "I would have thought coupling UI controls directly to config state" - not sure what you mean by this, but when you load the config state into the UI and edit it there, all editing happens obviously decoupled from the config object. Note in what I wrote above, there Is still such a config object, and it can contain any kind of validation you require.
    – Doc Brown
    Commented Nov 19, 2023 at 21:18
  • 1
    "I would have thought coupling UI controls directly to config state" - what I was thinking here in my example, was that having to write a validation for the UI, in addition to the already existing validation that would run against the config object, would be bad practice for reason of it being duplicated functionality, and thus twice the risk of error, twice the maintenance burden etc... though in reality, this needs to happen anyway unless I'm ok with a "config rejected" or similar message when trying to apply the config. Commented Nov 19, 2023 at 21:33
  • "We have indeed a Deep Copy operation for the configuration class in our code, but only for certain use cases beyond what you described." - If you can, though I understand if you can't, it would be good to understand what these additional use cases are that justified the deep copy op in your team's view, just for educational value alone. Commented Nov 19, 2023 at 21:34
2

So this is where I feel it would help to introduce you to a bunch of OO UI terminology, such as "model-view-controller" and "model-view-viewmodel", as well as the concept of a "DTO" (data transfer object).

In this terminology, the copy you are editing is a "model". The controls in the UI are attached to a "view", which translates from the model and orders changes through the "controller". The place you store it persistently is a "repository". Usually this is a database but there is no reason it can't be an XML file.

Opinions differ as to whether it's better to have "smart" objects that know about the repository and can write themselves back, or "dumb" ones that just store the data and require an explicit call to persist.

Now, if the repository was a database, people would not thing twice about making a "deep copy" from the database into some DTOs to represent the model. After all, you don't want to hit the database every time you draw the screen. So why should it be different if the "repository" is not a database, but in fact another object?

-1

Forget about “deep copy”. If you want to edit an object in your GUI you make a copy that is just deep enough to be edited without affecting the original, and no deeper. You want an editable copy.

For example, if there is a list of items the customer ordered, they can edit the list, but probably not the description of the items that’s in some database. The customers name and address may be editable somewhere else, but unless you can edit it as part of editing an order, your order just copies a reference to the customer. If I edit my name and address at the same time on another screen, that would even update the display as part of the order.

Not the answer you're looking for? Browse other questions tagged or ask your own question.