Chapter 8
Abstract classes, Interfaces, and Patterns

Kurt Nørmark ©
Department of Computer Science, Aalborg University, Denmark


Abstract
Previous lecture Next lecture
Index References Contents

In this chapter of the material we study inheritance.

At the conceptual level, we start by a study of class specialization and class extension. Following that the programming language concept of inheritance is introduced.

In the last part of the chapter we study inheritance in C#. This includes polymorphism, dynamic binding, abstract classes, and a number of design patterns for which inheritance is of central importance.


Method Combination

Method Combination
Slide Annotated slide Contents Index
References Textbook 

We sometimes talk about method combination when two or more methods of the same name Op cooperate in solving a given problem

Class B is a subclass of class A

  • Programmatic (imperative) control of the combination of Op methods

    • Superclass controlled: The Op method in class A controls the activation of Op in class B

    • Subclass controlled: The Op method in class B controls the activation of Op in class A

    • Imperative method combination

  • An overall (declarative) pattern controls the mutual cooperation among Op methods

    • A.Op does not call B.Op   -   B.Op does not call A.Op.

    • A separate abstraction controls how Op methods in different classes are combined

    • Declarative method combination

C# supports subclass controlled, imperative method combination via use of the notation base.Op(...)

Parameter Variance
Slide Annotated slide Contents Index
References Textbook 

How do the parameters of Op in class A and B vary in relation the variation of A and B

Class B is a subclass of class A, and T is a subclass of S.

Program: An illustration of the problems with covariance.
   A aref;
   B bref = new B();
   S sref = new S();

   aref = bref;    // aref is of static type A and dynamic type B
   aref.Op(sref);  // B.Op is called with an S-object as parameter.
                   // What if an operation from T is activated on the S-object?

Program: The full C# program.
using System;

class S{
  public void Sop(){
   Console.WriteLine("Sop");
  }
}

class T:S{
  public void Top(){
   Console.WriteLine("Top");
  }
}

class A {
 public void Op(S x){
   x.Sop();
 }
}

class B: A {
 public void Op(T x){
   x.Top();
 }
}

class Client{
 public static void Main(){
   A aref;
   B bref = new B();
   S sref = new S();

   aref = bref;    // aref is of static type A and dynamic type B
   aref.Op(sref);  // B.Op is called with an S-object as parameter.
                   // What if an operation from T is activated on the S-object?
 }
}

It turns out that parameter variance is not really a relevant topic in C#...

Covariance and Contravariance
Slide Annotated slide Contents Index
References Textbook 

  • Covariance: The parameters S and T vary the same way as A and B

Class B is a subclass of class A, and the parameter class T is a subclass of S.

  • Contravariance: The parameters S and T vary the opposite way as A and B

Class B is a subclass of class A, and the parameter class S is a subclass of T.

Exercise 8.2. Parameter variance

First, be sure you understand the co-variance problem stated above. Why is it problematic to execute aref.Op(sref)in the class Client?

The parameter variance problem, and the distinction between covariance and contravariance, is not really a topic in C#. The program with the classes A/B/S/T on the previous page compiles and runs without problems. Explain why!


Abstract Classes - Sealed Classes

Abstract Classes
Slide Annotated slide Contents Index
References Textbook 

Abstract classes are used for concepts that we cannot or will not implement in full details

The concept abstract class: An abstract class is a class with one or more abstract operations
The concept abstract operation: An abstract operation is specially marked operation with a name and with formal parameters, but without a body

  • An abstract class

    • may announce a number of abstract operations, which must be supplied in subclasses

    • cannot be instantiated

    • is intended to be completed/finished in a subclass

Abstract classes and abstract methods in C#
Slide Annotated slide Contents Index
References Textbook 

An abstract Stack without data representation, but with some implemented operations

Program: An abstract class Stack - without data representation - with a non-abstract ToggleTop method.
using System;

public abstract class Stack{ 
  
  abstract public void Push(Object el);

  abstract public void Pop();

  abstract public Object Top{
    get;}

  abstract public bool Full{
    get;}

  abstract public bool Empty{
    get;}

  abstract public int Size{
    get;}   

  public void ToggleTop(){
    if (Size >= 2){
      Object topEl1 = Top;  Pop();
      Object topEl2 = Top;  Pop();
      Push(topEl1); Push(topEl2);
    }
  }   

  public override String ToString(){
    return String.Format("Stack[{0}]", Size );
  }   
}

  • Detailed rules - some are surprising

    • Abstract classes

      • can be derived from a non-abstract class

      • do not need not to have abstract members

      • can have constructors

    • Abstract methods

      • are implicitly virtual

Exercise 8.4. A specialization of Stack

On the slide to which this exercise belongs, we have shown an abstract class Stack.

It is noteworthy that the abstract Stack is programmed without any instance variables (that is, without any data representation of the stack). Notice also that we have been able to program a single non-abstract method ToggleTop, which uses the abstract methods Top, Pop, and Push.

Make a non-abstract specialization of Stack, and decide on a reasonable data representation of the stack.

In this exercise it is OK to ignore exception/error handling. You can, for instance, assume that the capacity of the stack is unlimited; That popping an empty stack an empty stack does nothing; And that the top of an empty stack returns the string "Not Possible". In a later lecture we will revisit this exercise in order to introduce exception handling. Exception handling is relevant when we work on full or empty stacks.

Write a client of your stack class, and demonstrate the use of the inherited method ToggleTop. If you want, you can also adapt my stack client class .

Exercise 8.4. Course and Project classes

In the earlier exercise about courses and projects (found in the lecture about classes) we programmed the classes BooleanCourse, GradedCourse, and Project. Revise and reorganize your solution (or the model solution) such that BooleanCourse and GradedCourse have a common abstract superclass called Course.

Be sure to implement the method Passed as an abstract method in class Course.

In the Main method (of the client class of Course and Project) you should demonstrate that both boolean courses and graded courses can be referred to by variables of static type Course.

Abstract Properties
Slide Annotated slide Contents Index
References Textbook 

Properties and indexers may be abstract in the same way as methods

Program: The abstract class Point with four abstract properties.
using System;

abstract public class AbstractPoint {

  public enum PointRepresentation {Polar, Rectangular}

  // We have not yet decided on the data representation of Point

  public abstract double X {
    get ; 
    set ;
  }

  public abstract double Y {
    get ; 
    set ;
  }

  public abstract double R {
    get ; 
    set ;
  }

  public abstract double A {
    get ; 
    set ;
  }

  public void Move(double dx, double dy){
    X += dx;  Y += dy;
  }

  public void Rotate(double angle){
    A += angle;
  }

  public override string ToString(){
    return  "(" + X + ", " + Y + ")" + " " +  "[r:" + R + ", a:" + A + "]  ";
  }

  protected static double RadiusGivenXy(double x, double y){
    return Math.Sqrt(x * x + y * y);
  }

  protected static double AngleGivenXy(double x, double y){
    return Math.Atan2(y,x);
  }

  protected static double XGivenRadiusAngle(double r, double a){
    return r * Math.Cos(a);
  }

  protected static double YGivenRadiusAngle(double r, double a){
    return r * Math.Sin(a);
  }

  
}

Program: A non-abstract specialization of class Point (with private polar representation).
using System;

public class Point: AbstractPoint {

  // Polar representation of points:
  private double radius, angle;            // radius, angle

  // Point constructor:
  public Point(PointRepresentation pr, double n1, double n2){
    if (pr == PointRepresentation.Polar){
      radius = n1; angle = n2;
    } 
    else if (pr == PointRepresentation.Rectangular){
      radius = RadiusGivenXy(n1, n2);
      angle  = AngleGivenXy(n1, n2);
    } else {
      throw new Exception("Should not happen");
    }
  }   

  public override double X {
    get {
      return XGivenRadiusAngle(radius, angle);}
    set {
      double yBefore = YGivenRadiusAngle(radius, angle);
      angle = AngleGivenXy(value, yBefore);
      radius = RadiusGivenXy(value, yBefore);
    }
  }   

  public override double Y {
    get {
      return YGivenRadiusAngle(radius, angle);}
    set {
      double xBefore = XGivenRadiusAngle(radius, angle);
      angle = AngleGivenXy(xBefore, value);
      radius = RadiusGivenXy(xBefore, value);
    }
  }   

  public override double R {
    get {
     return radius;}
    set {
     radius = value;}
  }   

  public override double A {
    get {
     return angle;}
    set {
     angle = value;}
  }   

}

Program: Some client class of Point - Similar to a Point client from an earlier lecture.
// A client of Point that instantiates three points and calculates
// the circumference of the implied triangle.

using System;

public class Application{

  public static Point PromptPoint(string prompt){
    double x, y;
    AbstractPoint.PointRepresentation mode = 
         AbstractPoint.PointRepresentation.Rectangular;
    Console.WriteLine(prompt);
    x = Double.Parse(Console.ReadLine());
    y = Double.Parse(Console.ReadLine());
    return new Point(mode,x,y);
  }

  public static void Main(){
    AbstractPoint p1, p2, p3;
    double p1p2Dist, p2p3Dist,  p3p1Dist, circumference;

    p1 = PromptPoint("Enter first point");
    p2 = PromptPoint("Enter second point");
    p3 = PromptPoint("Enter third point");

    p1.Rotate(Math.PI);
    p2.Move(1.0, 2.0);

    p1p2Dist = Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + 
                         (p1.Y - p2.Y) * (p1.Y - p2.Y));
    p2p3Dist = Math.Sqrt((p2.X - p3.X) * (p2.X - p3.X) + 
                         (p2.Y - p3.Y) * (p2.Y - p3.Y));
    p3p1Dist = Math.Sqrt((p3.X - p1.X) * (p3.X - p1.X) + 
                         (p3.Y - p1.Y) * (p3.Y - p1.Y));

    circumference = p1p2Dist + p2p3Dist + p3p1Dist;

    Console.WriteLine("Circumference:\n {0}\n {1}\n {2}\n {3}", 
                       p1, p2, p3, circumference);
  }

}

Program: Output from the Some client class of Point.
Enter first point
1,1
2,2
Enter second point
3,3
4,4
Enter third point
5,5
6,6
Circumference:
 (-1,1, -2,2) [r:2,45967477524977, a:4,24874137138388]  
 (4,3, 6,4) [r:7,71038261048049, a:0,979196616459785]  
 (5,5, 6,6) [r:8,59127464349732, a:0,876058050598193]  
 22,3713543258885

Reference

Sealed Classes and Sealed Methods
Slide Annotated slide Contents Index
References Textbook 

A sealed class C prevents the use of C as base class of other classes

  • Sealed class

    • Cannot be inherited by other classes

    • Seals all virtual methods in the class

  • Sealed method

    • Cannot be redefined and overridden in a subclass

    • The modifier sealed must be used together with override

      • A sealed, overridden method prevents additional overriding


Interfaces

Interfaces
Slide Annotated slide Contents Index
References Textbook 

An interface corresponds to a fully abstract class. No matters of substance is found in the interface, just declarations of intent

The concept interface: An interface describes signatures of operations, but it does not implement any of them

  • Interfaces

    • Classes and structs can implement one or more interfaces

    • An interface can be used as a type, just like classes

      • Variables and parameters can be declared of interface types

    • Interfaces can be organized in multiple inheritance hierarchies

Program: Sketches of the classes Vehicle, FixedProperty, Bus, and House - for the exercise.
using System;

public class FixedProperty{

  protected string location;
  protected bool inCity; 
  protected decimal estimatedValue;

  public FixedProperty(string location, bool inCity, decimal value){
    this.location = location;
    this.inCity = inCity;
    this.estimatedValue = value;
  }

  public FixedProperty(string location):
    this(location,true,1000000){
  }

  public string Location{
    get{
      return location;
    }
  }
}

public class Vehicle{

  protected int registrationNumber;
  protected double maxVelocity;
  protected decimal value;

  public Vehicle(int registrationNumber, double maxVelocity, 
                 decimal value){
    this.registrationNumber = registrationNumber;
    this.maxVelocity = maxVelocity;
    this.value = value;
  }

  public int RegistrationNumber{
    get{
      return registrationNumber;
    }
  }

}

public class Bus: Vehicle{

  protected int numberOfSeats;

  public Bus(int numberOfSeats, int regNumber, decimal value):
      base(regNumber, 80, value){
    this.numberOfSeats = numberOfSeats;
  }

  public int NumberOfSeats{
    get{
     return numberOfSeats;
    }
  } 

}

public class House: FixedProperty {

  protected double area;

  public House(string location, bool inCity, double area, 
               decimal value):
      base(location, inCity, value){
   this.area = area;
  }

  public double Area{
    get{
     return area;
    }
  } 
}

Exercise 8.5. The interface ITaxable

For the purpose of this exercise you are given a couple of very simple classes called Bus and House. Class Bus specializes the class Vehicle. Class House specializes the class FixedProperty. All the classes are here.

First in this exercise, program an interface ITaxable with a parameterless operation TaxValue. The operation should return a decimal number.

Next, program variations of class House and class Bus which implement the interface ITaxable. Feel free to invent the concrete taxation of houses and busses. Notice that both class House and Bus have a superclass, namely FixedProperty and Vehicle, respectively. Therefore it is essential that taxation is introduced via an interface.

Demonstrate that taxable house objects and taxable bus objects can be used together as objects of type ITaxable.

Interfaces in C#
Slide Annotated slide Contents Index
References Textbook 

Both classes, structs and interfaces can implement one or more interfaces

Interfaces can contain signatures of methods, properties, indexers, and events

Syntax: The syntax of a C# interface, together with the syntaxes of method, property, indexer, and event descriptions in an interface

modifiers interface interface-name : base-interfaces {
  method-descriptions
  property-descriptions
  indexer-descriptions
  event-descriptions
}

return-type method-name(formal-parameter-list);

return-type property-name{
  get;
  set;
}

return-type this[formal-parameter-list]{
  get;
  set;
}

event delegate-type event-name;

Examples of Interfaces
Slide Annotated slide Contents Index
References Textbook 

Two or more unrelated classes can be used together if they implement the same interface

Program: The interface IGameObject.
public enum GameObjectMedium {Paper, Plastic, Electronic}

public interface IGameObject{

  int GameValue{
    get;
  }

  GameObjectMedium Medium{
    get;
  }
}

Program: The class Die which implements IGameObject.
using System;

public class Die: IGameObject {
  private int numberOfEyes;
  private Random randomNumberSupplier; 
  private readonly int maxNumberOfEyes;

  public Die (): this(6){}

  public Die (int maxNumberOfEyes){
    randomNumberSupplier = 
      new Random(unchecked((int)DateTime.Now.Ticks));
    this.maxNumberOfEyes = maxNumberOfEyes;
    numberOfEyes = NewTossHowManyEyes();
  }   
    
  public void Toss (){
    numberOfEyes = NewTossHowManyEyes();
  }

  private int NewTossHowManyEyes (){
    return randomNumberSupplier.Next(1,maxNumberOfEyes + 1);
  }

  public int NumberOfEyes() {
    return numberOfEyes;
  }

  public override String ToString(){
    return String.Format("Die[{0}]: {1}", maxNumberOfEyes, numberOfEyes);
  }

  public int GameValue{
    get{
      return numberOfEyes;
    }
  }

  public GameObjectMedium Medium{
    get{
      return 
       GameObjectMedium.Plastic;
    }
  }   

}

Program: The class Card which implements IGameObject.
using System;

public class Card: IGameObject{
  public enum CardSuite { spades, hearts, clubs, diamonds };
  public enum CardValue { two = 2, three = 3, four = 4, five = 5, 
                          six = 6, seven = 7, eight = 8, nine = 9,
                          ten = 10, jack = 11, queen = 12, king = 13,
                          ace = 14 };

  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }

  public int GameValue{
    get { return (int)(this.value); }
  }

  public GameObjectMedium Medium{
    get{
      return GameObjectMedium.Paper;
    }
  }   
}

Program: A sample Client program of Die and Card.
using System;
using System.Collections.Generic;

class Client{

  public static void Main(){

    Die d1 = new Die(),
        d2 = new Die(10),
        d3 = new Die(18);

    Card c1 =  new Card(Card.CardSuite.spades, Card.CardValue.queen),
         c2 =  new Card(Card.CardSuite.clubs, Card.CardValue.four),
         c3 =  new Card(Card.CardSuite.diamonds, Card.CardValue.ace);

    IGameObject[] gameObjects = {d1, d2, d3, c1, c2, c3};

    foreach(IGameObject gao in gameObjects){
      Console.WriteLine("{0}: {1} {2}", 
                        gao, gao.GameValue, gao.Medium); 
    }
  }
}

Program: Output from the sample Client program of Die and Card.
Die[6]: 5: 5 Plastic
Die[10]: 9: 9 Plastic
Die[18]: 15: 15 Plastic
Suite:spades, Value:queen: 12 Paper
Suite:clubs, Value:four: 4 Paper
Suite:diamonds, Value:ace: 14 Paper

In the example above, the GameObject could as well have been implemented as an abstract superclass

Exercise 8.6. An abstract GameObject class

On the slide, to which this exercise belongs, we have written an interface IGameObject which is implemented by both class Die and class Card.

Restructure this program such that class Die and class Card both inherit an abstract class GameObject. You should write the class GameObject.

The client program should survive this restructuring. (You may, however, need to change the name of the type IGameObject to GameObject). Compile and run the given client program with your classes.

Interfaces from the C# Libraries
Slide Annotated slide Contents Index
References Textbook 

The C# library contains a number of important interfaces which are used frequently in many C# programs

  • IComparable

    • An interface that prescribes a CompareTo method

    • Used to support general sorting and searching methods

  • IEnumerable

    • An interface that prescribes a method for accessing an enumerator

  • IEnumerator

    • An interface that prescribes methods for traversal of data collections

    • Supports the underlying machinery of the foreach control structure

  • IDisposable

    • An interface that prescribes a Dispose method

    • Used for deletion of resources that cannot be deleted by the garbage collector

    • Supports the C# using control structure

  • ICloneable

    • An interface that prescribes a Clone method

  • IFormattable

    • An interface that prescribes an extended ToString method

References

Sample use of IComparable
Slide Annotated slide Contents Index
References Textbook 

Object of classes that implement IComparable can be sorted by a method such as Array.Sort

Program: A reproduction of the interface IComparable.
using System;

public interface IComparable{
  int CompareTo(Object other);
}

References

  • x.CompareTo(y)

    • Negative: x is less than y

    • Zero: x is equal to y

    • Positive: x is greater than y

Exercise 8.7. Comparable Dice

In this exercise we will arrange that two dice can be compared to each other. The result of die1.CompareTo(die2) is an integer. If the integer is negative, die1 is considered less than die2; If zero, die1 is considered equal to die2; And if positive, die1 is considered greater than die2. When two dice can be compared to each other, it is possible sort an array of dice with the standard Sort method in C#.

Program a version of class Die which implements the interface System.IComparable.

Consult the documentation of the (overloaded) static method System.Array.Sort and locate the Sort method which relies on IComparable elements.

Make an array of dice and sort them by use of the Sort method.

Sample use of IEnumerator and IEnumerable
Slide Annotated slide Contents Index
References Textbook 

The interface IEnumerator in System.Collections is used as the basis for iteration with foreach

Program: A reproduction of the interface IEnumerable.
using System.Collections;

public interface IEnumerable{
  IEnumerator GetEnumerator();
}

Program: A reproduction of the interface IEnumerator.
using System;

public interface IEnumerator{
  Object Current{
    get;
  }

  bool MoveNext();
 
  void Reset();
}

Program: IEnumerator in the type Interval.
using System;
using System.Collections;

public struct Interval: IEnumerable{

  private readonly int from, to;

  public Interval(int from, int to){
    this.from = from;
    this.to = to;
  }

  public int From{
    get {return from;}
  }

  public int To{
    get {return to;}
  }

  public int Length{
    get {return Math.Abs(to - from) + 1;}
  }

  public int this[int i]{
    get {if (from <= to){
           if (i >= 0 && i <= Math.Abs(from-to))
               return from + i;
           else throw new Exception("Error"); }
         else if (from > to){
           if (i >= 0 && i <= Math.Abs(from-to))
               return from - i;
           else throw new Exception("Error"); }
         else throw new Exception("Should not happen"); }
  }

  // Overloaded operators have been hidden in this version

  private class IntervalEnumerator: IEnumerator{
 
    private readonly Interval interval; 
    private int idx;

    public IntervalEnumerator (Interval i){
      this.interval = i;
      idx = -1;   // position enumerator outside range
    }
 
    public Object Current{ 
         get {return (interval.From < interval.To) ? 
                       interval.From + idx :
                       interval.From - idx;}
    }

    public bool MoveNext (){
      if ( idx < Math.Abs(interval.To - interval.From))
         {idx++; return true;}
      else
         {return false;}
    }

    public void Reset(){
      idx = -1;         
    }
  }    
    
  public IEnumerator GetEnumerator (){
    return new IntervalEnumerator(this);
  }

}

Program: Iteration with and without foreach based on the enumerator.
using System;
using System.Collections;

public class app {

  public static void Main(){

    Interval iv1 = new Interval(14,17);

    foreach(int k in iv1){
      Console.Write("{0,4}", k);
    }
    Console.WriteLine();

    IEnumerator e = iv1.GetEnumerator();
    while (e.MoveNext()){
      Console.Write("{0,4}", (int)e.Current);
    }  
    Console.WriteLine();       
  }

}

References

The interface IEnumerable prescribes an operation GetEnumerator that returns an enumerator of type IEnumerator

Sample use of IFormattable
Slide Annotated slide Contents Index
References Textbook 

The ToString method prescribed by IFormattable takes two parameters which allow finer control of the string generation than the parameterless method ToString from class Object.

Program: A reproduction of the interface IFormattable.
using System;

public interface IFormattable{
  string ToString(string format, IFormatProvider formatProvider);
}

  • The ToString method of IFormattable:

    • The first parameter is typically a single letter formatting string, and the other is an IFormatProvider

    • The IformatProvider can provide culture sensible information.

    • ToString from Object typically calls ToString(null, null)

Program: The struct Card that implements IFormattable.
using System;

public enum CardSuite:byte 
          {Spades, Hearts, Clubs, Diamonds };
public enum CardValue: byte 
          {Ace = 1, Two = 2, Three = 3, Four = 4, Five = 5, 
           Six = 6, Seven = 7, Eight = 8, Nine = 9, Ten = 10, 
           Jack = 11, Queen = 12, King = 13};

public struct Card: IFormattable{
  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
   this.suite = suite;
   this.value = value;
  }
 
  // Card methods and properties here... 
 
  public System.Drawing.Color Color (){
   System.Drawing.Color result;
   if (suite == CardSuite.Spades || suite == CardSuite.Clubs)
     result = System.Drawing.Color.Black;
   else
     result = System.Drawing.Color.Red;
   return result;
  }

  public override String ToString(){
    return this.ToString(null, null);
  }
 
  public String ToString(string format, IFormatProvider fp){
    if (format == null || format == "G" || format == "L") 
        return String.Format("Card Suite: {0}, Value: {1}, Color: {2}", 
                              suite, value, Color().ToString());

    else if (format == "S") 
        return String.Format("Card {0}: {1}", suite, (int)value);

    else if (format == "V") 
        return String.Format("Card value: {0}", value);

    else throw new FormatException(
                     String.Format("Invalid format: {0}", format));
  }   

}

Program: A client of Card which applies formatting of cards.
using System;

class CardClient{

  public static void Main(){
    Card c1 = new Card(CardSuite.Hearts, CardValue.Eight),
         c2 = new Card(CardSuite.Diamonds, CardValue.King);

    Console.WriteLine("c1 is a {0}", c1);
    Console.WriteLine("c1 is a {0:S}", c1); Console.WriteLine();

    Console.WriteLine("c2 is a {0:S}", c2);
    Console.WriteLine("c2 is a {0:L}", c2);
    Console.WriteLine("c2 is a {0:V}", c2);


  }

}

Program: Output from the client program.
c1 is a Card Suite: Hearts, Value: Eight, Color: Color [Red]
c1 is a Card Hearts: 8

c2 is a Card Diamonds: 13
c2 is a Card Suite: Diamonds, Value: King, Color: Color [Red]
c2 is a Card value: King

Explicit Interface Member Implementations
Slide Annotated slide Contents Index
References Textbook 

If a member of an interface collides with a member of a class, the member of the interface can be implemented as an explicit interface member

Explicit interface members can also be used to implement several interfaces with colliding members

Program: The class Playing card with a property Value.
using System;

public class Card{
  public enum CardSuite { spades, hearts, clubs, diamonds };
  public enum CardValue { two = 2, three = 3, four = 4, five = 5, 
                          six = 6, seven = 7, eight = 8, nine = 9,
                          ten = 10, jack = 11, queen = 12, king = 13,
                          ace = 14 };

  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }
}

Program: The Interface IGameObject with a conflicting Value property.
public enum GameObjectMedium {Paper, Plastic, Electronic}

public interface IGameObject{

  int Value{
    get;
  }

  GameObjectMedium Medium{
    get;
  }
}

Program: A class Card which implements IGameObject.
using System;

public class Card: IGameObject{
  public enum CardSuite { spades, hearts, clubs, diamonds };
  public enum CardValue { two = 2, three = 3, four = 4, five = 5, 
                          six = 6, seven = 7, eight = 8, nine = 9,
                          ten = 10, jack = 11, queen = 12, king = 13,
                          ace = 14 };

  private CardSuite suite;
  private CardValue value;

  public Card(CardSuite suite, CardValue value){
    this.suite = suite;
    this.value = value;
  }

  public CardSuite Suite{
    get { return this.suite; }
  }

  public CardValue Value{
    get { return this.value; }
  }

  public override String ToString(){
    return String.Format("Suite:{0}, Value:{1}", suite, value);
  }

  int IGameObject.Value{
    get { return (int)(this.value); }
  }

  public GameObjectMedium Medium{
    get{
      return GameObjectMedium.Paper;
    }
  }
}

Program: Sample use of class Card in a Client class.
using System;

class Client{

  public static void Main(){
    Card cs = 
       new Card(Card.CardSuite.spades, Card.CardValue.queen);

    // Use of Value from Card
    Console.WriteLine(cs.Value);

    // Must cast to use the implementation of 
    // Value from IGameObject
    Console.WriteLine(((IGameObject)cs).Value);
  }
}

Program: Output of Card Client.
queen
12


Patterns and Techniques

The Composite design pattern
Slide Annotated slide Contents Index
References Textbook 

Reference

A Composite design pattern composes objects into tree structures.

Clients operate uniformly on leaves and composite nodes

A template of the class structure in the Composite design pattern.

The tree structure may be non-mutable and built via constructors

Alternatively, the tree structure may be mutable, and built via Add and Remove operations

A Composite Example: Music Elements
Slide Annotated slide Contents Index
References Textbook 

A music element can either be a note, a pause, a sequential or a parallel collection of music elements

A music element has operations such as Duration, Transpose, and Linearize

The class diagram of Music Elements

An application of Music Elements
Slide Annotated slide Contents Index
References Textbook 

Program: An application of some MusicElement objects.
public class Application{

  public static void Main(){

    MusicElement someMusic =
     SequentialMusicElement.MakeSequentialMusicElement(
       SequentialMusicElement.MakeSequentialMusicElement(
         new Note(60, 480),      
         new Pause(480),     
         new Note(64, 480), 
         new Note(60, 480)),
       ParallelMusicElement.MakeParallelMusicElement(
         new Note(60, 960),
         new Note(64, 960),
         new Note(67, 960)
       ));

    Song aSong = new Song(someMusic.Linearize(0));
    aSong.WriteStandardMidiFile("song.mid");
  }
}

Reference

A possible tree of objects which represent various music elements. Nodes named Ni are Note instances, and the node named P is a Pause instance

References

Implementation of MusicElement classes
Slide Annotated slide Contents Index
References Textbook 

The classes in the MusicElement composite

Program: The abstract class MusicElement.
public abstract class MusicElement{

  public abstract int Duration{
    get;
  }

  public abstract MusicElement Transpose(int levels);

  public abstract TimedNote[] Linearize(int startTime);
}

Program: The class Note.
using System;

public class Note: MusicElement{
  
  private byte value;
  private int duration;
  private byte volume;
  private Instrument instrument;

  public Note(byte value, int duration, byte volume, 
              Instrument instrument){
    this.value = value;
    this.duration = duration;
    this.volume = volume;
    this.instrument = instrument;
  }

  public Note(byte value, int duration):
   this(value, duration, 64, Instrument.Piano){
  }

  public override int Duration{
    get{
      return duration;
    }
  }

  public override TimedNote[] Linearize(int startTime){
    TimedNote[] result = new TimedNote[1];
    result[0] = new TimedNote(startTime, value, duration, volume, 
                              instrument);
    return result;
  }

  public override MusicElement Transpose(int levels){
     return new Note(Util.ByteBetween(value + levels, 0, 127),
                     duration, volume, instrument);
  }
}

Program: The class Pause.
using System;

public class Pause: MusicElement{
  
  private int duration;

  public Pause(int duration){
    this.duration = duration;
  }

  public override int Duration{
    get{
      return duration;
    }
  }

  public override TimedNote[] Linearize(int startTime){
    return new TimedNote[0];
  }

  public override MusicElement Transpose(int levels){
     return new Pause(this.Duration);
  }
}

Program: The class SequentialMusicElement.
using System;
using System.Collections.Generic;

public class SequentialMusicElement: MusicElement{
  private List<MusicElement> elements;
  
  public SequentialMusicElement(MusicElement[] elements){
    this.elements = new List<MusicElement>(elements);
  }

  // Factory method:
  public static MusicElement 
    MakeSequentialMusicElement(params MusicElement[] elements){
      return new SequentialMusicElement(elements);
  }

  public override TimedNote[] Linearize(int startTime){
    int time = startTime;
    List<TimedNote> result = new List<TimedNote>();
    
    foreach(MusicElement me in elements){
      result.AddRange(me.Linearize(time));
      time = time + me.Duration;
    }

    return result.ToArray();
  }

  public override int Duration{
    get{
      int result = 0;
   
      foreach(MusicElement me in elements){
        result += me.Duration;
      }
 
      return result;
    }
  }

  public override MusicElement Transpose(int levels){
    List<MusicElement> transposedElements = new List<MusicElement>();

    foreach(MusicElement me in elements)
      transposedElements.Add(me.Transpose(levels));
    
    return new SequentialMusicElement(transposedElements.ToArray());
  }
}

Program: The class ParallelMusicElement.
using System;
using System.Collections.Generic;

public class ParallelMusicElement: MusicElement{
  private List<MusicElement> elements;
  
  public ParallelMusicElement(MusicElement[] elements){
    this.elements = new List<MusicElement>(elements);
  }

  // Factory method:
  public static MusicElement 
    MakeParallelMusicElement(params MusicElement[] elements){
      return new ParallelMusicElement(elements);
  }  

  public override TimedNote[] Linearize(int startTime){
    int time = startTime;
    List<TimedNote> result = new List<TimedNote>();
    
    foreach(MusicElement me in elements){
      result.AddRange(me.Linearize(time));
      time = startTime;
    }

    return result.ToArray();
  }

  public override int Duration{
    get{
      int result = 0;
   
      foreach(MusicElement me in elements){
        result = Math.Max(result, me.Duration);
      }
 
      return result;
    }
  }

  public override MusicElement Transpose(int levels){
    List<MusicElement> transposedElements = new List<MusicElement>();

    foreach(MusicElement me in elements)
      transposedElements.Add(me.Transpose(levels));
    
    return new ParallelMusicElement(transposedElements.ToArray());
  }
}

A Composite example: IntSequence
Slide Annotated slide Contents Index
References 

An integer sequence can either be a singular integer, an interval, or a composite sequence

An integer sequence has the operations Min, Max, and GetEnumerator

The class diagram of the IntSequence composite.

A Composite example: IntSequence application
Slide Annotated slide Contents Index
References 

An IntSequence Composite

Program: An application of IntSequence objects.
using System;

class SeqApp {

  public static void Main(){

    IntSequence isq = 
      new IntCompSeq(
            new IntCompSeq(
              new IntInterval(3,5), new IntSingular(17) ),
            new IntCompSeq(
              new IntInterval(12,7), new IntSingular(29) ) );

    Console.WriteLine("Min: {0} Max: {1}", isq.Min, isq.Max);

    foreach(int i in isq)
      Console.Write("{0,4}", i);
  }

}

Program: Output from the IntSequence application.
Min: 3 Max:29
   3   4   5  17  12  11  10   9   8   7  29

A possible tree of objects which represent the integer sequence 3, 4, 5, 17, 12, 11, 10, 9, 8, 7, 29.

Implementation of the IntSequence classes
Slide Annotated slide Contents Index
References 

The classes in the IntSequence composite design pattern

Program: The abstract class IntSequence.
public abstract class IntSequence: IEnumerable {
  public abstract IEnumerator GetEnumerator();
  public abstract int Min {get;}
  public abstract int Max {get;}
}    

Program: The class IntInterval.
public class IntInterval: IntSequence{

  private int from, to;

  public IntInterval(int from, int to){
    this.from = from;
    this.to = to;
  }

  public override int Min{
    get {return Math.Min(from,to);}
  }

  public override int Max{
    get {return Math.Max(from,to);}
  }
    
  public override IEnumerator GetEnumerator (){
    return new IntervalEnumerator(this);
  }

  private class IntervalEnumerator: IEnumerator{
 
    private readonly IntInterval interval; 
    private int idx;

    public IntervalEnumerator (IntInterval i){
      this.interval = i;
      idx = -1;   // position enumerator outside range
    }
 
    public Object Current{ 
         get {return (interval.from < interval.to) ? 
                       interval.from + idx :
                       interval.from - idx;}
    }

    public bool MoveNext (){
      if ( idx < Math.Abs(interval.to - interval.from))
         {idx++; return true;}
      else
         {return false;}
    }

    public void Reset(){
      idx = -1;         
    }
  }

}    

Program: The class IntSingular.
public class IntSingular: IntSequence{

  private int it;

  public IntSingular(int it){
    this.it = it;
  }

  public override int Min{
    get {return it;}
  }

  public override int Max{
    get {return it;}
  }

  public override IEnumerator GetEnumerator (){
    return new SingularEnumerator(this);
  }

  private class SingularEnumerator: IEnumerator{
 
    private readonly IntSingular ints; 
    private int idx;

    public SingularEnumerator (IntSingular ints){
      this.ints = ints;
      idx = -1;   // position enumerator outside range
    }
 
    public Object Current{ 
         get {return ints.it;}
    }

    public bool MoveNext (){
      if (idx == -1)
         {idx++; return true;}
      else
         {return false;}
    }

    public void Reset(){
      idx = -1;         
    }
  }
}    

Program: The class IntCompSeq.
public class IntCompSeq: IntSequence{

  private IntSequence s1, s2;

  public IntCompSeq(IntSequence s1, IntSequence s2) {
    this.s1 = s1;
    this.s2 = s2;
  }

  public override int Min{
    get {return Math.Min(s1.Min,s2.Min);}
  }

  public override int Max{
    get {return Math.Max(s1.Max, s2.Max);}
  }

  public override IEnumerator GetEnumerator (){
    return new CompositeEnumerator(this);
  }

  private class CompositeEnumerator: IEnumerator{

    private IntSequence s1, s2;
    private int idx;   //  0: not started.  
                       //  1: s1 is current.  2:  s2 is current.
    private IEnumerator idxEnumerator;
 
    public CompositeEnumerator (IntCompSeq outerIntCompSeq){
      this.s1 = outerIntCompSeq.s1;
      this.s2 = outerIntCompSeq.s2;
      idx = 0;   // 0: outside.   1: at s1.   2: at s2
      idxEnumerator = null;
    }
 
    public Object Current{ 
     get {return idxEnumerator.Current;}
    }

    public bool MoveNext (){
       if (idx == 0){    // At start position.
         idx = 1;
         idxEnumerator = s1.GetEnumerator();
         return idxEnumerator.MoveNext();    
      } else if (idx == 1){      // At left sequence
         bool hasMoved1 = idxEnumerator.MoveNext();
         if (hasMoved1) 
           return true;
         else{
           idxEnumerator = s2.GetEnumerator(); idx = 2;
           return idxEnumerator.MoveNext();
        }
       } else if (idx == 2) {    // At right sequence
           bool hasMoved2 = idxEnumerator.MoveNext();
           if (hasMoved2) 
             return true;
           else return false;
       } else return false;
    }

    public void Reset(){
      idx = 0;         
    }
  }
}    

A Composite Example: A GUI
Slide Annotated slide Contents Index
References Textbook 

The classes of a Graphical User Interface (GUI) make up a Composite design pattern

Program: A program that builds a sample composite graphical user interface.
using System;
using System.Windows.Forms;
using System.Drawing;

// In System:
// public delegate void EventHandler (Object sender, EventArgs e)

public class Window: Form{

  Button b1, b2, paBt;
  Panel pa;
  TextBox tb, paTb;

  // Constructor
  public Window (){
    this.Size=new Size(150,300);

    b1 = new Button();
    b1.Text="Click Me";
    b1.Size=new Size(100,25);
    b1.Location = new Point(25,25);
    b1.BackColor = Color.Yellow;
    b1.Click += ClickHandler; 
                              // Alternatively:
                              // b1.Click+=new EventHandler(ClickHandler);
    b2 = new Button();
    b2.Text="Erase";
    b2.Size=new Size(100,25);
    b2.Location = new Point(25,55);
    b2.BackColor=Color.Green; 
    b2.Click += EraseHandler; 
                              // Alternatively:
                              // b2.Click+=new EventHandler(EraseHandler);
    tb = new TextBox();
    tb.Location = new Point(25,100);
    tb.Size=new Size(100,25);
    tb.BackColor=Color.White;
    tb.ReadOnly=true;
    tb.RightToLeft=RightToLeft.Yes;

    pa = new Panel();
    pa.Location = new Point(25,150);
    pa.Size=new Size(100, 75);
    pa.BackColor=Color.Red;

    paBt = new Button();
    paBt.Text="A";
    paBt.Location = new Point(10,10);
    paBt.Size=new Size(25,25);
    paBt.BackColor=Color.Blue; 
    paBt.Click += PanelButtonClickHandler;

    paTb = new TextBox();
    paTb.Location = new Point(10,40);
    paTb.Size=new Size(50,25);
    paTb.BackColor=Color.Gray;
    paTb.ReadOnly=true;
    paTb.RightToLeft=RightToLeft.Yes;

    this.Controls.Add(b1);
    this.Controls.Add(b2);
    this.Controls.Add(tb);

    pa.Controls.Add(paBt);
    pa.Controls.Add(paTb);

    this.Controls.Add(pa);
  }

  // Eventhandler:
  private void ClickHandler(object obj, EventArgs ea) {
    tb.Text = "You clicked me";
  }

  // Eventhandler:
  private void PanelButtonClickHandler(object obj, EventArgs ea) {
    paTb.Text += "A";
  }

  // Eventhandler:
  private void EraseHandler(object obj, EventArgs ea) {
    tb.Text = "";
  }

}

class ButtonTest{

  public static void Main(){
    Window win = new Window();
    Application.Run(win);
  }

}

Figure. A Form (Window) with two buttons, a textbox, and a panel.

A Composite Example: A GUI
Slide Annotated slide Contents Index
References Textbook 

The object structure of the sample graphical user interface

The tree of objects behind the graphical user interface. These objects represents a composite design pattern in an executing program.

A Composite Example: A GUI
Slide Annotated slide Contents Index
References Textbook 

An small extract of the Windows Form Component class structure

We recognize the structure of a Composite design pattern

An extract of the Windows Form classes for GUI building. We see two Composites among these classes.

Cloning
Slide Annotated slide Contents Index
References Textbook 

The concept cloning: Cloning creates a copy of an existing object

Reference

  • Object cloning

    • Shallow cloning:

      • Instance variables of value type: Copied bit-by-bit

      • Instance variables of reference types:

        • The reference is copied

        • The object pointed at by the reference is not copied

    • Deep cloning:

      • Like shallow cloning

      • But objects referred by references are copied recursively

Cloning in C#
Slide Annotated slide Contents Index
References Textbook 

Internally, C# supports shallow cloning of every object.

Externally, it must be decided on a class-by-class basis if cloning is supported.

References

  • Cloning in C#

    • Shallow cloning is facilitated by the protected method MemberwiseClone in System.Object

    • A class C that allows clients to clone instances of C should implement the interface ICloneable

Program: A reproduction of the interface ICloneable.
using System;

public interface ICloneable{
  Object Clone();
}

Program: A cloneable class Point.
using System;

public class Point: ICloneable {
  private double x, y;

  public Point(double x, double y){
   this.x = x; this.y = y;
  }

  public double X {
    get {return x;}
    set {x = value;}
  }

  public double Y {
    get {return y;}
    set {y = value;}
  }

  public Point move(double dx, double dy){
    Point result = (Point)MemberwiseClone();  // cloning from within Point is OK.
    result.x = x + dx;
    result.y = y + dy;   
    return result;
  }

  // public Clone method that delegates the work of
  // the protected method MemberwiseClone();       
  public Object Clone(){
    return MemberwiseClone();
  }

  public override string ToString(){
    return "Point: " + "(" + x + "," + y + ")" + ".";
  }
}

Program: A sample client of class Point.
using System;

public class Application{

  public static void Main(){
    Point p1 = new Point(1.1, 2.2),
          p2, p3;

    p2 = (Point)p1.Clone();  // First p1.Clone(), then cast to Point.
    p3 = p1.move(3.3, 4.4);
    Console.WriteLine("{0} {1} {2}", p1, p2, p3);
  }

}

Program: Illegal cloning with MemberwiseClone.
using System;

public class Application{

  public static void Main(){
    Point p1 = new Point(1.1, 2.2),
          p2, p3;

    p2 = (Point)p1.MemberwiseClone();  
    // Compile-time error.
    // Cannot access protected member 'object.MemberwiseClone()'
    // via a qualifier of type 'Point'

    p3 = p1.move(3.3, 4.4);
    Console.WriteLine("{0} {1} {2}", p1, p2, p3);
  }

}

The fragile base class problem
Slide Annotated slide Contents Index
References Textbook 

If all methods are virtual it is possible to introduce erroneous dynamic bindings

This can happen if a new method in a superclass is given the same name as a dangerous method in a subclass

Program: The initial program.
// Original program. No problems.

using System;

class A {

  public void M1(){
    Console.WriteLine("Method 1");
  }
}

class B: A {
  
  public void M2(){
    Console.WriteLine("Dangerous Method 2");
  }
}

class Client{
  
  public static void Main(){
    A a = new B();
    B b = new B();

    a.M1();  // Nothing dangerous expected
//  a.M2();  // Compile-time error
             // 'A' does not contain a definition for 'M2'
    b.M2();  // Expects dangerous operation
  }
}

Program: Output of original program.
Method 1
Dangerous Method 2

Program: A revised program with a new version of class A with a method M2.
// We have added M2 to class A.
// In addtion, M1 now calls M2.
// Does not compile.

using System;

// New version of A
class A {

  public void M1(){
    Console.WriteLine("Method 1");
    this.M2();
  }

  // New method in this version.
  // Same name as the dangerous operation in subclass B
  public void M2(){
    Console.WriteLine("M2 in new version of A");
  }

}

class B: A {
  
  // Compile-time error in C#:
  // 'B.M2()' hides inherited member 'A.M2()'.
  //     Use the new keyword if hiding was intended.
  public void M2(){
    Console.WriteLine("Dangerous Method 2");
  }
}

class Client{
  
  public static void Main(){
    A a = new B();
    B b = new B();

    a.M1();  // Nothing dangerous expected.
             // Will, however, call the dangerous operation
             // if M2 is regarded as virtual.

    a.M2();  // Makes sense when M2 exists in class A.
             // Dangerous

    b.M2();  // Expects dangerous operation
  }
}

Program: The revised version with method A.M2 being virtual.
// Dangerous program.
// M2 is virtual in A and overridden in B.
// Compiles and runs
// Default Java semantics.

using System;

// New version of A
class A {

  public void M1(){
    Console.WriteLine("Method 1");
    this.M2();
  }

  // New method in this version.
  // Same name as the dangerous operation in subclass B
  public  virtual  void M2(){
    Console.WriteLine("M2 in new version of A");
  }

}

class B: A {

  public override void M2(){
    Console.WriteLine("Dangerous Method 2");
  }
}

class Client{
  
  public static void Main(){
    A a = new B();
    B b = new B();

    a.M1();  // Nothing dangerous expected
             // Will, however, call the dangerous operation
             // because M2 is virtual.

    a.M2();  // Makes sense when M2 exists in class A.
             // Calls the dangerous method

    b.M2();  // Calls the dangerous method.
             // This is expected, however.
  }
}

Program: Output of revised program.
Method 1
Dangerous Method 2
Dangerous Method 2
Dangerous Method 2

Program: The revised version with method A.M2 being hidden.
// Non-Dangerous program.
// Compiles and runs.
// M2 is declared new in B.
// A.M2 is not virtual.

using System;

// New version of A
class A {

  public void M1(){
    Console.WriteLine("Method 1");
    this.M2();
  }

  // New method in this version.
  // Same name as the dangerous operation in subclass B
  // M2 is not virtual.
  public void M2(){
    Console.WriteLine("M2 in new version of A");
  }

}

class B: A {

  public  new  void M2(){
    Console.WriteLine("Dangerous Method 2");
  }
}

class Client{
  
  public static void Main(){
    A a = new B();
    B b = new B();

    a.M1();  // Nothing dangerous expected
             // Will call a.M2 - the non-dangerous version

    a.M2();  // Makes sense when M2 exists in class A
             // Will call a.M2 - the non-dangerous version

    b.M2();  // Expects dangerous operation
  }
}

Program: Output of revised program.
Method 1
M2 in new version of A
M2 in new version of A
Dangerous Method 2

The Visitor design pattern
Slide Annotated slide Contents Index
References Textbook 

Visitor organizes operations on Composites which need to traverse the Component nodes

A template of the class structure in the Composite design pattern.

  • The natural object-oriented solution:

    • One method per operation per Component class

  • The Visitor solution

    • All methods that pertain to a given operation are refactored and encapsulated in its own class

Natural object-oriented IntSequence traversals
Slide Annotated slide Contents Index
References Textbook 

We will study Min, Max, and Sum traversals of integer sequences

Integer sequences are represented as a Composite (trees) of intervals, singulars and composite sequences

Reference

The class diagram of the IntSequence composite.

Program: The abstract class IntSequence.
public abstract class IntSequence {
  public abstract int Min {get;}
  public abstract int Max {get;}
  public abstract int Sum();
}    

Program: The class IntInterval.
public class IntInterval: IntSequence{

  private int from, to;

  public IntInterval(int from, int to){
    this.from = from;
    this.to = to;
  }

  public int From{
    get{return from;}
  }

  public int To{
    get{return to;}
  }

  public override int Min{
    get {return Math.Min(from,to);}
  }

  public override int Max{
    get {return Math.Max(from,to);}
  }
    
  public override int Sum(){
    int res = 0;
    int lower = Math.Min(from,to),
        upper = Math.Max(from,to);

    for (int i = lower; i <= upper; i++) 
       res += i;
    return res;
  }
}    

Program: The class IntSingular.
public class IntSingular: IntSequence{

  private int it;

  public IntSingular(int it){
    this.it = it;
  }

  public int TheInt{
     get{return it;}
  }

  public override int Min{
    get {return it;}
  }

  public override int Max{
    get {return it;}
  }

  public override int Sum(){
    return it;
  }
}    

Program: The class IntCompSeq.
public class IntCompSeq: IntSequence{

  private IntSequence s1, s2;  // Binary sequence: Exactly two subsequences.

  public IntCompSeq(IntSequence s1, IntSequence s2) {
    this.s1 = s1;
    this.s2 = s2;
  }

  public IntSequence First{
    get{return s1;}
  }

  public IntSequence Second{
    get{return s2;}
  }

  public override int Min{
    get {return Math.Min(s1.Min, s2.Min);}
  }

  public override int Max{
    get {return Math.Max(s1.Max, s2.Max);}
  }

  public override int Sum(){
    return s1.Sum() + s2.Sum();
  }
}    

Program: A sample application of IntSequences.
using System;

class SeqApp {

  public static void Main(){

    IntSequence isq = 
      new IntCompSeq(
            new IntCompSeq(
              new IntInterval(3,5), new IntSingular(-7) ),
            new IntCompSeq(
              new IntInterval(12,7), new IntCompSeq(
                                           new IntInterval(18,-18),
                                           new IntInterval(3,5)
                                           )));

    Console.WriteLine("Min: {0} Max: {1}", isq.Min, isq.Max);
    Console.WriteLine("Sum: {0}", isq.Sum());
  }

}

Program: Program output.
Min: -18 Max: 18
Sum: 74

Towards a Visitor solution
Slide Annotated slide Contents Index
References Textbook 

The transition from natural object-oriented traversals to Visitor

  • Steps:

    • A Visitor interface and three concrete Visitor classes are defined

    • The Intsequence classes are refactored - the traversal methods are moved to the visitor classes

    • Accept methods are defined in the IntSequence classes. Accept takes a Visitor as parameter

    • Accept passes this to the visitor, which in turn may activate Accept on components

Try the accompanying SVG animation

A Visitor example: IntSequence
Slide Annotated slide Contents Index
References Textbook 

Program: The Visitor Interface.
public interface Visitor{
  int Visit (IntInterval iv);
  int Visit (IntSingular iv);
  int Visit (IntCompSeq iv);
}    

Program: The abstract class IntSequence.
public abstract class IntSequence {
  public abstract int Accept(Visitor v);
}    

Program: The class IntInterval.
public class IntInterval: IntSequence{

  private int from, to;

  public IntInterval(int from, int to){
    this.from = from;
    this.to = to;
  }

  public int From{
    get{return from;}
  }

  public int To{
    get{return to;}
  }

  public override int Accept(Visitor v){
    return v.Visit(this);
  } 
}    

Program: The class IntSingular.
public class IntSingular: IntSequence{

  private int it;

  public IntSingular(int it){
    this.it = it;
  }

  public int TheInt{
     get{return it;}
  }

  public override int Accept(Visitor v){
    return v.Visit(this);
  } 
}    

Program: The class IntCompSeq.
public class IntCompSeq: IntSequence{

  private IntSequence s1, s2;  // Binary sequence: Exactly two subsequences.

  public IntCompSeq(IntSequence s1, IntSequence s2) {
    this.s1 = s1;
    this.s2 = s2;
  }

  public IntSequence First{
    get{return s1;}
  }

  public IntSequence Second{
    get{return s2;}
  }

  public override int Accept(Visitor v){
    return v.Visit(this);
  } 
}    

Program: The class MinVisitor.
public class MinVisitor: Visitor{
  public int Visit (IntInterval iv){
    return Math.Min(iv.From, iv.To);
  }

  public int Visit (IntSingular iv){
    return iv.TheInt;
  }

  public int Visit (IntCompSeq iv){
    return Math.Min(iv.First.Accept(this), 
                    iv.Second.Accept(this));
  }
}    

Program: The class MaxVisitor.
public class MaxVisitor: Visitor{
  public int Visit (IntInterval iv){
    return Math.Max(iv.From, iv.To);
  }

  public int Visit (IntSingular iv){
    return iv.TheInt;
  }

  public int Visit (IntCompSeq iv){
    return Math.Max(iv.First.Accept(this), 
                    iv.Second.Accept(this));
  }
}    

Program: The class SumVisitor.
public class SumVisitor: Visitor{
  public int Visit (IntInterval iv){
    int res = 0;
    int lower = Math.Min(iv.From,iv.To),
        upper = Math.Max(iv.From,iv.To);

    for (int i = lower; i <= upper; i++) 
       res += i;
    return res;
  }

  public int Visit (IntSingular iv){
    return iv.TheInt;
  }

  public int Visit (IntCompSeq iv){
    return (iv.First.Accept(this) + 
            iv.Second.Accept(this));
  }
}    

Program: A sample application of IntSequences and visitors.
using System;

class SeqApp {

  public static void Main(){

    IntSequence isq = 
      new IntCompSeq(
            new IntCompSeq(
              new IntInterval(3,5), new IntSingular(-7) ),
            new IntCompSeq(
              new IntInterval(12,7), new IntCompSeq(
                                           new IntInterval(18,-18),
                                           new IntInterval(3,5)
                                           )));
    Visitor min = new MinVisitor();
    Visitor max = new MaxVisitor();
    Visitor sum = new SumVisitor();


    Console.WriteLine("Min: {0} Max: {1}", isq.Accept(min),
                                           isq.Accept(max));

//  Alternative activation of Visit methods:
//  Console.WriteLine("Min: {0} Max: {1}", min.Visit((IntCompSeq)isq), 
//                                         max.Visit((IntCompSeq)isq));

    Console.WriteLine("Sum: {0}", isq.Accept(sum));
  }
}

Program: Program output.
Min: -18 Max: 18
Sum: 74

Visitors - Pros and Cons
Slide Annotated slide Contents Index
References Textbook 

There are both advantages and disadvantages of Visitor

Reference

  • Consequences of using a Visitor

    • A new kind of traversal can be added without affecting the classes of the Composite

    • A Visitor encapsulates all methods related to a particular traversal

    • State related to a traversal can - in a natural way - be represented in the Visitor

    • If a new class is added to the Composite all Visitor classes are affected

    • The indirect recursion that involves Accept and the Visit methods is more complex than the direct recursion in the natural object-oriented solution

Visitor is frequently used for processing of abstract syntax trees in compiler construction tools


Collected references
Contents Index
Point classes and representation independence Earlier in these notes
The using control structure Later in these notes
Cloning in C# Later in these notes
The interface IComparable in System MSDN2 API Documentation
The generic interface IComparable Later in these notes
The iterator design pattern Later in these notes
The type Interval and overloaded operators Earlier in these notes
The interface IEnumerable in System.Collections MSDN2 API Documentation
The interface IEnumerator in System.Collections MSDN2 API Documentation
Design patterns Earlier in these notes
The generated MIDI file
The auxiliary classes TimedNote and Song
MIP Exam January 2008 (In Danish)
Copying and comparing objects via references Earlier in these notes
Method MemberwiseClone MSDN2 API Documentation
Members of class Object Earlier in these notes
Integer Sequence Composite Earlier in these notes

 

Chapter 8: Abstract classes, Interfaces, and Patterns
Course home     Author home     About producing this web     Previous lecture (top)     Next lecture (top)     Previous lecture (bund)     Next lecture (bund)     
Generated: February 7, 2011, 12:17:38