Skip to content

Instantly share code, notes, and snippets.

@jv-amorim
Created April 26, 2020 20:45
Show Gist options
  • Save jv-amorim/d2478cc47691d80bb685f0fbc844e7d0 to your computer and use it in GitHub Desktop.
Save jv-amorim/d2478cc47691d80bb685f0fbc844e7d0 to your computer and use it in GitHub Desktop.
Notes about Interfaces in C#.

📌️ C# Interfaces Notes

Content:

  • Interfaces;
  • Repository Pattern;
  • Repository Factory Pattern;
  • Dynamic Factory (instead of Compile-Time Factory);
  • Benefits of using interfaces in Unit Testing.

📜️ Interfaces:

Interfaces describe a group of related functions that can belong to any class or struct. The interface is an contract. When a class implements an interface, it fullfils that contract by saying "I have these functions" (these functions can be properties, methods, events and indexers). In C# we declare a interface with the keyword "interface". It is a convention to start the interface names with an "I". Example of a interface in C#:

public interface IPersonRepository
{
    IEnumerable<Person> GetPeople();

    Person GetPerson(int id);

    void AddPerson(Person newPerson);

    void UpdatePerson(int id, Person updatedPerson);

    void DeletePerson(int id);
}

The interfaces only contains the signature of the methods and attributes. The implementation is made in the classes that uses this interface.

Interfaces are very useful:

  • To use repository and factory patterns (see below);
  • To use dynamic factory, instead of compile-time factory (see below);
  • In the Unit Testing (see below);
  • In Dependency Injection, which enables the development of loosely coupled code;
  • To use caching decorator pattern (see below);
  • To create mocking repositories (similar to fake repositories) for using in tests.

✅️ Explicit Interface Implementation:

If a class implements two interfaces that contain a member with the same signature, then implementing that member on the class will cause both interfaces to use that member as their implementation. It's possible to implement an interface member explicitly—creating a class member that is only called through the interface, and is specific to that interface. Name the class member with the name of the interface and a period.

public class SampleClass : IControl, ISurface
{
    void IControl.Paint()
    {
        System.Console.WriteLine("IControl.Paint");
    }
    void ISurface.Paint()
    {
        System.Console.WriteLine("ISurface.Paint");
    }
}

✅️ Interface Segregation Principle:

One of the SOLID principles, more information can be found below.

✅️ Danger of Interfaces ⚠️:

We have seen the advice to program to an abstraction rather than a concrete type and our translation programmed to an interface rather than a concrete class. This sounds like good advice, but we should not follow it blindly. Each of our tools have both pros and cons. We have seen a lot of benefits to using interfaces, and it is tempting to start using interfaces everywhere. I have run into problems with an application that had too much abstraction. It was difficult to find things in the code. I was confused about what classes were causing a particular behavior. There was code that was easy to break because a change in one place would have a ripple effect throughout the application. The best outcome is to find the right balance, just enough abstraction to get the benefits, but not too much to cause confusion.

✅️ When use interfaces and when use abstract class?

If we need code shared between classes, it's best to use a abstract class.

✅️ Interface Inheritance:

Interfaces also can inherit from another interfaces (to see the official documentation for more information).


📦️ Repository Pattern:

"The repository pattern mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects." - Martin Fowler.

The repository pattern lets us separate our application code from specific data storage technology. We do not want our application code to need to know how to make HTTP calls or how to crack open a file on the file system. Instead, the repository is responsible for those details. The repository then exposes the data as standard C# objects that our application can use. A service repository will know how to make HTTP calls to a web service that returns JSON. A CSV repository will know how to open a file on the file system and parse the comma-separated values. A SQL repository will know how to connect to a SQLite database using Entity Framework. In addition to these repositories, we must create a repository interface. This will be our contract that has the methods to interact with our data, and it's what the application will use to communicate with our repositories. There are several approaches to repositories. We can start out with a CRUD repository where CRUD stands for create, read, update, and delete.


🏭️ Repository Factory Pattern:

A factory method is responsible for getting us an instance of an object. In our case, the factory will return an object that implements our repository interface. So let's look at the code. The GetRepository method returns an IPersonRespository. The UI Code can call this method instead of taking responsibility for creating objects itself. This factory method takes a string as a parameter. The string will indicate what type of repository we want. Based on the value of the parameter, we new up a ServiceRepository, a CSVRepository, or a SQLRepository.

public static IPersonRepository GetRepository(string repositoryType)
{
	IPersonRepository repository = null;

	switch (repositoryType)
	{
		case "Service": repository = new ServiceRepository();
			break;
		case "CSV": repository = new CSVRepository();
			break;
		case "SQL": repository = new SQLRepository();
			break;
		default:
			throw new ArgumentException("Invalid repository type");
	}

	return repository;
}

💫️ Dynamic Factory (instead of Compile-Time Factory):

It's very common to hear advice to program to an abstraction rather than a concrete type. When using interfaces, this means to program to an interface rather than a concrete class. We've done in the "Repository Factory Pattern" topic already. In our code above, we use a factory method to get an object that implements our interface, IPersonRepository. Our factory method has the responsibility to give us an instance of a repository, and it has compile-time references to each of our repositories. We can remove these compile-time references, but do we really need to do this? There are a few drawbacks to the way our compile-time factory works. Our factory method has a parameter, a string specifying the repository type. In itself, this is not too bad, but the result is that the calling code is selecting which repository type to use. Depending on the situation, this may be okay. But for our example, we would be better off if that responsibility was somewhere else. The biggest drawback is that we have compile-time references to all of our repositories. This means that if we need to add a new repository, we need to recompile our entire application. This may not be a problem for an internal application. But if we ship our application out to multiple clients, then each client could potentially have a different build, and that is a support nightmare. We will replace our compile-time factory with a dynamic factory. This factory has no parameter. Instead, it uses configuration information to pick which repository to use. The calling code no longer has that responsibility. More importantly, our dynamic factory has no compile-time references to the repositories. Instead, it loads up the appropriate assemblies at runtime. Since we have no compile-time references, we do not need to rebuild the application to add a new repository. This means that each of our clients can have the same build of the core application, and that is much easier to support. Our dynamic factory method uses a little bit of reflection to load up the appropriate assembly at runtime.

public static IPersonRepository GetRepository()
{
	string repositoryTypeName = ConfigurationManager.AppSettings["RepositoryType"];
	Type repositoryType = Type.GetType(repositoryTypeName);
	object repository = Activator.CreateInstance(repositoryType);
	IPersonRepository personRepository = repository as IPersonRepository;
	return personRepository;
}

In the first line of the method, we get the name of the repository type from an XML settings file in the folder of our builded application. It is in this file where we define the repository that our application will use.


✔️ Benefits of using interfaces in Unit Testing:

Unit testing is a big part of development process. It have a large number of benefits, which results in a more fast development. There are different kinds of tests. In the unit tests we test pieces of functionality in isolation. The isolation is a big part of that definition. When we are testing a small piece of functionality, we want to fake up anything that is not directly relevant to the test. The good news is that interfaces can help us isolate our code to make it easier to test. An interface is a plug-in point to our code. We can unplug our real functionality and plug in a test object.


⏳️ Caching Decorator Pattern:

(Nothing to read, more study is needed.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment