Yes, there are other advantages to using polymorphism in your code. Or, in your words, reference objects by your superclass.
Two, for example:
Replace if
s 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:
- Code will need maintenance;
- You may have to maintain your own code;
- Someone else may have to maintain the code you wrote; and
- 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 if
s 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 if
s 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>();
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!
– dellasavia
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 ofImpressora
. However, there would be reason for someone to deliberately write a codeImpressora oImpressora = new ImpressoraXPTO()
?– dellasavia
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).
– Luiz Vieira
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.
– Luiz Vieira
Excellent explanation, @Luizvieira, +1 =)
– Gabriel Câmara
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
– dellasavia