Copyright © 2012 David Schmidt

Lecture 5:
Interfaces, Delegates, and Reactive systems


5.1 Interfaces: specifications for connection
    5.1.1 Interfaces as "data types"
    5.1.2 Interfaces as connection points
5.2 Abstract classes
5.3 Reactive systems
5.4 Delegates: How events trigger computation
5.5 Models and Controllers in reactive systems


Components communicate by calling each other's methods. If object A wants knowledge from object B, A must know the name of the method, f, in B to call. The method names are more important than the class name.

In this lecture, we learn how to use delegates and interfaces to define the "data type" of a single method and the "data type" of a class-with-methods. Both delegates and interfaces are hugely useful in practice for defining "plug-in" connection points between code assemblies.


5.1 Interfaces: specifications for connection

An interface is a listing of the public methods an object must have to connect or to be connected to others. An interface is a "data type" of a collection of classes. An example explains it best:


5.1.1 Interfaces as "data types"

Say we are assembling a computerized card game, where a dealer deals cards to players. Some players are human, some are computer players --- they're mixed together. Regardless of the form of player, the dealer will ask a player if it wants a card, and if yes, deal it one.

We might try this awkward coding of class Dealer:

// dealer for a card game of one human and one computer player:
public class Dealer {  
  private HumanPlayer h;
  private ComputerPlayer c;
  private CardDeck d;  // deck of cards; has a  dealNewCard  method

  public Dealer(HumanPlayer h, ComputerPlayer c) {  this.h = h;  this.c = c; }

  public void PlayOneRound() {
    ...
    while (h.wantsCard()) {
        h.getsCard(d.dealNewCard())
    }
    while (c.wantsCard() {
        c.getsCard(d.dealNewCard()}
    ...
  }
}
The duplicate code is bad, since both the human and computer objects are called the same way. We can name both of them Players and recode the Dealer with an interface like this:
===================================================

// a  Player  is any class that has these two methods with
//    the behaviors described in the comments:
public interface Player {
  // wantsCard  returns true when the Player wants another card:
  public bool wantsCard();
  // getsCard(c)  adds Card  c  to the hand held within the Player:
  public void getsCard(Card c);
}

===================================================
The interface describes the "type of class" that the Dealer can connect to.

We code class HumanPlayer and class ComputerPlayer to have the methods listed in the interface:

===================================================

// HumanPlayer is the computerized "proxy" for the human;
//   it _implements_ (matches) interface Player:
public class HumanPlayer : Player {
  private HandOfCards h;
  ...
  public bool wantsCard() {
    Console.WriteLine("Do you want another card?");
    string answer = Console.ReadLine();
    return (answer == "Yes");
  }
  public void getsCard(Card c) { h.add(c); }
  ...
}

// ComputerPlayer is a computer card player; it also implements Player:
public class ComputerPlayer : Player {
  private HandOfCards h;
  ...
  public bool wantsCard() {
    return (h.score() < 17)
  }
  public void getsCard(Card c) { h.add(c); }
  ...
}

===================================================
The two classes implement Player --- they have "data type" Player. The Dealer is simplified into a entity that deals to Players:
===================================================

// dealer for a card game of Players:
public class Dealer {  
  private List<Player> players;
  private CardDeck d;  // deck of cards

  // construct dealer: pl  is a list of the Player objects
  //   that the dealer contacts
  public Dealer(List<Player> pl) {  players = pl; }

  // plays one round of the card game with the  players:
  public void PlayOneRound() {
    ...
    foreach(Player p in players) {
      while (p.wantsCard()) {
          p.getsCard(d.dealNewCard())
      }
    }
    ...
  }
}

===================================================
The coding is both simpler and more general. As an exercise, use Nclass to draw a class diagram of the interface and the three classes.


5.1.2 Interfaces as connection points

We use interfaces every day --- we call them "specifications" or "sizes" --- What size of shoe do you wear? (8 narrow? 9 1/2 wide?) What type of battery do I need for my flashlight? (1.5 volt AAA?) What kind of "dingle" do I need to connect this darned VGA cable to my MacBook? (#%^*Q@! ?)

The brand of shoe or battery or cable is unimportant --- the specification is what matters. A C# interface is a specification for a class you want to connect to --- it is a "connection point" or "plug-in point".

Here are three examples of "plug-in" interfaces:

  1. Data structure plug-ins: If you have dared to look at the .NET library, you know there is a huge collection of data-structure classes, some graphics-based, some storage-base, far too many to remember. Many of the data-structure classes have similar abilities, and if we would organize the structures to use the same method names to do the same actions, we might do better at selecting and "plugging in" a structure into a system. Interfaces are used exactly for this reason --- organizing together classes that do "the same thing".

    Look at the .NET reference page for System.Collections. First, you see the names of the basic data structures supported by C#. Next, you see the interfaces that are used to describe the "types" of data structures, e.g.,

    ===================================================
    
    Interface 	Description
    -----------------------------
    ICollection 	Defines size, enumerators and synchronization methods for all collections.
    
    IComparer 	Exposes a method that compares two objects.
    
    IDictionary 	Represents a collection of key-and-value pairs.
    
    IDictionaryEnumerator 	Enumerates the elements of a dictionary.
    
    IEnumerable 	Exposes the enumerator, which supports a simple iteration over a collection.
    
    IEnumerator 	Supports a simple iteration over a collection.
    
    IHashCodeProvider 	Supplies a hash code for an object, using a custom hash function.
    
    IList 	Represents a collection of objects that can be individually accessed by index.
    
    ===================================================
    If you click on any of the interface names (try IList, the "data type" of collections that are sequential and are indexed by nonnegative ints), you will see the definition of the interface and the data structures that implement the interface.

    Another important interface is IDictionary, the "data type" of table-like data structures that use (non-int) keys for lookups.

    Say you are building an assembly that relies on a table, but you don't want to commit to a particular coding of a table --- maybe it doesn't matter or maybe you want to leave this work to an expert --- then you code your assembly to depend on the IDictionary interface. The idea looks like this:

    
    
    That is, within the assembly, the table, no matter how it is implemented, can be indexed and assigned to and can be called with the methods, Add, Clear, Contains, etc. (see the methods listing in the .NET documentation).

    The assembly's code might be integrated like this:

    // Main assembles my system:
    public void Main() {
        // define the system's main table:
        IDictionary main_table = new Hashtable();
        // or maybe:   IDictionary main_table = new HybridDictionary();
        // or any of the classes listed at
        //   http://msdn.microsoft.com/en-us/library/system.collections.idictionary%28v=vs.71%29.aspx
    
        // plug the main_table into the constructed assembly:
        Assembly my_assembly = new Assembly(main_table);
         ...  // do more assembly work and start the system:
        myassembly.run();
    }
    

  2. Subassembly plug-ins: Your company is building an important tool, with multiple subassemblies, and each subassembly is developed by a separate team. Perhaps your team is writing the "control subassembly" (this is sometimes called the "business logic" in a business-database), which receives input requests from some external device and contacts a database for data to compute answers that are returned to the output Form/UI.

    GUIs are tricky to implement well, and specialists write these. Databases are also tricky to build, and specialists write these. So, your team is given an interface for communicating with the GUI and an interface for communicating with the database, and you use these interfaces to write the control subassembly by itself:

    
    
    You "plug" a UserInterface and a Database into the Controller.

    Your assembly must also have an interface so that your assembly can "plug" into the one that generates inputs for the Controller. (You can think of the - - -> arrows into the UserInterface and the Database interface as "sockets" and the ---|> arrow out of the assembly to ControllerInterface as a "plug with pins" that fits into a socket.)

    You test your Controller subassembly with simple-minded class-codings that implement the two interfaces and you "plug" your assembly into a test harnass that generates calls to the handle_Input method. This assembly testing will minimize the headaches of testing the entire system when it is assembled with the actual subassemblies.

  3. Network plug-ins: When networks became popular, so did Client-Server systems, where a client computer logs in and does business with a server computer --- in this way, a company's computers can share a common file system or data base. For clients to share a server, there must be standard methods for logging in, transferring data, etc. CORBA (Common Object Request Broker Architecture) is a standardized collection of interfaces that a Client server must implement to talk to a server. (A server computer has its own set of interfaces to implement to be a server.)

    The Client computer and Server computer connect through a piece of code called the Object Request Broker (ORB), that understands the Client and Server interfaces and so can match Client to Server for communication.

    You can read a simple description and see a simple diagram at the Wikipedia page for CORBA. CORBA's interfaces make it easy for businesses to communicate with each others' servers. And web-based internet commerce (e.g., Amazon.com) is based on a similar set of interfaces that define the TCP/IP network protocol.


5.2 Abstract classes

In the example with the Dealer and card Players, you might have noticed that both HumanPlayer and ComputerPlayer have duplicate codings of getsCard. We can make an "interface" that holds a partial coding of a class. This is called an abstract class. Here is the revised example, where the interface is replaced by an abstract class:
===================================================

// a  Player  holds a hand of cards and has the two behaviors listed:
public abstract class Player {
  protected HandOfCards h;  // note change of visibility of field
  ...
  public abstract bool wantsCard();  // note that body of method is MISSING

  public void getsCard(Card c) { h.add(c); }
}

public class HumanPlayer : Player {
  // We must supply the coding for  wantsCard:
  public bool wantsCard() {
    Console.WriteLine("Do you want another card?");
    string answer = Console.ReadLine();
    return (answer == "Yes");
  }
}

public class ComputerPlayer : Player {
  public bool wantsCard() {
    return (base.h.score() < 17) // say  base.h  to use var  h  in the superclass
  }
}

===================================================
The abstract class removes duplicate coding, but it splits the classes into two pieces, which makes them harder to understand. Use an abstract class only when there is a significant reduction in redundant coding. Otherwise, stick with interfaces, which are simpler and safer.


5.3 Reactive systems

A console application is controlled by the commands in the Main method --- this is the "algorithm." The human might be asked to participate and supply input, via Console.ReadLine(), but the algorithm in Main controls what is done in what order. Computer simulations are usually built in this style.

A card game is controlled by the algorithm coded within the game's Dealer, which acts as a "referee" and enforces a "protocol" (rules of play). The Dealer controls the game. Perhaps the human is asked to supply input or respond to dialog boxes, but the Dealer's algorithm controls what is done in what order.

A reactive system is a program that is controlled by its inputs. A vending machine is a classic reactive system: the machine (and its software) waits for input (e.g., coins inserted). The input triggers some computation, and the machine again waits for input (this time, a press of a button to select a candy bar) which triggers more computation.

A VS Forms application is a reactive system: the Main method activates a Form (via Application.Run(new Form1())), and the Form waits on user input (a button press or text entry) --- these are called events). An event calls an event handler method (e.g., private void button1_Click(object sender, EventArgs e) when button1 is pressed) that does computation.


5.4 Delegates: How events trigger computation

Say you build a Forms application with a button, button1, and you want the code in private void button1_Click(object sender, EventArgs e) to execute when the button is pressed.

When you tell Visual Studio to register button1_Click as the event handler for button1 (look at the Properties window for button1 and you will find this information displayed), Visual Studio generates hidden code that saves the "handle" for the method (the "address" of the method's code --- where to "jump") in a table buried deep inside the package, System.Windows.Forms.

Because of the delegate declaration, ClickHandler, inside System.Windows.Forms, the developers of System.Windows.Forms were able to write "their subassembly" for event handling so that it "plugs into" the GUI you design when you use Visual Studio.

Again, think of a delegate declaration as an interface for a single method. It can be used like an interface to make a "connection point".

(A method that implements a delegate interface is called a delegate object. There are some technical details about a delegate object --- the object holds both the handle to the method and also a handle to the frame where the method's nonlocal variables (class fields) live. A delegate object is also called a closure object, which is older terminology. We leave it at that; take CIS505 to learn more....)

Soon we will use delegate declarations and delegate objects in our own coding. See the Visual Studio notes Section on "Delegates" to see how delegates are commonly used in systems coding.


5.5 Models and Controllers in reactive systems

Here is critical terminology:
Every program has some data structures (tables, trees, lists, databases) to be maintained. In an object language, a data structure is declared as a field within a class, along with methods that know how to do lookups and updates on the data structure. Such classes are called model classes or entity classes.

Every program has an algorithm that knows the correct order of commands/rules/protocol for building/maintaining the data structures. In an object language, the algorithm is called the controller.

Every program has an input/output device or display or user-interface. In an object language, the device/display/UI is defined within a class, called the view class or boundary class.

If you use Visual Studio to construct a Forms application, you know that class Form1 is the view class --- its job is to accept input and show output. You must write some additional, separate classes to define program's model. These classes are usually coded as a separate Project or Solution.

But what about the controller? If you are a Visual Studio user, you can be lazy and insert the controller as code inside private void button1_Click(object sender, EventArgs e), which resides inside class Form1 as the event handler for a button press. If you do this, you are basically forced to declare the model objects as fields inside class Form1 as well.

Congratulations! Visual Studio has led you to mush together the model, controller, and view assemblies inside class Form1, the template for generating a GUI Form! This is a bad design that is never used in practice, because teams of engineers must develop the Model, View, and Controller assemblies separately, test them separately, and assemble them in a plug-and-play, unplug-replug fashion. We must learn how to do this.

Exercise