Why create an object using the superclass?

Asked

Viewed 2,292 times

11

Given the following code:

public class Musico {

    public void tocaInstrumento() {
        // faz algo
    }
}

.

public class Baterista extends Musico {

    public void giraBaqueta() {
        // faz algo
    }
}

.

public class Violonista extends Musico {

    public void trocaCordas() {
    }
}

I can use the polymorphism to do the following:

    Musico musico1 = new Violonista();
    Musico musico2 = new Baterista();

    musico1 = musico2;

However, I cannot see the methods of the subclass:

    musico1.trocaCordas(); //ERRO DE COMPILAÇÃO!

If I use:

Violonista musico1 = new Violonista();

Wouldn’t it be better? What is the advantage of using Musico type to reference an object in a subclass? It would simply be the possibility that I could assign musico1 = musico2;, for example? Or there are other advantages?

8 answers

6


His example is not exactly to "create" an object using the superclass, but to create an object using a subclass and manipulate it through the superclass.

To explain in a simpler way, I will start from the following code:

Musico musico1 = new Violonista();
Musico musico2 = new Baterista();

What you did there was basically declare two variables of the kind Musico (the variables musico1 and musico2) and initialize them with content that are instances of daughter classes (inherited) from Musico (respectively instances of Violonista and Baterista). That is just the character of the polymorphism (etymology: poly [many] + morphs [form] = quality or state of what may take different forms)

Also as already answered appropriately, polymorphism is what allows you to add instances of different objects in the same list (ArrayList<Musico>, for example), since this feature of object orientation allows to manipulate objects in an "abstract" way (that is, without knowing specific details of the implementation or which instance is actually stored in the variable).

The build error stems from the fact that the compiler has no way of knowing which class you instanced, because the variable type is Músico. You can, however, try to make a type casting:

((Violonista) musico1).trocaCordas();

Basically you’re telling the compiler to handle variable content musico1 as an instance of the class Violonista. If that content really is an instance of this class, everything will work as expected, but if it is not you will have an error at runtime. To ensure that you are not doing anything foolish, you can check whether the instance is, in fact, inherited from that class:

if(musico1 instanceof Violonista)
    ((Violonista) musico1).trocaCordas();

I know I’m probably raining on the wet with all these explanations (because you’ve had really good answers), but I wanted to finally offer an example where there’s a real advantage to using this kind of manipulation through the parent/abstract class.

Consider, for example, that you are the creator/developer of the Windows Operating System. Of course you want other developers to create applications for your OS. Imagine that I am the developer of MS Word. For my part, I want my software to be able to print text on the client’s printer. But, the problem is that the customer can have different types of printers, and new types can be created after the release of my software. How to proceed?

Suppose you, the OS developer, have an interface for printers, where you basically created and published an abstract class (i.e., a type) called Impressora. Such a class has only one method:

public abstract class Impressora {
    public void imprimir(String sTexto);
}

The "Abstract" is there only to say that this class does not serve to be instantiated, only to be inherited. Anyway, now suppose the manufacturer of the XPTO printer, knowing the publicly available interface made by you, create your own print driver, in which he inherits his class Impressora, for example, as follows::

import IMPRESSORA.DO.SISTEMA.OPERACIONAL;

public class ImpressoraXPTO extends Impressora {
    public void imprimir(String sTexto) {
        . . .
        // Faz a impressão específica da impressora XPTO
        . . .
    }
}

Dai, I, the developer of MS Word, need to request the printing of the texts created in my software. Also, knowing that there is something publicly available created by you I simply reuse what the OS already proves, for example in a similar way to this:

import IMPRESSORA.DO.SISTEMA.OPERACIONAL;

public class MSWord {

    public void imprimirArquivo(String sTexto)
    {
        // Suponha que exista uma função do sistema operacional que
        // devolva a instância da impressora padrão 
        // (sem importar se é uma XPTO, uma HP, uma Epson, uma George Foreman, etc).
        Impressora oImpressora = SO.getImpressoraPadrao();

        // Como todas têm o método `imprimir` (por herdar de `Impressora`), eu posso 
        // chamá-lo sem me preocupar com os detalhes de como cada impressora
        // realmente trabalha!
        oImpressora.imprimir(sTexto);
    }
}

In this simple example you can get an idea of the advantage of working with abstraction and polymorphism. First, you can decouple the use of the implementation; that is, whoever uses your code can "work on abstraction", meaning that you can use the basic methods and properties that all inheritances will have. Secondly, it facilitates the extension as it is very easy and practical to add new specific classes (in the example, new printers - as long as they inherit from the standard interface Impressora - will be guaranteed to use my MS Word on your operating system without this software needing to be changed).

In the case of your example, your parent/abstract class is Musico and the classes of real interest are those that inherit from it: Violonista and Baterista. Inheritance makes a lot of sense when classes share something, be it attributes (variables) or behaviors (methods). In his example, the only thing that a guitarist and a drummer share is the behavior of "playing the instrument" (through the method tocaInstrumento inherited).

Ideally this method needs to be reimplemented in the child classes so that each type of musician plays appropriately (the guitarist switches [didn’t mean "plays"?] the strings, and the drummer spins the drumstick). When some other part of your code manipulates the "musicians" (that’s right, in an abstract way), it will "prompt" them to play the instrument (that is, they will invoke the playing method), but each type of musician (each object) will do it in a specific way, according to its own implementation. The fact is that whoever manipulates the "musicians" (someone cited a stage in examples, but could be the orchestra simulator) does not need to know exactly what each one does.

  • 2

    As mentioned in http://answall.com/a/5337/4156 I already knew the advantages of inheritance and polymorphism, the doubt was really specific as to why to use the superclass (in your example, an interface) rather than directly using the subclass. Your answer is almost an article! Congratulations!

  • However, another question arises. It makes sense to use, as in your example, Impressora oImpressora = SO.getImpressoraPadrao(), because you do not know which specific printer the method will return, can come any one, but I know that will always be subclass of Impressora. However, there would be reason for someone to deliberately write a code Impressora oImpressora = new ImpressoraXPTO() ?

  • Yes, there is. In the printer example, the operating system itself would do this when the user selected XPTO as the default printer. At the end of the day, someone has to instantiate the specific object. In fact, facilitating this process is one of the main motivators of project standards such as Abstract Factor (http://pt.wikipedia.org/wiki/Abstract_Factory) and Factory Method (http://pt.wikipedia.org/wiki/Factory_Method).

  • 1

    Now, if you only have a local use of one specific class (but inherit from another to "win" existing attributes and methods), it makes more sense even if you manipulate the instance in your own class. I mean, then abstraction and polymorphism don’t have as much relevance as inheritance itself.

  • 1

    Excellent explanation, @Luizvieira, +1 =)

  • I found this link that may be useful for a better understanding: http://stackoverflow.com/questions/9852831/polymorphism-why-use-list-list-new-arraylist-instead-of-arraylist-list-n

Show 1 more comment

5

I think I understand your question, you want a reason to instantiate

Musico musico1 = new Violonista();

instead of

Violonista musico1 = new Violonista();

Any method that accepts a Musico will accept your object Violonista, both in the parameters and in the return, and even you can yes add your Violonista to a list of Musico I think we see more of this construction for didactic reasons.

I believe you already knew the characteristics and advantages of inheritance and polymorphism, and that basically the only doubt was to use the Musicoor the Violonista when declaring its variable. I am correct?

If so, I see no reason to use the first option. Please correct me if I’m wrong.

(Now, create a field in a class like Musico instead of Violonista would make a lot of difference)

  • I think you’re right. The question was what case would need to be instantiated in this way and in fact there is no advantage. The cast can be done at any time and a method that receives Musico as parameter will accept any child.

3

The advantages of using abstraction are the same as the polymorphism, that in my view the main one is the creation of generic routines that do not need to know the specific types. This allows you to create new variations of data types without changing existing algorithms.

Maybe you’re struggling because you’re thinking about class details and not a routine that can abstract concepts.

Imagine that a CasaDeShow will receive a band composed by several musicians. This house just needs to ask them to play the instruments, she doesn’t want to know the details of each of them. Here the use of abstraction falls perfectly.

On the other hand, if we had an instrument repair workshop, there should be a specific section for each type of musician and instrument, with individual treatment for the particularities of each one. This rather reminds the overload of methods.

Finally, most of the concepts related to Object Orientation have some relation to reality, but sometimes we have to strive to think in an abstract way.

  • I understood the question of being able to create generic routines that don’t need to know the specific types. But I could perfectly pass an argument of type Drummer for a method that only receives type Musician, because Drummer is a Musician.

  • @dellasavia That’s right. The point is not that you at all times You must work with abstract types, because I don’t see the point in that either. But the more you can work with the abstractions, the less code you’ll get repeated.

3

Calm down, let’s go in pieces.

Why gives build error?

First of all, you can’t access trocaCordas, because musico1 happens to be a drummer when you put musico1 = musico2.

What are the advantages of inheritance?

Second, there are many advantages to using a superclass. The first is that all the child classes will inherit the methods public and protected of the father. In the case of example you used, both Baterista, how much Violinista have the method tocaInstrumento. This means that when you want to edit the method tocaInstrumento, you NAY need to edit it several times, but only once, so that the Violinista and the Baterista will receive the changes. This and other properties can be seen in this inheritance class.

What is the advantage of using the parent class to define objects?

This question starts more than when you need something generic, let’s imagine that you want to save a band. We know that the band is not only composed by drummers or violinists, but by musicians, so we would have that my band is a list of Musico.

ArrayList<Musico> banda = new ArrayList<Musico>();

and within that list I’ll have every kind of musician:

banda.add(new Violinista());
banda.add(new Baterista());

the advantage of this is that when I need to perform an action of Musico i can iterate only on one for:

for(int i = 0; i < banda.size(); i++){
  banda.get(i).tocaInstrumento();
}

or

for(Musico musico: banda){
  musico.tocaInstrumento();
}

This was only an introduction of the advantages of a basic feature of Object Orientation, more information can be found here.

  • I understood, but see: If I create the object Violista musico1 = new Violonista(); I will also be able to insert musico1 in a list that only accepts Musico type objectives, because musico1 is a Guitarist and Guitarist is a Musician. I still don’t see any advantage of using the supertype to create the reference.

  • Yes, but if you create one ArrayList<Violinista>, in their band list, will only have violinists, while a ArrayList<Musico> allows whichever type of musician. This is used when you don’t know which child will be referenced. An example is the ArrayList, another example could be, for example, if a user is choosing what kind of musician he is, you will not know, hand in hand, what he will choose, so you will not be able to reference a specific type of musician. It became clearer what I mean?

2

Yes, there are other advantages to using polymorphism in your code. Or, in your words, reference objects by your superclass.

Two, for example:

Replace ifs by polymorphism

Let’s assume that, in your example of musicians, you want to create a web page to display the details of each of them. If the musician is a drummer, you want to display an image (avatar) of a drum set. If you are a guitarist, a guitar; violinist, violinist, and so on.

So, in a first version, you write:

public void exibirAvatar(PaginaWeb pagina, Musico musico) {
  String imagem;
  if (musico instanceof Baterista) {
    imagem = "/img/bateria.jpg";
  } else if (musico instanceof Guitarrista) {
    imagem = "/img/guitarra.jpg";
  } else if (musico instanceof Violinista) {
    imagem = "/img/violino.jpg";
  } else {
    imagem = "/img/imagem-padrao.jpg";
  }
  pagina.exibirAvatar(imagem);
} 

I think we’ve all written some kind of code that looks like this.

The problem is that sometimes we forget that code is not static, that is, we forget that:

  1. Code will need maintenance;
  2. You may have to maintain your own code;
  3. Someone else may have to maintain the code you wrote; and
  4. You may have to maintain a code written by someone else.

In a more practical way: and if further on, it is necessary to add bassists, vocalists, saxophonists, etc? In this case, it would be better to have thought about another modeling for the classes, but this is not the case now.

Then, in a second version, you change your initial code. But, in haste, and using CTRL-C, CTRL-V (use every day, so be clear):

public void exibirAvatar(PaginaWeb pagina, Musico musico) {
  String imagem;
  if (musico instanceof Baterista) {
    imagem = "/img/bateria.jpg";
  } else if (musico instanceof Guitarrista) {
    imagem = "/img/guitarra.jpg";
  } else if (musico instanceof Violinista) {
    imagem = "/img/violino.jpg";
  } else if (musico instanceof Baixista) {
    imagem = "/img/guitarra.jpg";
  } else if (musico instanceof Saxofonista) {
    imagem = "/img/saxofone.jpg";
  } else {
    imagem = "/img/imagem-padrao.jpg";
  }
  pagina.exibirAvatar(imagem);
} 

Bassist already has the wrong image... An alternative solution, using polymorphism, is to 'move' the ifs for each of the sub-classes:

public void exibirAvatar(PaginaWeb pagina, Musico musico) {
  String imagem = musico.getImagem();
  pagina.exibirAvatar(imagem);
} 

public class Baterista extends Musico {
  public String getImagem() {
    return "/img/bateria.jpg";
  }
}

public class Guitarrista extends Musico {
  public String getImagem() {
    return "/img/guitarra.jpg";
  }
}

// etc

Thus replacing a series of ifs chained by polymorphism.

Using false/alternative implementations in testing

This is an example I often give to those who work with me. Suppose you are developing a system to send SMS messages when an event occurs. For example, when a server on your network crashes, you want to notify all sysadmins.

To facilitate the example, let’s assume that your phone operator provides a library in which, to send SMS messages, just know the phone number and the content of the message.

In a first version, you write:

public interface Notificador {
  void notificar(PossuiCelular pessoa, String mensagem);
}

public class NotificadorProducao implements Notificador {

  private final OperadoraTelefonia operadora; // biblioteca da sua operadora

  public NotificadorSysadmin(OperadoraTelefonia operadora) {
    this.operadora = operadora;
  }

  public void notificar(PossuiCelular pessoa, String mensagem) {
    String celular = pessoar.getCelular();
    operadora.enviarSms(celular, mensagem);
  }

}

and there, where periodically the status of servers is effectively verified:

public class VerificadorStatusServidor {

  private final Sysadmins sysadmins;
  private final Servidores servidores;
  private final Notificador notificador;

  public NotificadorSysadmin(Sysadmins sysadmins,
                             Servidores servidores, 
                             Notificador notificador) {
    this.sysadmins = sysadmins;
    this.servidores = servidores;
    this.notificador = notificador;
  }

  public void check() {
    for (Servidor servidor : servidores.getTodos()) {
      if (servidor.naoResponde()) {
        notificarSysadmins(servidor);
      }
    }
  }

  private void notificarSysadmins(Servidor servidor) {
    for (Sysadmin admin : sysadmins.getTodos()) {
      notificador.notificar(admin, "Servidor " + servidor.getNome() + " não responde");
    }
  }

}

As good practice, you wrote a test for this (and used TDD inclusive).

public void sysadmin_deve_ser_notificado() {
  Sysadmins admins = new Sysadmins();
  admins.add(new Sysadmin("A", "555-0000"));
  admins.add(new Sysadmin("B", "555-1111"));
  admins.add(new Sysadmin("C", "555-2222"));

  Servidores servidores = new Servidores();
  servidores.add(new Servidor("1", "online"));
  servidores.add(new Servidor("2", "offline"));
  servidores.add(new Servidor("3", "offline"));

  Notificador notificador = new NotificadorSysadmin(new OperadoraTelefonia());

  VerificadorStatusServidor verificador = new VerificadorStatusServidor(admins, servidores, notificador);

  verificador.check();

  // assertivas
}

All tests pass and everyone is happy.

The only question is, a month later, your project manager asks:

"Our telephone bill had an increase of 1200%!!!!!!!!!!! Who called several times for the sysadmins?"

And the truth is that no one called, just the integration server that ran the tests with each commit. And each time the above test ran, it sent one Real text, with real cost telephony for the sysadmins.

The solution, in this case, is to use false implementations. Replace the Notificador real by one for test environment. That is, in the test code above, replace

Notificador notificador = new NotificadorSysadmin(new OperadoraTelefonia());

for

Notificador notificador = new NotificadorFalso();

Whereas NotificadorFalso is:

public class NotificadorFalso implements Notificador {

  private List<Notificacao> notificacoes = new ArrayList<Notificacao>();

  public void notificar(PossuiCelular pessoa, String mensagem) {
    Notificacao n = new Notificacao(pessoa, mensagem);
    notificacoes.add(n);
  }

}

Thus, accessing the list of notificacoes of your Notifirfalso, you are still able to check whether your VerificadorStatusServidor works properly, without having the costs real text messaging.

It is even possible to simulate cases where SMS communication fails, for example:

public class NotificadorComErroSMS implements Notificador {
  public void notificar(PossuiCelular pessoa, String mensagem) {
    throw new ExcecaoDeComunicacaoSms();
  }
}

Finally, note that it is the kind of thing we do several times a day and sometimes we do not notice:

 Set<String> nomesDistintos = new HashSet<String>();
 // oops, quero percorrer com nomes ordenados
 Set<String> nomesDistintos = new TreeSet<String>();
 // oops, quero percorrer na ordem que foram inseridos
 Set<String> nomesDistintos = new LinkedHashSet<String>();
  • I am adept at explanations followed by real examples. I think it is an excellent didactic approach and greatly facilitates understanding. Thank you!

2

There are other advantages. The difference is implied and, if you look at it well, it’s almost obvious: A Musico can be a Baterista or a Violonista, but a Baterista can’t be a Violonista and vice versa.

Why would that be useful? When you don’t need one Baterista or Violinista in particular, you need any Musico.

Let’s assume this with an example: a Band. How could we express this? Well, it can be like a class:

class Banda {
  private Vocalista vocalista;
  private Guitarrista guitarrista;
  private Baterista baterista;
  private Violonista violonista;

  // encapsulamento, construtores e afins...
}

Okay, that works. But we have a problem: What if the band has two guitarists? Or if she doesn’t have a guitarist?

We can fix it this way:

class Banda {
  private Vector<Musico> musicos;

  // encapsulamento, construtores e afins

  public void tocarMusica() {
    for (Musico musico : musicos) musico.tocaInstrumento();
  }
}

So it doesn’t matter if a band has more or less musicians of each type, because it accepts any number of musicians, and everyone can play their instrument.

  • even if I create the object Guitarist musico1 = new Guitarist(); I will be able to perfectly insert it in this vector. So what’s the advantage of using the supertype to create the reference?

  • Look at the for. You are creating a list of Musicos, and not of Violonistas. That’s the advantage: Being both one and the other.

  • I understood, but my doubt is in the form of creation of the object and not in the access to it. It does not matter if I create Musico musico1 = new Violonista() or Violonista musico1 = new Violonista(). In both ways, I’ll be able to rule musico1 for a for who only accepts Musico.

  • Yes. The only difference is in access. I don’t see an advantage in creation.

1

In Software Design we must always prevent abstractions to the detriment of concrete classes to decrease Coupling and dependency among the various collaborators of the system. That is, we should seek to promote Weak Coupling between the objects that collaborate to perform a service or functionality.

Strong coupling occurs when a dependent class contains a direct reference to a concrete class, which provides the desired behavior. Dependency cannot be replaced without the need for a change in the dependent class.

Weak coupling occurs when the dependent class contains reference to an interface, which can be implemented by one or several concrete classes.

Interface determines a specific "contract" with a defined list of methods and / or attributes that concrete classes should implement.

Any class that implements the interface can satisfy the dependency and a change in its implementation will not generate propagation of change in other concrete classes, that is, this approach promotes extensibility in software design at a very low cost.

The more we reference concrete classes in our code, the more We have increased the coupling and we have lost the ability to maintain at low cost. For this reason High Coupling decreases the quality of the software and should be avoided.

See illustrative image of low coupling.

inserir a descrição da imagem aqui

1

The advantage in their case of the use of the type Musico is the possibility to use methods that deal with the characteristics that Violinista and Baterista have in common. On the other hand, non-use would imply code duplicity to treat particularly Violinist and Drummer. You can’t use the methods of Violinista in a variable of type Musico because at this point, the musician type variable can be both Baterista, Flautista, etc..., so you can’t call, for example, Musico.trocaCordas() and this could be the Baterista. Let us therefore say that if Musico have the method AbrirPartitura, this method can be called directly, both in Violisita.AbrirPartirura(), Baterista.AbrirPartitura(), Flautista.AbrirPartitura() and Musico.AbrirPartitura (and musician can be any class that inherits from Musico).

Browser other questions tagged

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