Search code examples
c#interfaceseparation-of-concernsvisitor-pattern

Role of interfaces for restricting access to a class


Two classes, S and R and a message M (implemented as a class, a delegate or whatever).

Could I satisfy the following requirements with the help of interfaces in C#? If yes, how?

  1. any object of S shall be able to send M to any object of R
  2. any object of any other class X shall not be able to send M to any object of R
  3. objects of S shall not be able to send any other message N to objects of R (although R might receive other message types from other classes' objects)
  4. any attempt to send M or N in violation of the above shall result in a compiler error (ie. shall be statically checked)

It sounds quite simple and natural to me, but no matter how hard I try and search here on SO or the web, I can't find anything useful. The only thing I have found are references to the Factory pattern, which, I think, doesn't really apply here because the problem is not about construction of S,R or X.

Of course I would also be grateful for any other solution that does not involve interfaces.

BTW: although it sounds a little like it, this is neither a homework assignment, nor is it from a professional context. I am just a hobbyist programmer trying to explore the possibilities of the language I love.

Edit:

In order to provide a (hypothetical) code example: the ideal way for me would be to be able to write (pseudo-)code like below. I know that the language does not support that. That is the reason why I am asking to find a pattern or something that achieves the same thing.

class Receiver
{
    permits[MSender] void MessageM(); // <- I know that the "permits[]" access modifier does not exist in C#!!!
    permits[NSender] void MessageN();
}

class MSender
{
    Receiver r;
    public void JustDoIt()
    {
        r.MessageM(); // compiles successfully
        r.MessageN(); // does not compile
    }
}

class NSender // totally unrelated to sender despite the similar name
{
    Receiver r;
    public void DoItDifferently()
    {
        r.MessageM(); // does not compile
        r.MessageN(); // compiles successfully
    }
}

Solution

  • An outline of a visitor pattern:

    See https://dotnetfiddle.net/MfGWqw

    public class Program
    {
        public static void Main()
        {
            R recv = new R();
            new S().send( recv, new M()); // OK
            new S().send( recv, new N()); // Compilation error (line 9, col 3): 
                                          // The best overloaded method match for 'S.send(R, M)' 
                                          // has some invalid arguments
            new X().send( recv, new N()); // OK
            new X().send( recv, new M()); // Also compilation error ... 
        }
    
    
    }
    
    // Message types    
    
    public class M{}
    
    public class N{}
    
    // Receiver     
    public class R
    {
        public void accept( S sender, M message){}
        public void accept( X sender, N message){}
    }
    
    // Sender types    
    
    public class S
    {
        public void send( R receiver, M message )
        {
            receiver.accept(this, message);
        }
    }
    
    public class X
    {
        public void send( R receiver, N message )
        {
            receiver.accept(this, message);
        }
    }
    

    I didn't use interfaces in that example, but of course, you could. I just wanted to outline the pattern which satisfies your requirements. I am expecting that you will need to adapt it to your needs.

    EDIT: To answer your comment ... If you are afraid of malicious implementations in S, you could address that with explicit interface implementations. An example:

    public interface IMReceiver
    {
        void accept( S sender, M message);
    }
    

    then change R :

    public class R : IMReceiver
    {
        void IMReceiver.accept( S sender, M message){} // <= explicit interface implementation.
                                                       // only visible when the reference is of 
                                                       // that interface type.
        public void accept( X sender, N message){} // you would do the same for N ...
    }
    

    and S:

    public class S
    {
        public void send( IMReceiver receiver, M message )
        {
            // receiver now has only accept( S, M ) available.
            receiver.accept(this, message);
            // MALICIOUS vv
            receiver.accept(new X(), new N()); // compilation error
        }
    }
    

    I only did it for S and M here for the example, but you'd probably want to do the same for X and N.

    see https://dotnetfiddle.net/b14BOc