6

I want to keep a dependency decoupled, but at the same time, once it's passed to the constructor, I want to allow changes only through Whatever (in the following example) because changing the dependency from the outside would invalidate the state of the Whatever object that depends on it. So I keep a copy instead:

class Whatever{
    private Dependency d;
    public constructor(Dependency d){
        this.d = d.clone();
    }
    // ...
}

However the dependency is huge so I've decided to avoid the copy; I've removed the clone() and made clear through documentation that once the dependency is passed to this class, it must not be modified from the outside.

Question is: is there a way to avoid the copy and at the same time maintaining proper encapsulation? And with proper encapsulation I mean either immediately error on access attempts from the outside or avoiding the possibility of the access from the outside entirely.

My guess is that I cannot. Not using the Java/C#/etc's OOP interpretation.

So I also ask, are there languages that cover such a case? How do they work?

An option I could have is the following, assuming a language doing reference counting:

class Whatever{
    private Dependency d;
    public constructor(Dependency d){
        this.d = d.isTheOnlyReference() ? d : d.clone();
    }
    // ...
}

new Whatever(new Dependency()); // no clone performed

Dependency d = new Dependency();
new Whatever(d); // clone performed, as Dependency has >= 1 references 
Wes
  • 832
  • 5
  • 12

4 Answers4

9

Sharing mutable state is not an evil you can fix with documentation. Why? Because programmers are amazing, at ignoring documentation.

What you can do is either defensive copy, which you rejected for performance reasons, keep it immutable, which you claim you can't, invalidate Whatever when it's dependency changes, or don't tell anyone else about this dependency so nothing else can change it.

I know two ways to invalidate Whatever:

Make Whatever an observer. When the dependency changes call Whatever.invalidate() so it knows it can't trust that it's dependency is the same.

Store a state counter. The dependency increments it's state counter every time it changes. When Whatever accepts the dependency it copies the counter. When Whatever uses the dependency it checks that the counter hasn't changed. Once it has Whatever knows not to trust its dependency anymore.

That lets you error out if the dependency has been mutated.

If you don't share this dependency then Whatever can control it's changes it self. Nothing else can change the dependency because nothing else knows about it.

Reference counting doesn't help here. What I think you're trying to do is not make extra clones when you've only stored a reference to it in one place.

That is a doable strategy. One that's easier to pull off if the things accepting the reference aren't responsible for deciding to clone.

Let's say I have an Injector class. Injector holds a reference to a Dependency instance. Might be Injector built it in the first place. Injector will never mutate Dependency so it doesn't count as far as sharing.

Injector can pass this instance once without cloning to anything, Whatever or, heh, whatever. After that it must clone. Now things accepting the Dependency reference don't need to worry about cloning and you only clone when you have to.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • I should've said that is not of my interest changing Dependency. I wanted to know if it's possible to work out Whatever without touching Dependency. Keeping the count of changes is a little change but still it is a change that is going to affect a lot of methods... and an observable interface is even a bigger change. This is often a problem I have and solving it without touching existing code (which not necessarily is my own) seems impossible. Thank you very much for your answer though, very much appreciated. – Wes Jan 10 '17 at 15:06
  • Only the invalidating strategies (observer and state counter) require changing `Dependency`. The injector strategy leaves `Dependency` and `Whatever` alone and nothing is ever invalid. Just allows state to be different in the different copies. Which you should choose depends on what you're trying to do. – candied_orange Jan 10 '17 at 15:51
1

If Whatever has a dependency, then usually you don't have a choice to allow changes to the dependency or not. You cannot require changes to go through Whatever, first because that couples the code changing the dependency with Whatever which is horrible, second because WhateverElse might also have the same dependency, and then you are stuck.

If you copy the dependency in the constructor, then you cannot react to changes so half of the value is gone. That would again mean that when the dependency changes, someone has to destroy Whatever and create a new one. That can be done. Often better is to make Whatever an observer, and write code to modify it appropriately when the dependency changes.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
1

I want to keep a dependency decoupled, but at the same time, once it's passed to the constructor, I want to allow changes only through Whatever...

If you want Whatever to be free to change the dependency, but not have anything else change that dependency, then clearly that dependency isn't in any way decoupled. You want Whatever to totally own it once it's given it. That's as far removed from decoupled as you can get.

So I keep a copy instead ... However the dependency is huge so I've decided to avoid the copy...

So your dependency needs to be owned by Whatever, but it's huge, so can't be copied? Why is the dependency huge? Does Whatever really use all of it? If so, presumably Whatever is huge too?

Question is: is there a way to avoid the copy and at the same time maintaining proper encapsulation?

You can't encapsulate something that is passed to you from the thing that passed it to you. So if the calling code gives that same huge dependency to other parts of the system, you have no encapsulation.

I can't change Dependency. Also it must be mutable...

So in summary:

  1. You have this huge mutable Dependency class that you can't change,
  2. Whatever needs to be able to change Dependency,
  3. But Whatever is tightly coupled to Dependency and so mustn't have anything else change Dependency

Asking for ways of preventing Dependency being changed outside of Whatever is asking for a "band aid" plaster to put on a severed limb. Your design is seriously flawed: Dependency should not be huge; Whatever should not be dependent on a mutable object that it needs to not change etc.

However, by the sounds of it, you are stuck with this flawed design. Sure, there are ways you can add even more complexity to the system to try and reduce the chances of other parts of the system modifying Dependency but non will be fool-proof. All will be at risk of failure through a change in the future.

So the best short-term solution really is to document this problem and to acknowledge you have a large technical debt that should be paid off as soon as possible by restructuring the system.

David Arno
  • 38,972
  • 9
  • 88
  • 121
  • Do you realize you didn't even try to answer the question? "Your design is seriously flawed" "large technical debt" are statements with little or no value. Dependency is huge, yes, because it contains an array, and that could be huge, i don't know and i don't care, I just want to avoid the copy if it's possible. – Wes Jan 10 '17 at 15:36
  • "Whatever should not dependent on mutable objects that must not change" how can you avoid that? it's not possible that you never encountered this problem. Look at java's iterators with their "counting the structural changes" as suggested by @CandiedOrange how else would you do that? If you want to avoid copies, there are no alternatives, I think. Enlighten us with your superior design. – Wes Jan 10 '17 at 15:37
  • 2
    @Wes, there are numerous possible solutions: make the array immutable; if you are dealing with a single thread, replace `Whatever` with static methods that perform operations on the array, such that it doesn't matter if it changes; just make a copy regardless; etc etc. When you start trying to work out strategies like no other part of the system having access to `Dependency`, then your design has gone wrong. So back up and do it differently. – David Arno Jan 10 '17 at 16:13
1

The most straightforward solution here to me is going to be an immutable or copy-on-write wrapper over Dependency if you can at least afford to work with a wrapped instance of it throughout the system.

Both would use something like reference-counting or GC to allow unmodified shallow copies of the resource to be shared.

If you use COW, for example, then whenever Whatever stores a copy of Dependency, it will just store a cheap shallow copy (just the cost of a pointer, e.g.). However, if something external attempts to write to the dependency wrapper, then a new version of the data will be generated without affecting Whatever's copy. An immutable version follows the same idea.

COW version is sorta like this:

void Something::create():
    self.ref_count = new Counter(1)
    self.data = new Data

void Something::copy(other):
    // Just shallow copy the data and increment ref count.
    self.ref_count = ++other.ref_count
    self.data = other.data

void Something::destroy():
    // Only destroy the data we're sharing if the ref count 
    // goes to zero.
    if --self.ref_count == 0:
        destroy self.data
        destroy self.ref_count
    self.ref_count = null
    self.data = null

void Something::write(...):
    // Make a new deep copy of the data with a unique ref
    // count and modify that. Then make this 'Something'
    // refer to the new data and new reference count.
    new_data = new Data(self.data)
    // change new_data
    self.destroy()
    self.data = new_data
    self.ref_count = new Counter(1)

While immutable version would be like this:

Something Something::write(...) const:
    // Make a new version of 'Something' with a deep
    // copy of the data and unique ref count. Modify the 
    // deep copy and return the new something.
    new_something = Something::create()
    new_something.data = new Data(self.data)
    // change new_something.data
    return new_something

The whole idea is to make copying dirt cheap by making it simply shallow copy the data in exchange for some more overhead whenever you want to modify the data, at which point a new version of the data is created when modifications are requested.

If the write logic is too granular to warrant creating a whole new deep copy of the data (and also potentially locking for thread-safety) for every single little change you can make, then an immutable design with a "transient" or "builder" is often your best bet. That "transient" or "builder" can aggregate multiple changes requested of the structure and then commit those changes to get a new immutable copy. Typically most languages I've seen have the appropriate mechanisms in place to generalize this kind of immutable wrapper in a way that will let you make any mutable data type you want immutable with a generalized version of this wrapper.