Italo Info


Padrão Singleton

Olá. Desta vez escrevo sobre um padrão de projeto para programação Orientada a Objetos, também muito conhecido: O Singleton. Esse padrão, quando aplicado ao projeto de software OO, visa que se utilize o objeto de uma determinada classe em qualquer parte do sistema, com a garantia que esse a classe desse objeto só é instanciada uma vez.

A idéia é, primeiramente, criar um construtor privado na classe para impedir a instanciação direta da mesma através do operador "new", chamado fora do escopo da classe e, depois, criar um método estático que retorna a única instância criada da classe. Veja o diagrama abaixo:

Diagrama do singleton
Diagrama do singleton - Básico

Perceba que o método "getInstance" é estático, assim como, também, o atributo privado "instance". O atributo "instance" representa a única instância da classe. Agora, veja abaixo qual a lógica do singleton na classe Singleton:


class Singleton {

  private static Singleton instance = null;

  private Singleton() {}

  public static Singleton getInstance() {
    if ( instance == null )
      instance = new Singleton();
    return instance;
  }

  public void algumMetodo() {

  }

}

public class Main {
  public static void main(String[] args) {
    Singleton.getInstance().algumMetodo();
  }
}

Esta é a implementação mais básica do singleton, onde, o método "getInstance" instancia a classe Singleton, apenas, se a mesma ainda não foi instanciada nenhuma vez e retorna a instância. Caso contrário, apenas retorna a única instância.

O método algumMetodo é apenas um método qualquer da classe singleton que pode ser chamado em qualquer parte do sistema através da chamada: Singleton.getInstance().algumMetodo();.


Singleton para player de audio

Agora, vamos a um exemplo mais interessante: Um player de áudio. Esse player de áudio é um programa de linha de comandos que recebe um argumento: o caminho de um arquivo de música em formato ".wav". Caso, nenhum argumento seja passado, o programa assume que no diretório atual da linha de comandos tem um arquivo de nome: "picapau.wav" e toca ele. Veja o código fonte da classe principal:


package singleton;

import javax.swing.JOptionPane;
import singleton.audio.AudioPlayer;

public class Main {

    public static void main(String[] args) {
        String audio = "picapau.wav";
        if ( args.length == 1 )
            audio = args[ 0 ];        
        
        try {
            AudioPlayer.getInstance().play( audio ); 
        } catch (RuntimeException ex) {
            JOptionPane.showMessageDialog( null, ex.getMessage() );
        }
    }

}

No programa acima, se o número de argumentos for 1, então, é passado para o player de áudio, a string correspondente ao valor do argumento. Caso, contrário, é passado para o player de áudio a string "picapau.wav".

Agora, repare na seguinte chamada: AudioPlayer.getInstance().play( audio );. Claramente, podemos perceber aqui que a classe AudioPlayer é uma classe Singleton. E, o método "play", toca o áudio.

Veja abaixo o código fonte da classe AudioPlayer:


package singleton.audio;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class AudioPlayer {
    
    private static AudioPlayer instance = null;
    
    private AudioPlayer() {}
    
    public static AudioPlayer getInstance() {
        if ( instance == null )
            instance = new AudioPlayer();
        return instance;
    }
        
    public void play( String arquivoAudio ) throws Exception {                            
        try {
            InputStream in = new FileInputStream( arquivoAudio );
            InputStream bufIn = new BufferedInputStream( in );
                        
            AudioInputStream ain = AudioSystem.getAudioInputStream( bufIn );
            Clip clip = AudioSystem.getClip();           
            clip.open( ain );            
            clip.start();  
            
            Thread.sleep( clip.getMicrosecondLength() / 1000 ); 
            
            clip.close();            
        } catch ( UnsupportedAudioFileException ex ) {            
            throw new Exception( "Formato de arquivo de áudio não suportado." );
        } catch ( LineUnavailableException ex ) {     
            throw new Exception( "Erro na abertura do arquivo de áudio." );        
        } catch ( FileNotFoundException ex ) {
            throw new Exception( "\""+arquivoAudio+"\"\nArquivo não encontrado." );
        } catch ( IOException ex ) {            
            throw new Exception( "Erro na leitura do arquivo de áudio." );
        }
    }
            
}

Observe primeiramente o método "getInstance", a variável privada e estática "instance" e o construtor privado como o único na classe. São características de uma classe Singleton!

Agora, você pode entender como a chamada ao método "play" no método principal funciona. Mas, entenda também que a classe AudioPlayer tem, de fato, o método de nome "play" que recebe o caminho do arquivo de áudio para tocar.


O método "play"

Pronto! Vamos agora focar no método "play" para entender como ele funciona. Perceba que há a possibilidade de uma exceção tipo Exception ser lançada com uma mensagem personalizada e capturada no método principal. O ideal era criar uma classe de exceção personalizada. Mas, por questões de simplicidade, utilizei a classe Exception mesmo. Se uma exception é lançada pelo método "play", é mostrada uma janelinha com a mensagem personalizada.


Singleton com multithreading

Como o singleton utiliza um atributo e um método estático, talvez não seja uma boa idéia utilizar essa solução em um ambiente concorrente.


O download do código fonte

Abaixo o link de download do código fonte do projeto que toca um áudio.

Link de download: singleton-exemplo.zip

Após baixar o projeto, você pode compilar se estiver com o JDK. Então, use a linha de comandos para entrar na pasta base do projeto descompactado e utilize o seguinte comando para compilar:


javac -d bin src/singleton/*.java src/singleton/audio/*.java

Agora para rodar, basta utilizar o seguinte comando:


java -cp bin singleton.Main oi-meu-chapa.wav

No comando acima, você pode passar qualquer arquivo de áudio em formato ".wav" no lugar de "oi-meu-chapa.wav". Se não passar o arquivo, o "picapau.wav" é executado por padrão, conforme o comando abaixo:


java -cp bin singleton.Main


Finalizando...

Esse é o final de mais um artigo sobre padrões de projeto. Desta vez, o padrão singleton foi discutido aqui. Espero que tenham gostado e, até o próximo!