I usually translate cases where a friend keyword seems handy into a pattern where a class itself determines which classes can instantiate it, without breaking proper OO-design.
The idea is that the class that wants control over who can instantiate, offers friendship to the classes that are allowed to get an instance of it. For example, class InControl allows classes ChosenOne and ChosenTwo to obtain instances of InControl.
A 'chosen' class (i.e. ChosenOne or ChosenTwo) can 'engage' the friendship offered by InControl with an instance of itself (friendship.engage(this)) and the friendship in turn can 'accept' that instance by providing it an instance of InControl (friend.accept(new InControl())).
The actual friendship is implemented with a nested single instance Friendship class. Friendship is confined to 'chosen' classes using a generic IFriendship interface with T being a chosen class. In turn, each 'chosen' class needs to implement the generic IFriendable interface with T being InControl, to be able to receive an instance of InControl.
The InControl class has a private constructor to avoid instantiation by anyone else but friends:
public interface IFriendable<T>
{
void accept(T friend);
}
public interface IFriendship<T>
{
void engage(T friend);
}
public class ChosenOne : IFriendable<InControl>
{
private InControl _friend { get; set; }
private ChosenOne()
{
InControl.friendship.engage(this);
}
public void accept(InControl friend)
{
_friend = friend;
}
}
public class ChosenTwo : IFriendable<InControl>
{
private InControl _friend { get; set; }
private ChosenTwo()
{
InControl.friendship.engage(this);
}
public void accept(InControl friend)
{
_friend = friend;
}
}
public class InControl
{
public interface IFriendship : IFriendship<ChosenOne>, IFriendship<ChosenTwo> { }
public static IFriendship friendship { get { return Friendship.instance; } }
private class Friendship : IFriendship
{
static Friendship()
{
}
internal static readonly Friendship instance = new Friendship();
public void engage(ChosenOne friend)
{
friend.accept(new InControl());
}
public void engage(ChosenTwo friend)
{
friend.accept(new InControl());
}
}
private InControl()
{
}
}