Padrão Memento + Padrão Prototype

Estou precisando muito da ajuda do GUJ!!!

Eu quero poder desfazer as minhas ações em um programa. Para isso, uso o padrão Memento. O programa é simples. Toda vez que clico em calcular, uma cópia do objeto atual é clonado pelo padrão prototype e salvo em uma lista. Quando eu clico em recuperar uma vez, a lista é percorrida uma vez e o último clone salvo nela é removido. O problema é que, pelos meus testes, todas as cópias que jogo na lista apontam para o mesmo objeto e não deveria ser assim, já que clono antes de jogar na lista.

Peço, por favor, que me ajudem. O programa parece grande, mas só preciso saber por que o mesmo objeto está sendo salvo na lista ao invés de clones dele. Se quiserem testar, é só copiar e colar que funciona sem problemas! Vou postar o código e explicando…

Essa é a classe Originador. Essa classe pede para fazer uma cópia de um objeto. Na linha 63, CallMementoFromOriginador() invoca nas linhas 93 e 94 a classe PrototypeFactory, que é um padrão Prototype. Nessa classe, uma cópia do objeto Originador é salvo. Há ainda nessa classe um método getClone() que retorna a cópia. Essa classe é mostrada em seguida.

package gof.comportamental.memento;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class Originador extends JFrame implements Cloneable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private JLabel valor1, valor2, resultado;
	private JButton calcular, desfazer;
	private JTextField campo1, campo2, camporesultado;
	private FlowLayout flowlayout;
	
	public Originador(){
		
		valor1 = new JLabel("Valor 1");
		valor2 = new JLabel("Valor 2");
		resultado = new JLabel("Resultado");
		campo1 = new JTextField(3);
		campo2 = new JTextField(3);
		camporesultado = new JTextField(3);
		calcular = new JButton("Calcular");
		desfazer = new JButton("Desfazer");

		
		flowlayout = new FlowLayout();
		
		this.setLayout(flowlayout);
		
		this.add(valor1);
		this.add(campo1);
		this.add(valor2);
		this.add(campo2);
		this.add(resultado);
		this.add(camporesultado);
		this.add(calcular);
		this.add(desfazer);	
		
	
		calcular.addActionListener
		( 
			new ActionListener()
			{
				public void actionPerformed( ActionEvent e )
				{
					if( e.getSource() == calcular )
					{
						getCamporesultado().setText(String.valueOf(
								Integer.parseInt(getCampo1().getText())+
								Integer.parseInt(getCampo2().getText())));
						//Chama a classe Memento para salvar um estado do objeto.
						try{
							
							CallMementoFromOriginador();
						}
						
						catch(CloneNotSupportedException ex){
							
							ex.printStackTrace();
						}
						
					}
				}
			}
		);
		
		desfazer.addActionListener
		( 
			new ActionListener()
			{
				public void actionPerformed( ActionEvent e )
				{
					if( e.getSource() == desfazer )
					{
						Memento.getInstance().getState();						
					}
				}
			}
		);	
	}
	
	public void CallMementoFromOriginador() throws CloneNotSupportedException{
		
		PrototypeFactory prototypefactory = new PrototypeFactory(this);
		Memento.getInstance().setState(prototypefactory.getClone());
	}
	
	public JTextField getCampo1() {
		return campo1;
	}

	public JTextField getCampo2() {
		return campo2;
	}


	public JTextField getCamporesultado() {
		return camporesultado;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}

	public static void main(String []args){
		
		Originador originador = new Originador();
		originador.setSize(230, 130);
		originador.setVisible(true);
		originador.setLocationRelativeTo(null);
		originador.setResizable(false);
		originador.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

}

Classe PrototypeFactory, que cria cópias do objeto Originador.

package gof.comportamental.memento;

public class PrototypeFactory {
	
	private Originador originador;
	
	//Cria um clone.
	public PrototypeFactory(Originador originador){
		
		this.originador = originador;
	}
	
	//Retorna um clone.
	public Originador getClone() throws CloneNotSupportedException{
		
		return (Originador)originador.clone();
	}
}

Essa é a classe Memento, que guarda em uma lista os objetos clonados de Originador e os recupera. Essa classe usa o padrão Singleton, mas não tem problemas, pois quero apena uma lista e um objeto Memento salvando e recuperando.

package gof.comportamental.memento;

import java.util.ArrayList;
import java.util.List;

public class Memento {
	
	private static final Memento instancia = new Memento();
	private static List<Originador> lista = new ArrayList<Originador>();
	
	private Memento(){}
	
	public static Memento getInstance(){
		
		return instancia;
	}
	
	//recupera o valor do objeto quando pedido
	public void getState(){
		
		if(!this.getLista().isEmpty()){

			System.out.println(getLista().remove(getLista().size()-1).getCamporesultado().getText());
		}
	}
	
	//Salva o estado do objeto para posterior recuperação.
	public void setState(Originador originador){
		
		lista.add(originador);
	}
	
	public List<Originador> getLista(){
		
		return lista;
	}

}

O programa simula um ctrl+z. Entrem com valores nos campos e precionem Calcular. Nessa hora, uma cópia do objeto é salvo automaticamente. Entrem novamente com valores e cliquem em Calcular. A mesma coisa acontece. Ao clicar em Desfazer, deveria aparecer o resultado da primeira conta no console mas, ao invés, aparece o da segunda conta. Aí que ví que o mesmo objeto está sendo guardado mais de uma vez na lista.

Agradeço desde já!

Oi ECO2004,

O problema esta ocorrendo porque os atributos campo1 e campo2, que são objetos do tipo JTextField, não são clonaveis. A classe Originador é clonavel, você tornou isto possível quando incluiu a interface Cloneable, porem os objetos agregados (os atributos) não são clonaveis.

Como constatar tal fato: Utilize o debugger e verifique os elementos da lista, os ids dos atributos campo1 e campo2 são sempre os mesmos para cada elemento (objetos da classe Originador). Perceba que os ids do elementos da lista são diferentes, resultantes do processo da clonagem.

É isso ai…bons estudos.

flws

[quote=fantomas]Oi ECO2004,

O problema esta ocorrendo porque os atributos campo1 e campo2, que são objetos do tipo JTextField, não são clonaveis. A classe Originador é clonavel, você tornou isto possível quando incluiu a interface Cloneable, porem os objetos agregados (os atributos) não são clonaveis.

Como constatar tal fato: Utilize o debugger e verifique os elementos da lista, os ids dos atributos campo1 e campo2 são sempre os mesmos para cada elemento (objetos da classe Originador). Perceba que os ids do elementos da lista são diferentes, resultantes do processo da clonagem.

É isso ai…bons estudos.

flws
[/quote]

Obrigado pela dica!

Eu resolvi o problema faz uns 15 minutos. Criei variáveis em Originador recebendo os valores dos JTextField. Depois, clonei o objeto Originador e setei as variáveis do clone com os valores retornados dos métodos get dessas variáveis.

Bem, você disse que um JTextField não é clonável. Deixe eu ver se entendi…objetos agregados, ou seja, aqueles que são referenciados em uma classe ou até mesmo as classes que são herdadas, não são clonáveis?

É isso mesmo, quando for utilizar clonagem tem que ficar atento aos atributos do objeto que esta sendo clonado, inclusive aqueles atributos que foram herdados; os objetos tem que ser originados de classes que possuam a interface Cloneable.

Uma boa pratica é clonar objetos que sejam o mais simples possivel, que possuam a estrutura mínima necessária para atingir seu objetivo; isso melhora a performance e o uso de memória.

Mesmo os mais experientes podem cair nesta cilada, não é toda hora que rola uma clonagem por ai.

Outra dica é tentar criar nomes de métodos que iniciam com letras minusculas, CallMementoFromOriginador() por exemplo, deveria ser callMementoFromOriginador();. Isto faz parte do padrão para geração de código Java, da uma vasculhada na net que vc irá encontar alguns documentos falando sobre o assunto.

Que bom que conseguiu resolver. :smiley:

flws

[quote=fantomas]É isso mesmo, quando for utilizar clonagem tem que ficar atento aos atributos do objeto que esta sendo clonado, inclusive aqueles atributos que foram herdados; os objetos tem que ser originados de classes que possuam a interface Cloneable.

Uma boa pratica é clonar objetos que sejam o mais simples possivel, que possuam a estrutura mínima necessária para atingir seu objetivo; isso melhora a performance e o uso de memória.

Mesmo os mais experientes podem cair nesta cilada, não é toda hora que rola uma clonagem por ai.

Outra dica é tentar criar nomes de métodos que iniciam com letras minusculas, CallMementoFromOriginador() por exemplo, deveria ser callMementoFromOriginador();. Isto faz parte do padrão para geração de código Java, da uma vasculhada na net que vc irá encontar alguns documentos falando sobre o assunto.

Que bom que conseguiu resolver. :smiley:

flws

[/quote]

Obrigado! Eu não sabia dessa restrição de clonagem!