Click here to Skip to main content
Click here to Skip to main content

C# Classes vs. Interfaces with Tree Traversal Example

, 5 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Example for the usage of classes and interfaces when working with hierarchical trees

Introduction

I was looking for a clear, practical example for the usage of classes and interfaces, but I did not find a convincing one. So I present my own example here based on the divisions and areas of England - seen as a structure of classes on the one hand and as several additional structures (e.g. a hierarchical tree) on the other hand.

I hope this helps to explain the different possibilities of classes and interfaces and the beauty of using both of them with their specific advantages.

Background

Here you find some theoretical background concerning classes and interfaces (thanks to Rahman Mahmoodi).

In my example I want to define classes for "country", "metropole", "county", "city", "village", "borough" or "district" in the object oriented point of view, because I want to model something close to real life: see Subdivisions of England (Wikipedia).

Based on these classes I want to "assemble" England with its countries, cities, boroughs, villages and so on like this:

var england = new Country("England");
var london = new Metropole("Greater London");

england.Counties = new County[]
{
    new County("East Sussex"),
    new County("Kent"),
    new County("West Sussex"),
    london
};

england.Counties[0].HumanSettlements = new HumanSettlement[] // East Sussex
{
    new City("Eastbourne", 99412, "Eastbourne Railway Station"),
    new City("Hastings", 90300),
    new City("Newhaven", 12250)
};
england.Counties[1].HumanSettlements = new HumanSettlement[] // Kent
{
    new Village("Ulcombe", "Peter Titchener"),
    new City("Ashford", 74204),
    new City("Dover", 28156, "Dover Priory"),
};
england.Counties[2].HumanSettlements = new HumanSettlement[] // West Sussex
{
    new City("Chichester", 23731),
    new City("Littlehampton", 27795),
    new City("Shoreham-by-Sea", 19175),
    new Village("West Chiltington", "Harvie Steele")
};

london.Boroughs = new Borough[] // Greater London
{ 
    new Borough("Croydon", "Fairfield Halls", "East Croydon station"),
    new Borough("Bromley", "London Fire Brigade", "Bromley South railway station"),
    new Borough("City of Westminster", "Palace of Westminster"),
    new Borough("Kingston upon Thames", "Coronation Stone"),
};
london.Boroughs[0].Districts = new District[] // Croydon
{
    new District("Addington"),
    new District("Forestdale"),
    new District("Kenley"),
};
london.Boroughs[1].Districts = new District[] // Bromley
{
    new District("Biggin Hill"),
    new District("Chislehurst"),
    new District("Crystal Palace"),
};
london.Boroughs[2].Districts = new District[] // City of Westminster
{
    new District("Bayswater"),
    new District("Paddington"),
    new District("Pimlico"),
    new District("Victoria")
};

But there is also e.g. a hierarchical point of view. The country of England consists of counties like "East Sussex" and "Kent", and we find cities and villages in them ("Eastbourne", "Dover", etc.). In England there is also the metropole "Greater London". This consists of boroughs like "Croydon", and the boroughs in their turn consist of districts like "Addington", "Bayswater" and "Pimlico":

So we do not only want to work with all our classes and objects via their special properties and methods. We also want to walk through the hierarchical structure of England, e. g. in a list like this one:

In addition to that we also want to focus on a railway point of view - let us say in cities and in boroughs - and maybe on an airport point of view - let us say in large cities and metropoles.

How can we do this?

The desired result of an object oriented view in combination with any additional view can be achieved by the usage of (abstract) classes on the one hand and interfaces on the other hand:

Object Oriented Point of View

According to the object oriented point of view we define our classes like "Country", "County", "HumanSettlement", "City", "Village", "Metropole", "Borough", and "District":

Here we can see that the country of England consists of counties. In the counties we find human settlements, i.e. cities or villages. A special kind of a county may be a metropole which consists of boroughs - which themselves consist of districts.

 

Footnote: The "HumanSettlement" is so-called "abstract" here, because it must be a city or a village. On the contrary "County" is not abstract, because it can be a county itself or a metropole (a special form of a county, UML "specialization").

Besides, the country is composed from counties (UML "composition": all counties in total are the country), but the county has an aggregation of human settlements, but also of rural areas and forests and so on (UML "aggregation").

 

Hierarchical Point of View

In our hierarchical point of view we look at all our objects as tree nodes. This enables us to walk through them one after the other using a process called "tree traversal". So we define an interface called "ITraversable". Thus we make all our objects (seen as nodes) "traversable".

Here is the UML diagram from above again, but now we do not show their structural associations, now we depict how we add the interface "ITraversable" to all of them:

One interesting aspect here is, that "Metropole", "City" and "Village" inherit the interface from their base classes. So all our classes/objects are "traversable" now.

Any Additional Point of View

I know, we could have done that also by a class "Node" as a base class for all the tree nodes. And you can do it, if you want. But now we add additional points of view and from now on it makes sense to use interfaces:

Here you can see, that large cities and metropoles are reachable by plane, and cities and boroughs are reachable by train. By introducing the interface "IByPlaneReachable" e.g. we can collect all large cities and all metropoles in a list "List<IByPlaneReachable> LocationsWithAirports". From that moment on we can work with specific classes/objects from the additional "airport point of view". The same is true for an implementation of the "railway point of view" and any other point of view.

So, if you want to do things like that you need either multiple inheritance (which is not implemented in C#) or you work with interfaces. In both cases pls. be aware of the so-called "diamond problem".

The Basic Part: the Classes

Let us start with a typical class here, the "City":

public class City : HumanSettlement
{
    public int Population { get; set; }
    public string Station { get; set; }

    public City(string Name, int Population, string Station = "")
        : base(Name)
    {
        this.Population = Population;
        this.Station = Station;
    }
    public override string ToString()
    {
        return String.Format("{0} ({1}) {2}", Name, Population.ToString("#,##0"), Station.ToUpper());
    }
}

The "City" has a "Population" and a "Station", and it has a "Name" in its base class ("HumanSettlement"). In addition to that we override "ToString()".

The "Village" is similar to the city, but here we only take care of the "Chairman" (in order to show inheritance and specialization):

public class Village : HumanSettlement
{
    public string Chairman { get; set; }

    public Village(string Name, string Chairman)
        : base(Name)
    {
        this.Chairman = Chairman;
    }
    public override string ToString()
    {
        return String.Format("{0} ({1})", Name, Chairman);
    }
}

All the other classes are extremely similar, pls. look into them in the source code directly. In the end all these classes enable us to assemble England as shown at the beginning.

So far it was straight forward. But now we add the the additional points of view to our solution:

The Magic Part 1: the Interface

The interfaces for our hierarchical tree view and for our railway view look like this:

public interface ITraversable
{
    string Name { get; }
    ITraversable[] Children { get; }
}
public interface IByTrainReachable
{
    string  Station { get; set; }

    string PrintStation();
}

Somebody once described interfaces by the example of driving cars: there may be a big difference between a Jaguar, a Porsche and a Dacia. But we drive all of them by using their steering weels, gear sticks and pedals. So, whatever the differences between the cars in detail may be, we operate them using their standard interfaces.

Now our interface "ITraversable" simply defines, that any object that shall be transversable (i.e. be a node in our hierarchical tree), must have "Children" and a "Name".

The "Children" may be empty or null, but then we automatically know, that the specific object is a leaf of our tree.

Our second interface "IByTrainReachable" defines, that any object that shall be reachable by train must have a "Station" and a method for printing it: "PrintStation()".

So we can use the interfaces like so:

public class Country : ITraversable // Now with tree interface
{
    public string Name { get; set; }
    public County[] Counties { get; set; }
    // Now with tree children (=Counties)
    public ITraversable[] Children { get { return Counties; } }
    public Country(string Name)
    {
        this.Name = Name;
    }
    public override string ToString()
    {
        return Name.ToUpper();
    }
}
public class County : ITraversable // Now with tree interface
{
    public string Name { get; set; }
    public HumanSettlement[] HumanSettlements { get; set; }
    // Now with tree children (=HumanSettlements)
    public virtual ITraversable[] Children { get { return HumanSettlements; } }

    public County(string Name)
    {
        this.Name = Name;
    }
    public override string ToString()
    {
        return Name;
    }
}
public abstract class HumanSettlement : ITraversable // Now with tree interface
{
    public string Name { get; set; }
    public ITraversable[] Children { get { return null; } } // Never has children

    public HumanSettlement(string Name)
    {
        this.Name = Name;
    }
    public override string ToString()
    {
        return Name.ToUpper();
    }
}
public class City : HumanSettlement, IByTrainReachable // Now with railway interface
{
    // and so on
    public string Station { get; set; }
    // and so on

    public string PrintStation()
    {
        return String.Format("{0} {1}", Name, Station.ToUpper());
    }
}

The "Country", the "County" and the "HumanSettlement" implement the "ITraversable" interface now. That means, that they must implement "ITraversable Children[]" and "Name". That's it.

The "City" inherits the interface "ITraversable" from its base class "HumanSettlement", but it implements the interface "IByTrainReachable" in addition.

We do the same for the "Metropole", "District" (and so on):

public class Metropole : County, IByPlaneReachable // Now with airport interface
{
    public Borough[] Boroughs { get; set; }
    // Now with children (=Boroughs)
    public override ITraversable[] Children { get { return Boroughs; } }

    public Metropole(string Name)
        : base(Name)
    {
    }
}
public class District : ITraversable // Now with tree interface
{
    public string Name { get; set; }
    public ITraversable[] Children { get { return null; } } // Never has children

    public District(string Name)
    {
        this.Name = Name;
    }
    public override string ToString()
    {
        return Name;
    }
}

Now all our classes/objects are "traversable", and some of them are also "reachable by train", "reachable by plane" and so on.

The Magic Part 2: Using the Interface

Of course we need a small procedure ("Traversal()") now in order to walk from one node to the next through our tree:

public class Tree
{
    public delegate void DoSomething(ITraversable Node);

    public static void Traversal(ITraversable Node, DoSomething Action)
    {
        Action(Node); // Perform action
        if (Node.Children != null)
            foreach (var child in Node.Children)
                Traversal(child, Action); // Recursion for each child
    }
    // Possible action for "DoSomething":
    public static void List(ITraversable Node)
    {
        Console.WriteLine(Node.ToString());
    }
    // Possible action for "DoSomething"
    public static void PrintStation(ITraversable Node)
    {
        if (Node is IByTrainReachable && (Node as IByTrainReachable).Station != "")
            Console.WriteLine(Path() + ": " + (Node as IByTrainReachable).PrintStation());
    }
}

Here we start with any traversable object (i.e. paramter "Node" for any object that implements the interface "ITraversable"). Then we "do something" with this object (seen as a node). Here we simply provide "List()" and "PrintStation()" as possible actions.

Afterwards we check whether this node has child nodes or not. If yes, we call the method itself recursivly with the child node.

The static method "Tree.Traversal()" is then called like so:

// Print the tree
Tree.Traversal(england, Tree.List);

In fact this single line starts the total walk through our tree beginning with the given node "england" and performing "Tree.List()" for each node in the tree - in the correct sequence.

So we get the total list of the tree that we wanted to create from the beginning:

ENGLAND
East Sussex
Eastbourne (99.412) EASTBOURNE RAILWAY STATION
Hastings (90.300) 
Newhaven (12.250) 
Kent
Ulcombe (Peter Titchener)
Ashford (74.204) 
Dover (28.156) DOVER PRIORY
West Sussex
Chichester (23.731) 
Littlehampton (27.795) 
Shoreham-by-Sea (19.175) 
West Chiltington (Harvie Steele)
Greater London
Croydon (Fairfield Halls) EAST CROYDON STATION
Addington
Forestdale
Kenley
Bromley (London Fire Brigade) BROMLEY SOUTH RAILWAY STATION
Biggin Hill
Chislehurst
Crystal Palace
City of Westminster (Palace of Westminster) 
Bayswater
Paddington
Pimlico
Victoria
Kingston upon Thames (Coronation Stone)

But we can also look at the railway stations:

public class Tree
{
    // ...

    // Possible action for "DoSomething"
    public static void PrintStation(ITraversable Node)
    {
        if (Node is IByTrainReachable && (Node as IByTrainReachable).Station != "")
            Console.WriteLine(Path() + ": " + (Node as IByTrainReachable).PrintStation());
    }
}
// Print all railway stations in the tree
Tree.Traversal(england, Tree.PrintStation);
England/East Sussex/Eastbourne: Eastbourne EASTBOURNE RAILWAY STATION
England/Kent/Dover: Dover DOVER PRIORY
England/Greater London/Croydon: Borough Croydon EAST CROYDON STATION
England/Greater London/Bromley: Borough Bromley BROMLEY SOUTH RAILWAY STATION

Isn't that wonderful?

Conclusion

This is my practical example for classes vs. interfaces. Does it clarify the possibilities and the epic aesthetics of the concept? Any comments are appreciated!

History

2nd of August, 2014 - Published.

3rd of August, 2014 - Multiple inheritance aspect added.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paul C. Rhodes
Architect
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralA little better, but... PinprofessionalPaulo Zemek5-Aug-14 10:56 
GeneralRe: A little better, but... PinmemberPaul C. Rhodes5-Aug-14 11:15 
GeneralRe: A little better, but... PinprofessionalPaulo Zemek5-Aug-14 11:50 
GeneralRe: A little better, but... PinmemberPaul C. Rhodes5-Aug-14 22:22 
GeneralRe: A little better, but... PinprofessionalPaulo Zemek6-Aug-14 3:49 
GeneralRe: A little better, but... PinprofessionalPaulo Zemek4-Sep-14 12:22 
GeneralRe: A little better, but... PinmemberPaul C. Rhodes4-Sep-14 12:50 
General!!! PinmemberAndrey Koryakin5-Aug-14 8:46 
Generalexcelent article Pinmembermarclinux4-Aug-14 11:08 
GeneralRe: excelent article PinmemberPaul C. Rhodes4-Aug-14 11:14 
GeneralMy vote of 5 PinprofessionalVolynsky Alex3-Aug-14 10:08 
QuestionAnswering your final question, no, it doesn't clarify the concept. PinprofessionalPaulo Zemek2-Aug-14 16:21 
AnswerRe: Answering your final question, no, it doesn't clarify the concept. [modified] PinmemberPaul C. Rhodes2-Aug-14 22:11 
GeneralRe: Answering your final question, no, it doesn't clarify the concept. PinprofessionalPaulo Zemek3-Aug-14 6:50 
GeneralRe: Answering your final question, no, it doesn't clarify the concept. PinmemberPaul C. Rhodes3-Aug-14 7:52 
GeneralRe: Answering your final question, no, it doesn't clarify the concept. PinprofessionalAgent__0073-Aug-14 18:28 
AnswerRe: Now the update is online PinmemberPaul C. Rhodes5-Aug-14 7:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141220.1 | Last Updated 5 Aug 2014
Article Copyright 2014 by Paul C. Rhodes
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid