Refactoring: Improving the Design of Existing Code, Second Edition (Garner McCloud's Library) by Martin Fowler

Refactoring: Improving the Design of Existing Code, Second Edition (Garner McCloud's Library) by Martin Fowler

Author:Martin Fowler
Language: eng
Format: epub
Publisher: Addison-Wesley Professional
Published: 2019-07-15T00:00:00+00:00


Another approach is to return a read-only proxy for the data structure. Such a proxy could raise an exception if the client code tries to modify the underlying object. Some languages make this easy, but it’s a pain in JavaScript, so I’ll leave it as an exercise for the reader. I could also take a copy and recursively freeze it to detect any modifications.

Dealing with the updates is valuable, but what about the readers? Here there are a few options.

The first option is to do the same thing as I did for the setters. Extract all the reads into their own functions and move them into the customer data class.

class CustomerData…

Click here to view code image

usage(customerID, year, month) { return this._data[customerID].usages[year][month]; }

top level…

Click here to view code image

function compareUsage (customerID, laterYear, month) { const later = getCustomerData().usage(customerID, laterYear, month); const earlier = getCustomerData().usage(customerID, laterYear - 1, month); return {laterAmount: later, change: later - earlier}; }

The great thing about this approach is that it gives customerData an explicit API that captures all the uses made of it. I can look at the class and see all their uses of the data. But this can be a lot of code for lots of special cases. Modern languages provide good affordances for digging into a list-and-hash [mf-lh] data structure, so it’s useful to give clients just such a data structure to work with.

If the client wants a data structure, I can just hand out the actual data. But the problem with this is that there’s no way to prevent clients from modifying the data directly, which breaks the whole point of encapsulating all the updates inside functions. Consequently, the simplest thing to do is to provide a copy of the underlying data, using the rawData method I wrote earlier.

class CustomerData…

Click here to view code image

get rawData() { return _.cloneDeep(this._data); }

top level…

Click here to view code image

function compareUsage (customerID, laterYear, month) { const later = getCustomerData().rawData[customerID].usages[laterYear][month]; const earlier = getCustomerData().rawData[customerID].usages[laterYear - 1][month]; return {laterAmount: later, change: later - earlier}; }

But although it’s simple, there are downsides. The most obvious problem is the cost of copying a large data structure, which may turn out to be a performance problem. As with anything like this, however, the performance cost might be acceptable—I would want to measure its impact before I start to worry about it. There may also be confusion if clients expect modifying the copied data to modify the original. In those cases, a read-only proxy or freezing the copied data might provide a helpful error should they do this.

Another option is more work, but offers the most control: Apply Encapsulate Record recursively. With this, I turn the customer record into its own class, apply Encapsulate Collection (170) to the usages, and create a usage class. I can then enforce control of updates by using accessors, perhaps applying Change Reference to Value (252) on the usage objects. But this can be a lot of effort for a large data structure—and not really needed if I don’t access that much of the data structure.



Download



Copyright Disclaimer:
This site does not store any files on its server. We only index and link to content provided by other sites. Please contact the content providers to delete copyright contents if any and email us, we'll remove relevant links or contents immediately.