Thursday, July 05, 2007 8:28 PM

Outro dia um participante de um dos grupos de discussão que participo mandou o seguinte problema, mais ou menos assim: Tenho uma aplicação MDI, e quero controlar a abertura de um formulário de forma que só exista uma instância dele aberta, se o usuário tentar abrí-lo novamente, não quero criar outra instância, mas sim ativar a já existente.

Basicamente o que ele estava fazendo era:

private void OpenSingletonForm() 
{
    Singleton form = new Singleton();

    form.MdiParent = this;
    form.Show();
}

Em resposta a um clique no botão, menu ou outro controle de UI. Então cada vez que o usuário clicava no botão, uma nova instância se abria.

A primeira solução dada foi a seguinte: Quando o usuário tentar abrir o formulário, teste já existe um formulário aberto com o mesmo nome, se houver não abra uma nova instância.

A implementação é moleza, fica assim:

 

private void OpenSingletonForm() 
{
    Singleton form = new Singleton();

    if (!IsFormOpen(form.Text))
    {
        form.MdiParent = this;
        form.Show();
    }

}

private bool IsFormOpen(string formName) 
{
    foreach (Form form in this.MdiChildren) 
    {
        if (form.Text == formName) 
        {
            form.Activate();
            return true;
        }
     }

    return false;
}

Ok, a solução funciona, mas não é a ideal, poderia citar uma série de problemas que ela tem, mas só o fato dela confiar em uma propriedade, Form.Text, que pode ser alterada em tempo de execução, já me traz desconforto bastante para tentar outra coisa.

Então eu respondi sugerindo que ele utilizasse um o design pattern de Singleton, que se encaixava perfeitamente no seu problema. Não mandei a solução escrita, não costumo fazer isto, mas enviei para ele os seguintes links:

Se você ler apenas um deles, são todos parecidos, vai chegar facilmente a uma solução simples. O cenário não pretende suportar multi-thread, portanto a versão mais simples de Singleton atende, o resultado ficaria assim:

    public partial class Singleton : Form 
    {
        //Construtor deve ser privado, ninguém pode instanciar o form direto
        private Singleton() 
        {
            InitializeComponent();
        }

        private static Singleton instance;

        public static void OpenForm(Form parent) 
        {
            if (instance == null) 
            {
                instance = new Singleton();
                instance.MdiParent = parent;
                instance.Show();
            } 
            else 
            {
                instance.Activate();
            }
        }

        protected override void OnClosing(CancelEventArgs e) 
        {
            instance = null;
            base.OnClosing(e);
        }
    }

A maior parte da implementação está no próprio formulário e não no seu pai, o que é bom, pois se você tiver que abri-lo de outro lugar o seu comportamento será o mesmo. O que fiz foi o seguinte:

  1. Tornei o construtor privado, para ninguém conseguir criar uma nova instância do formulário.
  2. Criei uma propriedade estática, privada, que guardará minha única instância corrente do formulário
  3. Criei um método que controla a existência desta instância única, não permitindo mais de uma instância do formulário por vez. Esta é a única forma de abrir o formulário.
  4. O único hack que fiz foi sobrescrever o OnClosing para destruir o ponteiro da instância antes do formulário fechar, senão minha propriedade terá um ponteiro para um formulário fechado e não conseguirá reabrí-lo. (Não sei se este é o melhor lugar para fazer isto, não pensei muito nisto)

Por último tenho que mudar a chamada do formulário, lá no formulário pai. O resultado é mais simples do que antes, apenas uma linha:

    Singleton.OpenForm(this);

Não preciso discorrer aqui sobre as vantagens da segunda solução, utilizando o Singleton, em relação à arquitetura, é perda de tempo. Em quantidade de linhas de código as opções são quase iguais, a segunda é um pouco menor, 13 contra 15. Em termos de tempo gasto pode se dizer que para ler um dos artigos e escrever o código talvez a segunda solução tomasse uns 30 minutos a mais que a primeira.

A resposta do desenvolvedor foi (nas minhas palavras, não literal): Muito obrigado, mas é um aplicativo simples, para um amigo meu, não vou usar design pattern, quero economizar trabalho e acho que a solução do fulano (solução 1) resolve o meu problema.

Escrevi isto tudo para chegar à seguinte moral da história:

  • Não fique assustado só porque alguém falou as palavras design pattern, nem sempre isto que dizer algo muito complicado
  • Confie que alguém já passou pelo seu problema e o design pattern é resultado destas experiências anteriores, o que economiza tempo e dor de cabeça. Por exemplo, aposto que ninguém tinha pensado na possibilidade existir problemas com multi-thread, eu não pensei até ver o Singleton pela primeira vez.
  • Seja curioso, pelo menos gaste 5 minutos lendo o artigo para ter uma idéia do que se trata. Somente assim você evoluirá na profissão e no seu conhecimento.

 

OBS.: Para as pessoas que participaram desta thread do grupo de discussão: Não tomem este post  como críticas pessoais, por favor. A história foi apenas uma inspiração e pano de fundo para que eu conseguisse comunicar o meu ponto.

Comments

At 7/10/2007 7:46 AM, Israel Aece said:

# re: To pattern or not to pattern

Boas Eduardo,

Se a pessoa não se preocupa em utilizar o Singleton para não permitir múltiplas instâncias de um mesmo objetos (no caso, Form), imagine que ele irá se preocupar com multi-threading...
At 7/16/2007 11:57 PM, Nélio Mesquita said:

# re: To pattern or not to pattern

Boa Eduardo! Gostei muito!
Post Comment
Title *
Name *
Email (never displayed)
Website
Comment * (Allowed tags: blockquote, a, strong, em, p, u, strike, super, sub, code)  
Please add 2 and 2 and type the answer here: