2

(I really struggled with coming up with a good title for this question, if anyone wants to help out with that..)

So I'm having an issue designing something. Essentially I have a class A, which is composed of an array of objects of type B. I only want the interface of class A to be exposed, and want to keep class B essentially hidden to any user. I want to be able to perform operations on type B and its data, but only through class A's interface/methods calling methods of an instance of B. The part where it gets tricky is that I want to create a method that performs operations on members of type B, but I wanted to implement an interface and then have a class that implements that interface because I want my user to be able to create their own implementation of this method. I was thinking of doing somtehing like:

public class A 
{
    B[] arr;
    C c;

    public A(C c) 
    { 
        arr = new B[100];
        this.c = c;
    }


    public void method1() 
    {
        var b = new B();
        b.someMethodofb(c);    // pass c to the method of b
    }

    private class B 
    {
        someMethodOfb(C c) 
        {
        }
    }
}

public class C : Interface1 
{
    public void method(B b) 
    {    
        //interface method we have implemented
    }
}

I made the class B private because I only want class A to be publicly available so anything that happens to class B happens through class A, which is also why I nested B within A. But since class B is private, will I be able to use it as a parameter for the method of my class C? The method of Interface1 implemented is going to affect the internal implementation of how B performs someMethodOfb, which is why I think I need to pass it in to be able to maintain the hidden nature of class B. Could there be a better way for me to design this and be able to achieve the goals I set out in the first paragraph?

maccettura
  • 10,514
  • 3
  • 28
  • 35
FrostyStraw
  • 1,628
  • 3
  • 25
  • 34
  • 1
    So you want `B` to be private but you also want an interface that exposes `method(B b)` to the world? Those 2 requirements are in conflict. – pmcilreavy Apr 19 '18 at 22:00
  • 1
    Are you experiencing an X/Y problem? https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem .... Can you explain the use case - maybe a different approach is what you need? – Charleh Apr 19 '18 at 22:01
  • @Charleh it's work related and I'm not sure how much details I can give. Essentially I have class A, containing an array of type B. I also want a method that performs operations on type B, but I need to be able to let a user implement their own version of that method. I could make the method a method of type B I guess, but I wanted to limit how exposed type B is the world. – FrostyStraw Apr 19 '18 at 22:04
  • 1
    Instead of exposing type B in the interface, you need to expose a publicly acceptable API that you can transpose onto type B in a method of type A that you control. – NetMage Apr 19 '18 at 22:13
  • @FrostyStraw, I had almost the same requirment some time ago, have a look to [this question](https://stackoverflow.com/q/1664793/4430204), it really helped me a lot – taquion Apr 19 '18 at 22:14
  • 1
    Short ad =). I implemented myself the solution for the scenario where your nested class is generic this is the [answer](https://stackoverflow.com/a/48951886/4430204) – taquion Apr 19 '18 at 22:15
  • Basically you have a public nested class but with a private constructor =) – taquion Apr 19 '18 at 22:17
  • @pmcilreavy what if I make just the constructor of B private (while the class remains public)? Could method (B b) still be exposed to the world? – FrostyStraw Apr 19 '18 at 22:32
  • @NetMage I'm not sure I understand what you mean. Is there any examples you could provide? – FrostyStraw Apr 19 '18 at 22:32
  • @taquion thanks, this looks promising, I'll see if I can do what i need with it – FrostyStraw Apr 19 '18 at 22:33
  • Not sure if this would help: perhaps have B implement an interface containing someMethodOfB(c). You can still have B as a nested class, but have C.method() take in the B interface instead of concrete B. – ylax Apr 19 '18 at 22:42

2 Answers2

2

I would suggest you add another interface for the public known side of B, have B implement that interface and have C's method(s) use the interface.

public interface IC {
    void method(IB b);
}

public interface IB {
    int Priority { get; set; }
    int Urgency { get; set; }
}

public class A {
    B[] arr;
    IC c;

    public A(C c) {
        arr = new B[100];
        this.c = c;
    }


    public void method1() {
        var r = (new Random()).Next(100);
        arr[r].someMethodOfB(c);    // pass c to the method of b
    }

    private class B : IB {
        public int Priority { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public int Urgency { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

        internal void someMethodOfB(IC aC) {
            aC.method(this);
            throw new NotImplementedException();
        }
    }
}

public class C : IC { // user implements
    public void method(IB b) {
        if (b.Priority > 10 || b.Urgency > 10)
            ; // do something with BI using b
        throw new NotImplementedException();
    }
}

Now the user of the classes needs to know IC so they can create C and they need to know IB so they can write the body of the methods in C, but they don't need to know all of B or have access to B.

NetMage
  • 26,163
  • 3
  • 34
  • 55
  • @IanMercer Proper naming is left as an exercise to the reader (though I did change to prefix with `I`). – NetMage Apr 19 '18 at 23:03
  • I would also strongly suggest that you consider changing B[] arr to IB[]. The less these classes are coupled, the easier you will find writing test cases for A. A should not care how B is implemented, just that it has the expected properties and methods. It should be just as happy to work with a Fake B. – Adam G Apr 20 '18 at 03:37
  • @AdamG I disagree. The point is that `IB` is the public interface for `B` but that `A` has access to the private side of `B` - otherwise, there is no private side and what is the point? – NetMage Apr 20 '18 at 16:49
  • @NetMage, the point is that your classes should depend on abstractions not concretions. This includes A. When testing the behaviour of A, you may need to inject some fake B objects that cause a behaviour in A. A complex type with its own internal validations/behaviours can make such corner cases very difficult to test. This is less important for simple B class like a dto. I should make a distinction here. The IB that you publish publically can be a different and restrictive that the interface your class uses in the array definition, allowing your class access to methods third parties cant see. – Adam G Apr 21 '18 at 11:54
  • @AdamG Where is `IString`? If you write an interface for every class in your projects, I can make a suggestion that will improve your productivity. – NetMage Apr 23 '18 at 17:55
  • Reductio ad adsurdum. We aren't talking about strings or the OP would not need an interface. B self evidently has behaviours undesirable to expose. Strings are immutable. I don't care if they used string.Join to create the one they passed in. Consider a ProductDispatcher class. When testing it, do you want to deal with creating a concrete customer object and adding some arrears invoices? Then you need real products, manufacturers, etc. Your ProductDispatcher tests fail every time those other classes change. No, you just need ICustomer to respond with InArrears. This improves my productivity. – Adam G Apr 24 '18 at 00:14
1

Let's use concrete examples :)

Say, we have three classes: Customer, Order, and OrderProcessor. Customer and Order are entities representing a customer and an order respectively, while OrderProcessor will process an order:

public interface IOrderProcessor
{
    void ProcessOrder(IOrder order);
}

public interface IOrder
{
    void FinalizeSelf(IOrderProcessor oProc);
    int CustomerId {get; set;}
}

public class Customer
{
    List<IOrder> _orders;
    IOrderProcessor _oProc;
    int _id;

    public Customer(IOrderProcessor oProc, int CustId)
    {
        _oProc = oProc;
        _orders = new List<IOrder>();
        _id = CustId;
    }

    public void CreateNewOrder()
    {
        IOrder _order = new Order() { CustomerId = _id };
        _order.FinalizeSelf(_oProc);
        _orders.Add(_order);
    }

    private class Order : IOrder
    {
        public int CustomerId {get; set;}
        public void FinalizeSelf(IOrderProcessor oProcessor)
        {
            oProcessor.ProcessOrder(this);
        }
    }
}
public class ConcreteProcessor : IOrderProcessor
{
    public void ProcessOrder(IOrder order)
    {
        //Do something
    }
}
ylax
  • 376
  • 4
  • 8