430 likes | 546 Views
Object Oriented Programming. Interfaces , Callbacks Delegates and Events. Dr. Mike Spann m.spann@bham.ac.uk. Contents. Introduction to interfaces Example – An IMeasureable interface Callback functions Delegates Events and event handlers Summary. Introduction to interfaces.
E N D
Object Oriented Programming Interfaces, Callbacks Delegates and Events Dr. Mike Spann m.spann@bham.ac.uk
Contents • Introduction to interfaces • Example – An IMeasureable interface • Callback functions • Delegates • Events and event handlers • Summary
Introductionto interfaces • We have already seen how an abstract base class links related derived class through inheritance • The overridden virtual methods of the abstract base class must be implemented in the derived classes • There are also occasions when we want unrelated classes to exhibit similar behaviour • For example we may want to be able to implement a sorting algorithm on unrelated classes
Introduction to interfaces • For example, we may want to sort objects of class Square on the basis of the length of their side • We could easily do this through polymorphism by implementing an abstract base class Sortable and using similar generic programming techniques we looked at in the last lecture • But, C# (along with Java) doesn’t support multiple inheritance
Sortable Shape Square Introduction to interfaces
Introduction to interfaces • An interface is simply a list of public methods that any class which implements that interface must provide implementations of • Unrelated classes can implement a single interface • In our example, we can define a ISortable interface such that objects of any class which implements this interface can be sorted on the basis of some comparative measure • Our Square class can implement the ISortable interface using the length of side as the comparative measure
Introduction to interfaces • Our ISortable interface contains a single public method compareTo() which defines how the comparison is made • This method is overridden in classes which implement this interface public interface ISortable { int compareTo(ISortable s); }
Introduction to interfaces • compareTo(ISortable b)returns–1,0 or 1 depending on whether some chosen instance field f of a ISortable object is such that : • this.f<b.f • this.f==b.f • this.f>b.f • We can implement this method in class Square to sort on the basis of length of side
Introduction to interfaces public class Square : Shape,ISortable { private int side; public Square(int s) { side = s; } public override void draw() { } public override double area() { return side * side; } public int compareTo(ISortable s) { Square sq=(Square)s; if (side<sq.side) return -(1); if (side > sq.side) return 1; return 0; } }
Introduction to interfaces • We can provide a ShellSort() method which implements the shell sort algorithm on arrays on ISortable objects • The compareTo() method of the objects implementing the interface is called inside ShellSort()
Introduction to interfaces public class ArrayAlg { public static void shellSort(ISortable[] a) { int n = a.Length; int incr = n / 2; while (incr >= 1) { for (int i = incr; i < n; i++) { ISortable temp = a[i]; int j = i; while (j >= incr&& temp.compareTo(a[j - incr]) < 0) { a[j] = a[j - incr]; j -= incr; } a[j] = temp; } incr /= 2; } } }
Introduction to interfaces • To test our program we simply create an array of Square objects and pass it into the ShellSort() method • Because of the cast in the compareTo() method in Square, the correctly implemented compareTo() method is called through polymorphism
Introduction to interfaces public class SortablesTest { static void Main(string[] args) { Square[] s = new Square[3]; s[0] = new Square(3); s[0] = new Square(2); s[0] = new Square(1); ArrayAlg.shellSort(s); // Sorts on length of side } }
An IMeasureable interface • Suppose we have a DataSet class which computes simple statistics of numbers read from an input stream • For example, the average and maximum average Input stream DataSet maximum
public class DataSet { public DataSet() { sum = 0.0; maximum = 0.0; count = 0; } public void add(double x) { sum += x; if (count == 0 || maximum < x) maximum = x; count++; } public double getAverage() { if (count == 0) return 0; else return sum / count; } public double getMaximum() { return maximum; } private double sum, maximum; private int count; }
An IMeasureable interface • Clearly we would have to modify the DataSet class if we wanted to get the average of a set of bank account balances or to find the coin with the highest value amongst a set • DataSet is not re-useable as it stands • However, if all classes that DataSet objects operate on implement an IMeasurable interface, then the class becomes more flexible
An IMeasureable interface public interface IMeasureable { double getMeasure(); } • Thus getMeasure() for BankAccount objects return the balance and for Coin objects returns the coin value
An IMeasureable interface public class BankAccount: IMeasureable { private double balance; private int accountNumber; . . double getMeasure() { return balance;} } public class Coin: IMeasurable { private double value; . . double getMeasure() { return value;} }
An IMeasureable interface • The IMeasurable interface expresses the commonality amongst objects • The fact that each measurable objects can return a value relating to its size • DataSet objects can then be used to analyse collections of objects of any class implementing this interface with minor modifications to the code
public class DataSet { public DataSet(){ sum = 0.0; count = 0; } public void add(IMeasureable x) { sum += x.getMeasure(); if (count == 0 || maximum.getMeasure() < x.getMeasure()) maximum = x; count++; } public double getAverage() { if (count == 0) return 0; else return sum / count; } public double getMaximum() { return maximum.getMeasure(); } private IMeasureable maximum; private double sum; private int count; }
An IMeasureable interface class MeasureablesTest { static void Main(string[] args) { DataSet d=new DataSet(); Coin c1=new Coin(10); Coin c2=new Coin(20); d.add(c1); d.add(c2); double maxCoin=d.getMaximum(); System.Console.WriteLine("coin max= " + maxCoin); } }
Callback functions • The DataSet class is useful as a re-usable class but is still limited • The IMeasurable interface can only be implemented by user defined classes • We can’t, for example, find the maximum of a set of Rectangle objects as Rectangle is a pre-defined class • We can only measure an object in one way. For example, in the case of BankAccount objects, we can only measure it in terms of the balance
Callback functions • The solution is to delegate the measuring to a separate class rather than being the responsibility of the objects we are measuring • We can create a separate IMeasurer interface and implement a measure() method in objects implementing this interface public interface IMeasurer { double measure(Object anObject); }
Callback functions public class DataSet { public DataSet(IMeasurer m) { measurer=m; } public void add(Object x) { sum=sum+measurer.measure(x); if (count==0 || maximum<measurer.measure(x)) maximum=measurer.measure(x); count++; } public double getMaximum() { return maximum; } private IMeasurer measurer; private double sum; private int count; private double maximum; }
Callback functions • A DataSet object makes a callback to the measure() method of an objectimplementing the IMeasurer interface when it needs to measure an object (such as checking a bank balance) • This is in contrast to calling the getMeasure() method of an object implementing the IMeasurable interface • We are now free to design any kind of measures on an object of any class • For example, we can measure Square objects by area • We require a SquareMeasurer class which implements the IMeasurer interface
Callback functions public class Square : Shape { private int side; public Square(int s) { side = s; } public override void draw() { } public override double area() { return side * side; } } public class SquareMeasurer : IMeasurer { public double measure(Object anObject) { Square sq=(Square) anObject; return sq.area(); } }
Callback functions class MeasurersTest { static void Main(string[] args) { IMeasurer m = new SquareMeasurer(); DataSet data = new DataSet(m); // Add squares to the data set data.add(new Square(5)); // Callback to m.measure() data.add(new Square (30)); // Callback to m.measure() // Get maximum double max = data.getMaximum(); } }
Callback functions • Adding square objects to data enforces callbacks to the measure() method of the SquareMeasurer object to be made • We have flexibility over our implementation of SquareMeasurer so that any feature of Square objects can be measured • Or even defining several measurer classes to measure different features • Callbacks are used extensively in building graphical user interfaces • A callback function is added to an event object which is called when the event is triggered • A managed way of doing this is to encapsulate the callback function into a delegate object
Delegates • A delegate object holds a reference to a method with a pre-defined signature • A signature is simply the argument list and return type of the method • The keyword delegate specifies that we are defining a delegate object • For example we can define a delegate object myDelegate which holds a method which returns void and takes an int and a double as arguments public delegate void myDelegate(int arg1, double arg2)
Delegates • A delegate object is initialized with a (callback) method • The method signature must match the delegate signature public delegate void myDelegate(int arg1, double arg2); public class App { public static void Main() { myDelegate call = new myDelegate(aMethod); // We can now treat call as a simple object and pass // it around as such } static void aMethod(int k, double x) {} }
Delegates • Delegate objects can be initialized with several method calls using the += operator • The method calls can then be invoked in a chain by passing the correct arguments to the delegate object • Essentially it amounts to calling methods through a proxy object and is a powerful mechanism for event handling as we shall see • Passing method calls into objects is then equivalent to passing delegate objects
Delegates myDelegate(int,double) call call(1,2.0) Invoked by aMethod() anotherMethod() aMethod(1,2.0) anotherMethod(1,2.0)
Delegates public delegate void myDelegate(int arg1, double arg2); public class App { public static void Main() { myDelegate call = new myDelegate(aMethod); call += new myDelegate(anotherMethod); call(1, 2.0); } static void aMethod(int k, double x) { System.Console.WriteLine("aMethod " + k + " " + x); } static void anotherMethod(int k, double x) { System.Console.WriteLine("anotherMethod " + k + " " + x); } }
Events and event handlers • C# has built in support for event handling • Event handling is usually used in GUI’s such as to notify an application when a mouse click or button press has occurred • But any type (not just graphical types) can use events to allow notification of any kind • A type must register its interest in handling a specific event with an event handler with a pre-defined signature • This is achieved using a delegate object
Events and event handlers • A type can define an event and a corresponding event handler which is a delegate object public class MyType { public delegate voidEventHandler(int arg1, int arg2); public event EventHandlermyEvent; // defines myEvent // and corresponding // event handler . }
Events and event handlers • An application can then register its interest in an event by adding an initialized delegate object to the event using the += operator public class App { public static void Main() { MyType m = new MyType(); m.myEvent += new MyType.EventHandler(myHandler); } public static void myHandler(int i1, int i2) { // Event handler code } }
Events and event handlers • Equivalent code is very common in GUI development where applications register their own event handlers to respond to events generated by graphical components such as button clicks • Normally such code is automatically generated if we are using visual programming techniques (‘drag and drop’) • However, it is still important to understand how it all works
Events and event handlers • For example the following code snippet registers a graphical applications event handler to respond to button clicks public class Button { public delegate voidEventHandler(.....); public event EventHandler Click; . . } public class MyGraphicalApp { Button button = new Button(); button.Click += new EventHandler(HandleButtonClick); public void HandleButtonClick(Object sender EventArgs e) { // Event handler code } }
Events and event handlers • For example, we can generate our own SafeArray class which fires an event on trying to access it beyond its bounds • We pass the array index into the event handler • In our simple application, the event handler simply prints out an error message • In an embedded system application, the array could be re-allocated to be a larger size in the event handler
Events and event handlers public class SafeArray { public delegate void OutOfBoundsEventHandler(int arg1); public event OutOfBoundsEventHandler myOutOfBoundsEvent; private int[] data; private int numberOfElements=0; public SafeArray(int n) { numberOfElements = n; data = new int[numberOfElements]; } public int access(int elem) { if (elem < numberOfElements) return data[elem]; else myOutOfBoundsEvent(elem); // Fire an event return 0; } }
Events and event handlers public class App { public static void Main() { SafeArray s = new SafeArray(10); s.myOutOfBoundsEvent += new SafeArray.OutOfBoundsEventHandler(myHandler); s.access(7); s.access(11); // Out of bounds event generated! } public static void myHandler(int i1) { System.Console.WriteLine("Index " + i1 + " out of bounds "); } }
Summary • We have seen how objects of unrelated classes can exhibit similar behaviour by implementing an interface • Interfaces support generic programming and avoid multiple inheritance • We looked at a detailed example involving a IMeasurable interface • We also introduced callbacks by having a separate measurer object implementing an IMeasurer interface • A callback function was made to a measure() method which returned some measure on the object passed to it • We have seen how delegates are objects which encapsulate method calls • These method calls can be chained using the += operator • We have seen how we can create types which can generate events and how applications can register their interest in events by initializing a delegate object with their own event handler