Friends and internal interface members at no cost with coding to interfaces






4.40/5 (15 votes)
Good coding practices sometimes lead to neat solutions of seemingly unrelated issues. Do you code to interfaces? - Get friendship at no cost.
Preface
My coding experience shows that once a particular class has more than one public method, I start loosing control over the exact functionality of that particular class. So, I try to emphasize the interface of any class. The result is that almost all my classes, even the very internal ones have corresponding interfaces.
Programing to an interface is a way of coding. If you are there, you will easily adopt the ideas from this article. If you are not sure what programing to an interface means, I hope, you will fill the gap as soon as possible.
In other words: "Start program to an interface".
Introduction
In this article I will try to get a solution for two specific problems
- Constructing objects from a predefined class only - aka friend access
- Defining interface members with an internal access
But first - my understanding of the statements above.
Problem Statement
Constructing objects from a predefined class only
Let's code a transaction manager
interface ITransaction { }
class LoadingTransaction : ITransaction() { }
class TransactionManager
{
ITransaction StartLoading()
{
return new LoadingTransaction();
}
}
I would say that hiding the public constructor of the LoadingTransaction
class suggests itself.
The design above surely imposes that any transaction should be created by a transaction manager only.
Interface members with internal access
While the web is full of excellent answers to "Why interface can't have internal members", I still think that the question itself is a mix of two orthogonal concerns:
- Who is allowed to implement a particular interface members
- Who is allowed to use that particular interface member
Now consider the following member of an ILogger
interface
interface ILogger
{
...
void Reconfigure();
}
Any class that implements the ILogger
interface is ought to implement the Reconfigure()
method. No doubt here.
But, what we may want is to restrict the initiators of a reconfiguration process - the callers of the Reconfigure()
method.
Indeed, suppose the reconfiguration trigger is an internal detail of our assembly - then we want to express this fact explicitly in our design.
No one should call the Reconfigure()
method from outside.
That what the internal access modifier on the Reconfigure()
method should mean.
Implementation
Before I am ready to step into the promised implementation I need a way of total separation of an interface and its implementation.
Decoupling an Interface from a Specific Implementation
Given the transaction manager we started with,
let's emit the ITransactionManager
interface and its implementation as we did with the transaction itself.
interface ITransaction { }
class LoadingTransaction : ITransaction() { }
interface ITransactionManager
{
ITransaction StartLoading();
}
class TransactionManager : ITransactionManager
{
ITransaction ITransactionManager.StartLoading()
{
return new LoadingTransaction();
}
}
(See my article why I use explicit implementation)
I always struggle not to deal with any concrete implementations.
Let's hide the public constructors of both LoadingTransaction
and TransactionManager
classes
class LoadingTransaction : ITransaction()
{
public static ITransaction New()
{
return new LoadingTransaction();
}
private LoadingTransaction()
{ }
}
class TransactionManager : ITransactionManager()
{
public static ITransactionManager New()
{
return new TransactionManager();
}
private TransactionManager()
{ }
}
The New()
method is a kind of a factory method that accomplishes the separation of an interface and its implementation.
(The same may be achieved by using a dependency injector container)
From this point no one (especially later maintainers) do not deal with any concrete instances of our classes.
For instance, a little benefit from this way of coding
void main()
{
var manager = TransactionManager.New();
manager.StartLoading(); // good; still using ITransactionManager interface
}
Regardless of the usage of the var
keyword the code above is not coupled to a concrete TransactionManager
instance.
What is not true with a common coding practice
void main()
{
var manager = new TransactionManager();
manager.StartLoading(); // bad! coupled to a concrete TransactionManager implementation
}
The New()
method is the crucial point of the friend access solution, so look at it once again carefully.
Now Comes the "Is My Friend Trick"
We are finally ready to express friend access explicitlyclass LoadingTransaction : ITransaction()
{
public static ITransaction New(TransactionManager onlyAccess)
{
return new LoadingTransaction();
}
private LoadingTransaction()
{ }
}
Surprisingly we require a concrete instance of the TransactionManager
class as a parameter of the LoadingTransaction.New()
method call.
But the only one who has that concrete instance is the
TransactionManager
itself!
Take a brief and recall
class TransactionManager : ITransactionManager()
{
public static ITransactionManager New()
{
return new TransactionManager();
}
private TransactionManager()
{ }
}
The factory method New()
returns an ITransactionManager
interface. The constructor of the TransactionManager
class is private and so inaccessible.
There is just no way to obtain a concrete instance of the TransactionManager
class.
Indeed, the only one who can call the LoadingTransaction.New(TransactionManager onlyAccess)
method is
the TransactionManager
itself or any of its descendant.
Internal members
It seems that the idea is clear: "Requiring a concrete instance of a class, while restricting the construction access of that class".
Now it is pretty easy to come up with this code
public class Internal
{
internal Internal()
{ }
}
public interface ILogger
{
...
void Reconfigure(Internal access);
}
We require a concrete instance of the Internal
class as a parameter of the Reconfigure()
method call.
But that required instance may only be constructed within our assembly.
An Accomplishing Touch
There are two ways our friend accessed method might be compromised
class LoadingTransaction : ITransaction()
{
public static ITransaction New(TransactionManager onlyAccess)
{ }
}
Accessing the friend method with a null reference
void main()
{
var transaction = LoadingTransaction.New(null);
}
What may be solved by just a run-time validation
class LoadingTransaction : ITransaction()
{
public static ITransaction New(TransactionManager onlyAccess)
{
if (onlyAccess == null)
throw new AccessViolationException();
}
}
The second flaw is accessing a friend method by casting to a concrete instance
void main()
{
ITransactionManager manager = TransactionManager.New();
var transaction = LoadingTransaction((TransactionManager) manager);
}
The solution here is a little bit more complicated. But the idea is pretty much the same.
We require a concrete instance of a nested FriendsOnly
class, while hiding its constructor.
class LoadingTransaction : ITransaction
{
public static ITransaction New(TransactionManager.FriendsOnly access)
{ }
}
class TransactionManager
{
private static FriendsOnly _meIsFriend;
public class FriendsOnly
{
public static void GrantAccessPermissions() { _meIsFriend = new FriendsOnly(); }
private FriendsOnly() { }
}
static TransactionManager()
{
FriendsOnly.GrantAccessPermissions();
}
public ITransaction StartLoading()
{
var transaction = LoadingTransaction.New(_meIsFriend);
...
}
}
However, I would simply ignore the second flaw, since casting to a concrete class is already a dirty thing to do.
Conclusion
The accessibility obstacle may be handled by nested classes as well. However, the method represented by this article is superior in my opinion. It does not compromise the design nor the coding style.
Once you adopt the practice of programming to interfaces, you get the friend access for almost no charge.