This is an architecture problem. Programmers encounter this encapsulation problem quite often, but I haven't yet seen a complete and clean solution.
Related questions:
readonly class design when a non-readonly class is already in place
Controlling read/write access to fields
Normally, in OOP paradigm, objects store their data in fields. The class' own methods have full access to its fields. When you need to return value, you just return a copy of the data, so that the outside code cannot break the data.
Now suppose that the data pieces are complex, so they're themselves encapsulated in class objects and that these objects cannot be easily copied. Now, if you return such object from some property, the outside code has the same access to it as your internal code. For example, if you return a List<int>
, everyone can add values to it. This is usually undesirable.
This problem is usually worked around using read-only wrappers - you wrap your full-access internal objects in read-only wrappers before returning. The problem with this approach is that the wrapper may be a poor substitution for the wrapped value - the wrapper is a different class. (And if you derive the read-only wrapper from the modifiable class (or vise-versa), then anybody can up-cast/down-cast the "read-only" object to the modifiable object, breaking the protection.)
I want a pattern such that:
- The data (say, an
int
value) has "public/read-only API" and "private/modifiable API". - Only the object creator has access to the "private/modifiable API".
- The private/public APIs may have both passive parts (e.g. methods, properties) and active parts (e.g. events).
- Delegates should not be used except at the object creation stage. All calls should be direct.
- The access to the internal data from the "public/read-only API" (and, preferably, from the "private/modifiable API" too) should be as direct as possible. I don't want a big stack of wrappers to accumulate when composing such objects.
Here are the sample interfaces:
interface IPublicApi {
int GetValue();
}
interface IPrivateApi {
void SetValue(int value);
}
interface IPrivateConsumer {
void OnValueChanged(); //Callback
}
I have devised such scheme. I want you to critique my solution or give your own solution.
How I do it
The question is quite interesting. I'm not in any way an expert in OOP (God! I wish I would!), but here is how I do it:
It's not very secure, since you can cast IReadOnlyFoo to Foo. But my philosophy here is the following: when you cast, you take all the responsibility on yourself. So, if you shoot yourself in the foot, it's your fault.
How I would do if I were to avoid casting problem
First thing to consider here is that there are value types and reference types.
Value types
For the sake of this answer I would classify value types for pure data types (int, float, bool, etc.) and structures.
Pure data types
It is interesting that you explain your problem using
int
which is value type. Value types are get copied by assignment. So, you don't need any kind of wrapper or read only reference mechanics for int. This is for sure. Just make a read-only property or property with private/protected setter and that's it. End of story.Structures
Basically, the same thing. In good designed code, you don't need any wrappers for structs. If you have some reference type values inside struct: I would say that this is a poor design.
Reference types
For reference types your proposed solution looks too complicated. I would do something like this:
There are several sub-problems that have to be solved.
My system consists of these classes:
ReadableInt
is the public APIReadableInt.PrivateApi
is the raw private API proxy objectReadableInt.IPrivateConsumer
is the public-to-private callback interfaceWritableInt
is some private API consumer, which may reside in another assembly.One can use the classes like this:
This is how the system works:
ReadableInt.PrivateApi
) gains access to the main object (ReadableInt
) private members by being an inner class. No up-casting/down-casting security breaches.ReadableInt.PrivateApi
constructor is markedinternal
, so onlyReadableInt
can create the instances. I could not find a more elegant way to prevent anyone from creating aReadableInt.PrivateApi
from aReadableInt
object.ReadableInt
needs a reference to the private API consumer to call it (notifications etc.). To decouple the public API from concrete private API consumers, the private API consumer is abstracted as theReadableInt.IPrivateConsumer
interface.ReadableInt
receives the reference to aReadableInt.IPrivateConsumer
object through the constructor.ReadableInt.PrivateApi
) is given to the creator (WriteableInt
) via callback (Action<PrivateApi>
) passed to theReadableInt
constructor. It's extremely ugly. Can anyone propose another way?WritableInt.OnValueChanged()
method is private, but is effectively public as it's an interface method. This can be solved with a delegate or a proxy. Is there any other way?This system works, but has some parts that I'm not proud of. I particularly dislike the initialization stage when all parts are linked together. Can this be simplified somehow?