Ran into an interesting situation today:
- Client-server application
- A class representing domain data that is used by both client and server assemblies — so that’s three assemblies:
- One for the data class
- One for the server class (uses the data assembly)
- One for the client class (uses the data assembly)
- Data class has getters and setters for its properties
- Some of the setters should only be callable by code in the server; for example, a guid that is assigned by the server to identify the item
In C++ this would be pretty easy; just declare the setters private and make the server a friend.
In C#, the (almost) equivalent technique is to make the setter internal and then open up the internals for the data assembly to the server assembly. But this opens things up way too much! Now any internal member of any class in the entire data assembly can be accessed by any class/function in the server assembly. (And don’t tell me to use single-class assemblies or I’ll punch you in the head — it’s simply not practical for a non-trivial application.)
Searching with Mr. Google mostly finds conversations that consist of “How do I make a friend class in C#?” followed by a response of “You can’t, and friends break encapsulation and are never necessary in a proper OOP design”. I think this represents pedantic and faulty thinking.
In the example I give here I don’t accept that the basic design is flawed — it falls out quite naturally from the nature of the architecture. And friend actually preserves encapsulation much better than internal (or simply keeping the setters public) because it limits the scope of the presumed “damage”.
I’ve no doubt that there are other designs that could accomplish this, but not without exacting a high cost in added complexity or development overhead. And to be clear, this is a pattern that will be found multiple times in any client-server design, so the complexity will stack up quickly!
The problem here is that the unit of encapsulation is actually the totality of the 3 classes: data, client, and server. But the programming tools in use do not easily allow the expression of that level of encapsulation without undue complexity.
So this, I argue, is why friend is needed.
And what is “need” anyway? We don’t need to use C#; other languages would be just as suitable. In fact, we don’t need to have the software we’re building because we could continue to do things manually. However, we’ve made choices along the way to achieve some particular goal, but each choice limits us and constrain our problem-solving environment from that point on. Within this context, we need tools that make what we have to do reasonably simple (or at least no more complex that it has to be).
To me, this is a great example of why “best practice” or other pedantic approaches are problematic at best, and destructive at worst…