Monday, 12 October 2015

Interface programming

By separating our implementation and declaration of what a class should do verses what it does we decouple the logic that our class implements from the contract our interface defines. We spoke about the fact that an interface is basically a schema for a class, it defines the public members that a class must implement. Let's say that we have the following interface.


interface IDataStorage<in Id, T>
{
void Create(T Entity);
T? Read(Id Id);
void Update(T Entity);
void Delete(Id Id);
void ListAll();
}


We're jumping the gun a bit with generics, but for now just trust me that the Id and T represent defined types, think of them as a place holders for either a string or an int or any other defined type. The in keyword for now just think of it as input, so we cannot return a type ID, just receive it as input. 

This Interface simply defines a function and four methods for reading and writing to some sort of data storage, Now let's create an entity class that we can pass to our implementation of IDataStorage. Let's stick to our Person concept.


class Person
{
static int runningID = 0;
public int Id { get; private set; } = runningID++;
public string FirstName { get; set; }
public string LastName { get; set; }
public Person() {
this.FirstName = "";
this.LastName = "";
}
public Person(string FirstName, string LastName) : this()
{
this.FirstName = FirstName;
this.LastName = LastName;
}
public string WriteValue() { return $"{FirstName} {LastName}"; }
}


With our person class ready, we can now create a data storage class that implements our interface


class ListStorage : IDataStorage<int, Person>
{
List<Person> People = new List<Person>();

public void Create(Person Entity)
{
if (!People.Contains(Entity))
People.Add(Entity);
}

public Person? Read(int Id) => People?.FirstOrDefault(p => p.Id == Id) ;

public void Update(Person Entity) => People[People.IndexOf(Entity)] = Entity;

public void Delete(int Id) {
var personToRemove = this.Read(Id);
if(personToRemove is not null)
People.Remove(personToRemove);
}

public void ListAll()
{
foreach (var p in People)
Console.WriteLine(p.WriteValue());
}
}


The idea is that our ListStorage class implements our interface; in a real word application you could use this as a mock data storage item for testing.

Now let's create another class, ArrayStorage that also implements our interface.


class ArrayStorage : IDataStorage<int, Person>
{
Person[] People = new Person[0];
int index = 0;
public void Create(Person Entity)
{
if (People == null)
People = new Person[10];
else if (People.Length == index)
Array.Resize(ref People, People.Length + 10);

for (var i = 0; i < index; i++)
if (People[i].Id == Entity.Id)
return;

People[index++] = Entity;
}

public void Delete(int Id) { throw new NotImplementedException(); }

public void ListAll()
{
for (var i = 0; i < index; i++)
Console.WriteLine(People[i].WriteValue());
}

public Person? Read(int Id)
{
for (var i = 0; i < index; i++)
if (People[i].Id == Id)
return People[i];
return null;
}

public void Update(Person Entity) { throw new NotImplementedException(); }
}


I didn't go to the trouble of implementing all of the members or even bulletproofing the ones I did. The idea is to demonstrate that there's two classes implementing the same interface in two different ways. The important take away is that we have abstracted the implementing of our logic, meaning that for any code that consumes our interface, the logic itself is insignificant, we can use the two repositories interchangeably.


class Program
{
static void Main(string[] args)
{
IDataStorage<int, Person> ListStorage = new ListStorage();
IDataStorage<int, Person> ArrayStorage = new ArrayStorage();

var John = new Person("John", "Smith");
var Mike = new Person("Mike", "Johnson");
var Frank = new Person("Frank", "White");

ListStorage.Create(John);
ListStorage.Create(Mike);

ArrayStorage.Create(John);
ArrayStorage.Create(Mike);
ArrayStorage.Create(Frank);

ListStorage.ListAll();
ArrayStorage.ListAll();
}
}


You can see that the ListStorage and ArrayStorage classes are basically interchangeable, by decoupling the definition of a class from it's implementation we increase the maintainability of our application. For example let's say that instead of a ListStorage class we had a OracleStroage class and our organisation decides to switch to Azure, well in such a case we could create an AzureStorage class that implements our interface, limiting the amount of changes that we would have to make to our application.

and for good measure, the full code code you can copy and paste into a console application.


namespace pav.interfaceBased;
interface IDataStorage<in Id, T>
{
void Create(T Entity);
T? Read(Id Id);
void Update(T Entity);
void Delete(Id Id);
void ListAll();
}

class ListStorage : IDataStorage<int, Person>
{
List<Person> People = new List<Person>();

public void Create(Person Entity)
{
if (!People.Contains(Entity))
People.Add(Entity);
}

public Person? Read(int Id) => People?.FirstOrDefault(p => p.Id == Id);

public void Update(Person Entity) => People[People.IndexOf(Entity)] = Entity;

public void Delete(int Id)
{
var personToRemove = this.Read(Id);
if (personToRemove is not null)
People.Remove(personToRemove);
}

public void ListAll()
{
foreach (var p in People)
Console.WriteLine(p.WriteValue());
}
}

class ArrayStorage : IDataStorage<int, Person>
{
Person[] People = new Person[0];
int index = 0;
public void Create(Person Entity)
{
if (People == null)
People = new Person[10];
else if (People.Length == index)
Array.Resize(ref People, People.Length + 10);

for (var i = 0; i < index; i++)
if (People[i].Id == Entity.Id)
return;

People[index++] = Entity;
}

public void Delete(int Id) { throw new NotImplementedException(); }

public void ListAll()
{
for (var i = 0; i < index; i++)
Console.WriteLine(People[i].WriteValue());
}

public Person? Read(int Id)
{
for (var i = 0; i < index; i++)
if (People[i].Id == Id)
return People[i];
return null;
}

public void Update(Person Entity) { throw new NotImplementedException(); }
}

class Person
{
static int runningID = 0;
public int Id { get; private set; } = runningID++;
public string FirstName { get; set; }
public string LastName { get; set; }

public Person()
{
this.FirstName = "";
this.LastName = "";
}
public Person(string FirstName, string LastName) : this()
{
this.FirstName = FirstName;
this.LastName = LastName;
}

public string WriteValue() { return $"{FirstName} {LastName}"; }
}


class Program
{
static void Main(string[] args)
{
IDataStorage<int, Person> ListStorage = new ListStorage();
IDataStorage<int, Person> ArrayStorage = new ArrayStorage();

var John = new Person("John", "Smith");
var Mike = new Person("Mike", "Johnson");
var Frank = new Person("Frank", "White");

ListStorage.Create(John);
ListStorage.Create(Mike);

ArrayStorage.Create(John);
ArrayStorage.Create(Mike);
ArrayStorage.Create(Frank);

ListStorage.ListAll();
ArrayStorage.ListAll();
}
}