views:

422

answers:

2

When writing GUI apps I use a top level class that "controls" or "coordinates" the application. The top level class would be responsible for coordinating things like initialising network connections, handling application wide UI actions, loading configuration files etc.

At certain stages in the GUI app control is handed off to a different class, for example the main control swaps from the login screen to the data entry screen once the user authenticates. The different classes need to use functionality of objects owned by the top level control. In the past I would simply pass the objects to the subordinate controls or create an interface. Lately I have changed to passing method delegates instead of whole objects with the two main reasons being:

  • It's a lot easier to mock a method than a class when unit testing,
  • It makes the code more readable by documenting in the class constructor exactly which methods subordinate classes are using.

Some simplified example code is below:

delegate bool LoginDelegate(string username, string password);
delegate void UpdateDataDelegate(BizData data);
delegate void PrintDataDelegate(BizData data);

class MainScreen {
    private MyNetwork m_network;
    private MyPrinter m_printer;

    private LoginScreen m_loginScreen;
    private DataEntryScreen m_dataEntryScreen;

    public MainScreen() {
        m_network = new Network();
        m_printer = new Printer();

        m_loginScreen = new LoginScreen(m_network.Login);
        m_dataEntryScreen = new DataEntryScreen(m_network.Update, m_printer.Print);
    }
}

class LoginScreen {
    LoginDelegate Login_External;

    public LoginScreen(LoginDelegate login) {
        Login_External = login
    }
}

class DataEntryScreen {
    UpdateDataDelegate UpdateData_External;
    PrintDataDelegate PrintData_External;

    public DataEntryScreen(UpdateDataDelegate updateData, PrintDataDelegate printData) {
        UpdateData_External = updateData;
        PrintData_External = printData;
    }
}

My question is that while I prefer this approach and it makes good sense to me how is the next developer that comes along going to find it? In sample and open source C# code interfaces are the preferred approach for decoupling whereas this approach of using delegates leans more towards functional programming. Am I likely to get the subsequent developers swearing under their breath for what is to them a counter-intuitive approach?

+1  A: 

While I can certainly see the positive side of using delegates rather than an interface, I have to disagree with both of your bullet points:

  • "It's a lot easier to mock a method than a class when unit testing". Most mock frameworks for c# are built around the idea of mocking a type. While many can mock methods, the samples and documentation (and focus) are normally around types. Mocking an interface with one method is just as easy or easier to mock than a method.

  • "It makes the code more readable by documenting in the class constructor exactly which methods subordinate classes are using." Also has it's cons - once a class needs multiple methods, the constructors get large; and once a subordinate class needs a new property or method, rather than just modifying the interface you must also add it to allthe class constructors up the chain.

I'm not saying this is a bad approach by any means - passing functions rather than types does clearly state what you are doing and can reduce your object model complexity. However, in c# your next developer will probably see this as odd or confusing (depending on skill level). Mixing bits of OO and Functional approaches will probably get a raised eyebrow at the very least from most developers you will work with.

Philip Rieck
I would argue that you don't need any mocking framework for mocking delegates; all you would do is create a delegate that returns what you need it to in the test. I don't have experience with those frameworks in c# though so it may be easy enough to use, but i can see the benefits and ease of mocking delegates by hand.
Kevlar
There's other upsides to a mocking framework then just not having to write the mocks - verifying the method was called, clear (in code) differences between test code and mocking setup, etc. I
Philip Rieck
+1  A: 

It's an interesting approach. You may want to pay attention to two things:

  1. Like Philip mentioned, when you have a lot of methods to define, you will end up with a big constructor. This will cause deep coupling between classes. One more or one less delegate will require everyone to modify the signature. You should consider making them public properties and using some DI framework.

  2. Breaking down the implementation to the method level can be too granular sometimes. With class/interface, you can group methods by the domain/functionality. If you replace them with delegates, they can be mixed up and become difficult to read/maintain.

It seems the number of delegates is an important factor here.

Kai Wang