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[] {
new City("Eastbourne", 99412, "Eastbourne Railway Station"),
new City("Hastings", 90300),
new City("Newhaven", 12250)
};
england.Counties[1].HumanSettlements = new HumanSettlement[] {
new Village("Ulcombe", "Peter Titchener"),
new City("Ashford", 74204),
new City("Dover", 28156, "Dover Priory"),
};
england.Counties[2].HumanSettlements = new HumanSettlement[] {
new City("Chichester", 23731),
new City("Littlehampton", 27795),
new City("Shoreham-by-Sea", 19175),
new Village("West Chiltington", "Harvie Steele")
};
london.Boroughs = new Borough[] {
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[] {
new District("Addington"),
new District("Forestdale"),
new District("Kenley"),
};
london.Boroughs[1].Districts = new District[] {
new District("Biggin Hill"),
new District("Chislehurst"),
new District("Crystal Palace"),
};
london.Boroughs[2].Districts = new District[] {
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 {
public string Name { get; set; }
public County[] Counties { get; set; }
public ITraversable[] Children { get { return Counties; } }
public Country(string Name)
{
this.Name = Name;
}
public override string ToString()
{
return Name.ToUpper();
}
}
public class County : ITraversable {
public string Name { get; set; }
public HumanSettlement[] HumanSettlements { get; set; }
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 {
public string Name { get; set; }
public ITraversable[] Children { get { return null; } }
public HumanSettlement(string Name)
{
this.Name = Name;
}
public override string ToString()
{
return Name.ToUpper();
}
}
public class City : HumanSettlement, IByTrainReachable {
public string Station { get; set; }
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 {
public Borough[] Boroughs { get; set; }
public override ITraversable[] Children { get { return Boroughs; } }
public Metropole(string Name)
: base(Name)
{
}
}
public class District : ITraversable {
public string Name { get; set; }
public ITraversable[] Children { get { return null; } }
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); if (Node.Children != null)
foreach (var child in Node.Children)
Traversal(child, Action); }
public static void List(ITraversable Node)
{
Console.WriteLine(Node.ToString());
}
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:
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
{
public static void PrintStation(ITraversable Node)
{
if (Node is IByTrainReachable && (Node as IByTrainReachable).Station != "")
Console.WriteLine(Path() + ": " + (Node as IByTrainReachable).PrintStation());
}
}
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.