In object orientation, why are interfaces useful?

Asked

Viewed 6,727 times

50

Someone can give a practical explanation of why interfaces are used and why they are useful to us developers?

7 answers

48


Interfaces are useful because they establish contracts. If a class implements an interface you will be able to reference class instances by the interface having only access to the members defined in the interface. That basically means that you guarantor that the class will display a certain behavior without knowing a priori how this behavior is implemented. For example, think about the interface IComparable of C#, it determines which classes that implement it have a comparison method. This idea can be used for example to build a binary tree class as shown in the book Microsoft Visual C# 2010 step by step, because signing this interface ensures that there is some way to compare objects of that class.

That’s why we say interfaces establish contracts: a class implement an interface is like signing a contract, it promises to have that functionality. This allows you to obey some of the SOLID principles and write codes that are poorly coupled and with high cohesion.

The first principle, Single Responsibility Principle, says that each class must have a single responsibility: to do something and to do it well. But several times to do something a class has dependencies, for example manage data or send emails. To respect this principle, these dependencies cannot be encoded in the class, they must be encoded in other classes and then used in the initial class.

The problem with this is that if you instantiate the dependencies within the class you are working on you will engage those classes. If you need to change the service of emails or data, you will have to change there too and this may not be interesting and entail in much more work than necessary, which was just changing a service.

The last principle of SOLID that talks about this, it is the Dependency Inversion Principle and it says in summary: "Rely on abstractions and not on concrete implementations". This basically means that if the dependencies of one of its components are concrete instances will become more difficult for you to maintain the software, you will have to fiddle with a code that is very attached.

A good example is the default repositories for data access. You create an interface, in the case below in C#

public interface IRepositorio<T>
{
    void Adicionar(T entidade);
    void Atualizar(T entidade);
    void Excluir(T entidade);
    IQueryable<T> ListarTudo();
    T ProcurarPorID(int id);
} 

When you need data access for a certain entity T you use a concrete implementation of this interface, but referenced by it. It means that you have a service that promising which allows to perform all these operations, but does not even say how.

From there on another Assembly you can implement as you like, Entity Framework, Nhibernate, etc., and at the end of the day, to change the implementation you just have to change in one place. Of course, for you to use in practice, tell which implementation to use for which interface there are techniques like Dependency Injection. The main thing to understand is that the interfaces serve to promise functionalities.

In summary: Interfaces are contracts that help you write code with low coupling and high cohesion.

  • 2

    Another main reason is the possibility to 'simulate' multiple inheritance, since everything that was said here fits for abstract classes.

  • I found the explanation very simple, objective and at the same time complete. I personally see classes as circuits you can splice into each other and solder there, or else you solder soquets and connect with plugs (which in case are interfaces) which allows you to unplug one class and plug another without having to solder everything again.

24

Interfaces should, as the name says, provide interfaces for manipulating objects. If a group of different objects has the same kind of action required, you implement an interface in all these objects.

Let me give you an example in C#. Consider these three classes:

class Cachorro
{
    public string Latir() { return "Au au"; }
}

class Gato
{
    public string Miar() { return "Miau"; }
}

class Vaca
{
    public string Mugir() { return "Muu"; }
}

I know, very childish, but it will be useful to illustrate the problem. Now we go to the middle of our super system that has a module that has a list of animals and needs to know what noise it makes. How do we do it without interfaces?

List<object> animais = GetAnimais(); // Animais podem ser cachorros, gatos ou vacas, nunca se sabe
foreach (var animal in animais)
{
    var cachorro = animal as Cachorro;
    if (cachorro != null) cachorro.Latir();

    var gato = animal as Gato;
    if (gato != null) gato.Miar();

    var vaca = animal as Vaca;
    if (vaca != null) vaca.Mugir();
}

The code is working. But if you work somewhere like Ibama, you have to do the conversion and the null-check for every kind of animal needed, and there could be thousands. Having thousands of lines of code is very un-legal when you can avoid.

So let’s rewrite our animals, and put an interface on them!

interface IAnimal
{
    string Falar();
}

class Cachorro : IAnimal
{
    public string Latir() { return "Au au"; }
    public string IAnimal.Falar() { return Latir(); }
}

class Gato : IAnimal
{
    public string Miar() { return "Miau"; }
    public string IAnimal.Falar() { return Miar(); }
}

class Vaca : IAnimal
{
    public string Mugir() { return "Muu"; }
    public string IAnimal.Falar() { return Mugir(); }
}

Now, that use of before turned into this:

List<IAnimal> animais = GetAnimais();
foreach (var animal in animais) animal.Falar();

Which will have the same effect. So you can also create 800 new animals, and you’ll only have to implement it as a Ianimal; you won’t have to move where you use your method Falar.

Another positive point is that changing object for IAnimal, you have a little more safe typing.

5

Read a little bit about project patterns that you will understand very well. But there goes a basic example.

public interface RepositorioCliente {
  public void inserir(Cliente cliente);
}

public class RepositorioClienteMysql implements RepositorioCliente {

  public void inserir(Cliente cliente) {
  //faz lógica de salvar
  }

}

When you use the repository class, you will not call it directly. You will instantiate the interface.

RepositorioCliente repositorio = new RepositorioClienteMysql();

If you need to change databases, all methods in common, the new class responsible for saving to the database will have to implement. And you’ll just need to change the instance.

RepositorioCliente repositorio = new RepositorioClienteNovoBanco();

And of course, classes should implement the repository interface.

4

When you want two different types to have the same behavior.

This becomes visible when we detect patterns of behavior between different types, but according to these patterns could be from the same logical family. An example is when you have in your code two distinct types like Cat and Dog, but detecting similarities such as the fact that the two move, emit sounds and are domestic animals, but note that each one does so in a specific way..

Then we draw a guy Animal where we set the standards across all subtypes of the class (Gato, Cachorro, Galinha) as Andar(), Falar().

And we implement all subtypes according to their type of expression of these patterns:

Animal: Andar() {} Falar(){}

Gato is a Animal soon

Andar() { 'anda rápido'} 
Falar() { 'Miau' }

Cachorro is a Animal, soon

Andar() {'anda desajeitado' } 
Falar() { 'Au au' }

Note that when defining Animal we do not know how animals to be implemented walk or speak. We will only know for each specific subtype to implement it.

This type composition practice is very useful in OOP and whenever a pattern is detected among several types there is a gain in reuse, simplification and elegance of the code

In your code you can always refer to the group as Animal, or the supertype, and thus reuse the methods that would otherwise have to be made one for each animal. reducing the reuse of your code:

  • AdicionarAnimal(Animal a)
    adds all animals (Cat, Chicken, Dog.. and futures)

  • RemoverAnimal(Animal a)
    removes all animals

Imagine you have to create such a method for each animal?

AdicionarGalinha... AdicionarCachorro.. .

So this composition of types helps and greatly in the reuse of logic, and in decreasing the complexity of the code.

3

I understand that the question has been answered before, but a very simple case is you imagine a method to send an alert when a task should be executed.

We create an interface: (simple for demo)

public interface IAlerta
{
    void Envia();
}

And a class that uses:

public class Tarefa
{
    public string Nome { get; set; }

    public IAlerta Alerta { get; set; }
}

We can have several versions for Ialerta, in this example an alert can be sent by e-mail or by sms, it is precisely in this scenario that the interfaces are necessary. The Task class does not need to know the implementation needed to send an sms or e-mail, it will only use the Alert method.Send(); which will be triggered by polymorphism.

We will simulate the implementation of the two versions of Ialerta

public class AlertaEmail : IAlerta
{
    public string Email { get; set; }
    public void Envia(string mensagem)
    {
        // aqui implementamos o envio por e-mail
    }
}

public class AlertaSMS : IAlerta
{
    public string Telefone { get; set; }
    public void Envia(string mensagem)
    {
        // aqui implementamos o envio por sms
    }
}

Let’s see an example of the use of the 2 alert implementations in task

AlertaEmail alertaEmail = new AlertaEmail() { Email = "[email protected]" };
AlertaSMS alertaSMS = new AlertaSMS() { Telefone = "99 9999 9999" };

Tarefa tarefa = new Tarefa();

tarefa.Alerta = alertaEmail;
tarefa.Alerta.Envia("Ops");

tarefa.Alerta = alertaSMS;
tarefa.Alerta.Envia("Ops");

Our task system works normally without having to "worry" about how the alert will be sent, or how it is implemented internally, just waiting for an instance that implements the Ialerta interface.

Assuming we wanted a new alert version that sends a tweet, we wouldn’t need to change our task system at all. We would only expect an implementation of Ialert that would do that, as you see it, could be implemented by anyone, including third parties. We would only need to send the interface and ask for the implemented code.

public class AlertaTwitter : IAlerta
{
    public string Twitter { get; set; }
    public void Envia(string mensagem)
    {
        // aqui implementamos um post no twitter
    }
}

And in our task, we would receive an instance of an object from the Alerttwitter class.

AlertaTwitter alertaTwitter = new AlertaTwitter() { Twitter = "@jaspion" };

tarefa.Alerta = alertaTwitter;

There are other scenarios very similar to this that can use, as a log system, you would have an Ilog Interface with a Gravalog method for example and that could be implemented in several ways, such as writing a text file, writing to a database, send an email, among others.

2

We should use the interface language construction, when we need to define the behavior of a family of objects, which have no common implementation.

For example, we need to define a family of eternal objects. In this case, we can have object iterators from the database, we can have data iterators from a CSV. We can have iterators directories, we can have iterators of N different things, whose implementation is not shareable. In this case, when we have no common implementation, we use the interface language construction.

By demonstrating this in PHP, we will pretend that we need a list of users, no matter where this data will come from. The code could be as follows:

interface DataUsers {
    public function getUsers();
}

class RenderUsers {
    private $datausers;

    public function __construct(DataUsers $datausers){
        $this->datausers = $datausers;
    }

    public function listUsers(){
        return $this->datausers->getUsers();
    }
}

class MySQLUsers implements DataUsers {
    public function getUsers(){
        // Aqui vai a implementação para o MySQL
    }
}

class XMLUsers implements DataUsers {
    public function getUsers(){
        // Aqui vai a implementação para o XML
    }
}

class WebServiceUsers implements DataUsers {
    public function getUsers(){
        // Aqui vai a implementação para um WebService qualquer
    }
}

Since we have an interface, the implementation may vary because we know that the return will always be the same, regardless of the input.

The use would look like this:

// Se você quer os dados vindos de um banco MySQL
$mysqlUsers = new MySQLUsers();
$usuarios = new RenderUsers( $mysqlUsers );
echo $usuarios->listUsers();

// Se você quer os dados vindos de um XML
$xmlUsers = new XMLUsers();
$usuarios = new RenderUsers( $xmlUsers );
echo $usuarios->listUsers();

// Se você quer os dados vindos de um WebService qualquer
$webServiceUsers = new WebServiceUsers();
$usuarios = new RenderUsers( $webServiceUsers );
echo $usuarios->listUsers();

The important thing in Object Orientation is to always program to the interface, regardless of where the data will come from, which is irrelevant.

1

I would say that interfaces are useful because it allows objects to depend on a definition of a class and not on a implementation of a particular class. This way we can "inject/attribuir" in objects any implementation that follows the definition of this class.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.