JSTL Checar se método existe

Amigos… no meu JSP… faço um foreach com JSTL em uma coleção…

Essa coleção é do tipo Calculable:

List<Calculable> calculables = new ArrayList<>(); req.setAttribute("Expenses", calculables);

Calculable é uma interface que algumas classes implementam… ou seja… dentro dessa lista…posso ter objetos de classes diferentes…

No JSP faço:

			<c:forEach var="Expense" items="${Expenses }">
				<tr>
					<td>${Expense.title }</td>
					<td>${Expense.value }</td>
					<td>0,00</td>
					<td>${Expense.value }</td>
					<td></td>
					<td></td>
										
					<c:if test="${not empty Expense.values }">
						<td>${Expense.values }</td>
					</c:if>
					

				</tr>
			</c:forEach>

Eu só quero mostrar o conteúdo do método getValues para os objetos que realmente possuem esse método… mas essa verificação não funciona…quando o foreach esta em um objeto que não tem esse método…recebo uma exception…

Acho que você consegue pegar o nome do class de Expensive e verificar se é a classe que possui tal método. Veja:

<c:if test="${Expense.getClass().name == 'NomeDaClasseQueTemOMetodoValues'}">
	<td>${Expense.values}</td>
</c:if>

@Lucas_Camara eu tinha achado essas solução googlando…mas… ai o meu polimorfismo vai pro espaço né… pq se eu tiver “n” novas classes com o método values … vou precisar ficar controlando esse if…

Mas valeu pela ajuda…

Isso tá com cheiro de problema de design de classes.

Se você precisa checar o tipo do objeto (instanceof, getClass().getName(), etc.) para tomar uma ação (mostrar algo ou não, nesse caso), o polimorfismo provavelmente já foi pro espaço. Há situações onde isso é inevitável (quando você depende das classes de um framework, por exemplo), mas até nesses casos a boa prática é esconder essas bruxarias em classes no porão, em lugares centralizados, atrás de métodos que não deixam isso transparecer para o resto do programa.

Se você tem uma lista de Calculables, tem que trata-la de acordo, fingindo ignorância sobre o seu conteúdo. Porque você não separa essa lista em duas (ou mais)? Quebra as interfaces em pedaços menores e usa referências de tipos que tornem essa tarefa mais fácil. Reorganiza a hierarquia de classes, cria tipos novo, particiona tipos grandes em tipos mais concisos.

Qual é a característica que todos esses objetos da lista em em comum para serem dispostos nessa mesma tabela?

A alternativa mais simples (porém ainda “errada”) é colocar o método getValue em Calculable e um novo método, tipo boolean hasValue(). Quem tem o valor, retorna true no hasValue() e retorna o valor no getValue(). Quem não tem esse valor, retorna false no hasValue() e atira uma UnsupportedOperationException para quem chamar getValue(). Isso ainda fica bem estranho, mais vai te ajudar a por um if ali no JSP para decidir se o getValue deve ser chamado ou não. Se nem todo Calculable deve ter um getValue(), então ele não deve estar na interface Calculable. Se alguém disser que é gambiarra, diga que é apenas uma alteração provisória do protocolo hehe.

É possível!!!

Vou detalhar o meu problema e talvez a solução esteja ai…

Estou aprendendo e por isso estudo e aplico o que aprendi… tenho certeza que com alguns frameworks minha vida ficará bem mais facilitada…mas até o momento, eu aprendi Servlets, JSP, JDBC, JSTL e EL.

Estou fazendo um pequeno programa que cadastra despesas.

Posso ter uma despesa simples: Luz - R$100,00

Mas posso ter também uma despesa “composta”:

Cartão de Crédito - Total da Soma das compras R$200,00

  • Sapato novo - R$100,00
  • Camisa nova - R$100,00

Pensando nisso… pensei em ter uma interface que obriga tanto a despesa simples como a despesa composta em implementar o método getValue … a despesa simples retornar simplesmente o proprio valor da despesa enquanto na despesa composta a implementação do getValue devolve a soma das compras (que chamo de operation).

Até aqui tudo bem, por favor, veja q o problema não está no getValue() da interface e sim no getValues() da despesa composta. Por que preciso no JSP mostraros DETALHES da despesa composta, quando esta for uma despesa composta! Por que śo ela tem detalhes!!!

Talvez (veja, talvez kkk) o meu UML ajude…

Estou fazendo uma abstração grande, por que colocarei Receitas também neste mesmo programa!!!

1 curtida

Edit: Não tinha entendido a questão de separar o getValue do getValues. Compreendi melhor e respondi abaixo.

Entendi o que você quer fazer, e sua lógica a respeito das despesas está correta. O nome desse padrão que você aplicou é Composite, se quiser ler mais sobre ele (tem uma seção no livro da Gang of Four). Mas acho que você já sabe disso, por causa dos nomes de algumas classes.

Vou implementar um esboço aqui para você ter mais umas ideias:

interface Expense {
    double getValue();
}

class SingleExpense implements Expense {
    final double value;

    SingleExpense(double value) {
        this.value = value;
    }

    @Override
    public double getValue() {
        return value;
    }
}

class CompositeExpense implements Expense {

    private final Expense[] expenses;

    CompositeExpense(Expense... expenses) {
        this.expenses = expenses;
    }

    @Override
    public double getValue() {
        double value = 0;
        for (Expense expense : expenses) {
            value += expense.getValue();
        }
        return value;
    }

}

public class Main {
    public static void main(String[] args) {
        List<Expense> expenses = new ArrayList<>();
        expenses.add(new SingleExpense(10));
        expenses.add(new SingleExpense(5.5));
        expenses.add(new CompositeExpense(new SingleExpense(1), new SingleExpense(2), new SingleExpense(3)));
        expenses.add(new SingleExpense(12));

        expenses.forEach(expense -> System.out.println(expense.getValue()));
    }
}
1 curtida

Entendi agora o motivo de você querer os dois métodos, para separar na exibição. O exemplo que eu implementei acima não considera isso.

Se você realmente quer distinguir os dois tipos de despesa, não pode trata-los como um tipo só (Expense).

A interface Calculable serve apenas para implementar o valor final de uma despesa, seja ela uma SingleExpense ou uma CompositeExpense. Isso funciona bem!

Eu tenho problemas em fazer a exibição de métodos especificos de cada tipo de Expense dentro de uma iteração usando o tipo mais generico de uma Expense, ou seja, um Calculable.

Vejo algumas formas de resolver isso, mas pela falta de experiência não sei por onde seguir. São elas:

  1. Eu fazer um If no JSTL e checar se a classe iterada é do tipo que possui o método getValues (e não o getValue da interface),

  2. A servlet enviar duas listas na request (não mais apenas uma de Calculable) … uma só com as SingleExpense e outra com as CompositeExpense … ai no JSP eu faço dois foreach e ai eu terei certeza que no foreach da lista de CompositeExpense o método getValues existirá…

Mas essas duas opções eu fico preso em uma possível existência de novos tipos de Despesa … nesse caso eu terei que ir na Servlet e incluir (opção 2) ou ir no JSP e fazer mais um if (Opção 1)…

Pode ser que exista uma opção 3, a qual eu não conheça… onde talvez eu pudesse checar no JSTL se o método existe e caso exista eu utilize… ou mesmo na Servlet eu criar a lista de uma forma diferente…que no JSTL eu possa fazer uma checagem menos acoplada…

Vejo também a opção q vc deu (acho q foi vc… )… de colocar na interface um boolean hasValues() … e cada implementação diz se tem ou não values… não tão elegante…mas resolve o problema (acho) de um if no jstl…

Não sei, estou trazendo a discussão para um nível de entendimento e boas praticas levando em consideração as tecnologias que eu tenho nesse momento (SERVLETs, JDBC, JSP, JSTL e EL)… Pode ser que a solução (com essas tecnologias em mão) realmente sejam apenas essas… SIM… Vai ter que fazer IFs sempre q necessário… ou SIM… vai ter que checar a Servlet sempre que necessário caso venha surgir novo tipo de Expense…

Você sacou perfeitamente o problema de ter que checar subtipo para tomar uma ação. Ao invés de encapsular a lógica dentro de cada tipo e utilizar o supertipo de forma genérica, essas soluções introduzem um code smell que é ficar adicionando um if novo toda vez que surgir um tipo.

Uma possível forma de resolver isso é alterar o tipo de retorno do getValue. Ao invés de retornar um double, você retorna um novo objeto do tipo Value. Nesse objeto, você seta informações como “valorTotal” ou “valor a partir da soma de outros valores”. Inclusive, nesse último caso, você pode até retornar uma lista com as despesas que formam essa despesa e montar tabelas internas ou algo assim.

Lá no JSP, você vai pegar a lista de Expense, pegar o Value de cada uma e colocar cada valor no campo certo. Onde o valor não existir, é 0.

1 curtida

Estava aqui pensando e isso que eu falei não faz o menor sentido. É a mesma coisa de colocar um método getValues() no Expense, e ainda poupar a criação de um tipo novo.

Vou tentar pensar em algo mais inteligente e posto aqui se conseguir.

Mas então… vc respondeu na mesma hora em que eu estava respondendo…

Mudei o meu método getValue da interface…para retornar um objeto do tipo Value…

public interface Calculable {

public Value getValue();

}

public class Value {

private Double amount;
private List<Operation> operations;

public Value(Double amount, List<Operation> operations) {
	this.amount = amount;
	this.operations = operations;
}

public Value(Double amount) {
	this.amount = amount;
}

public Double getAmount() {
	return amount;
}

public List<Operation> getOperations() {
	return operations;
}

}

e no meu JSP…eu verifico se o Expense.value.operations não é vazio…se for…não mostra…se não for…mostra…

Atendeu…

Ficou mais trabalhoso…mas atendeu… eu continuo mandando apenas um list de Calculables na request…

O que ficou estranho é que Expense.value não retorna um valor… é necessário agora fazer um Expense.value.amount no JSP…

Mas, atendeu…

Acha isso ruim?

Olha, acredito que uma solução similar e mais rápida é a que eu te falei em seguida: coloca o getValues no Expense mesmo. Na prática dá na mesma, só que com um tipo a menos.

Consegue enxergar que é a mesma coisa?

Você abordou de uma forma um pouco diferente do que eu tinha pensado. Dessa forma que você fez ficou melhor do que eu pensei.

A Expense simples pode retornar um Value com uma lista de apenas 1 operação. A Expense composta pode retornar uma lista com mais delas.

Agora eu te pergunto: O que acontece se uma Expense composta tiver outra expense composta dentro dela? Você considera apenas o valor bruto, como se fosse uma expense comum? Ou faz um “flatMap”, e considera todas as operações de expenses compostas filhas como sendo da pai também?

1 curtida

Mas ja é… vou colocar o código das classes… talvez fique mais claro entender essa “confusão” que estou fazendo kkk Estou colocando como estava antes da sugestão de criação de uma classe Value, ok?

public interface Calculable {
	public Double getValue();
}

public abstract class Transaction implements Calculable {

	protected Integer id;
	protected String title;
	protected User user;
	protected YearMonth period;
	
	public Transaction(Integer id, String title, User user, YearMonth period) {
		this.id = id;
		this.title = title;
		this.user = user;
		this.period = period;
	}
	
	public Integer getId() {
		return id;
	}
	
	public String getTitle() {
		return title;
	}
	
	public User getUser() {
		return user;
	}
	
	public YearMonth getPeriod() {
		return period;
	}
}

public abstract class SingleTransaction extends Transaction {

	protected Double value; //Não tenho o getValue da classe SingleTransaction. Ele vem da Interface Calculable
	
	public SingleTransaction(Integer id, String title, User user, YearMonth period, Double value) {
		super(id, title, user, period);
		this.value = value;
	}	

}

public abstract class CompositeTransaction extends Transaction {

	protected List<Operation> values;
	
	public CompositeTransaction(Integer id, String title, User user, YearMonth period, List<Operation> values) {
		super(id, title, user, period);
		this.values = values;
	}
	
	public List<Operation> getValues() {
		return values;
	}

}

public class SingleExpense extends SingleTransaction {

	public SingleExpense(Integer id, String title, User user, YearMonth period, Double value) {
		super(id, title, user, period, value);
	}

	//Interface Calculable
   @Override
	public Double getValue() {
		return this.value;
	}
	
}

public class CompositeExpense extends CompositeTransaction {

	public CompositeExpense(Integer id, String title, User user, YearMonth period, List<Operation> values) {
		super(id, title, user, period, values);
	}

	//Interface Calculable
   @Override
	public Double getValue() {
		Double amount = 0.;
		
		for (Operation operation : values) {
			amount += operation.getValue();
		}
		
		return amount;
	}
	
	

}

public class Operation {

	private Integer id;
	private String title;
	private Double value;

	public Operation(Integer id, String title, Double value) {
		this.id = id;
		this.title = title;
		this.value = value;
	}

	public Integer getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}

	public Double getValue() {
		return value;
	}

}

Informações importantes:

  • As classes abstratas SingleTransaction e CompositeTransaction foram criadas por que eu irei colocar receitas também, que são iguais as despesas mas são transações diferentes…

  • Respondendo ao seu questionamento anterior, na CompositeExpense eu criei um list de Operations e não de Expense por que na Lista eu não preciso ter novamente o Period… somente um titulo e valor (a principio…depois existirão variações…)

Opções até este momento:

  • Criar uma classe Value e alterar o retorno do método getValue da interface Calculable. A Classe Value receberá um valor e uma list de Operations, ou somente uma lista de Operations…

  • Algo que pensei: Ao invés de existir SingleExpense e CompositeExpense… existiria apenas Expense… e um atributo values sendo um list de Operations. Se uma Expense for simples esse list tem apenas uma linha…se for composta tem N linhas… mas ficaria algo do tipo:

    Despesa Luz com um list de uma linha no valor , por exemplo, de R$200,00 
    
    Ai não sei se isso seria uma gambiarra ou não!

Eu to um pouco ocupado, não sei se vou conseguir responder você logo, até porque tenho que pesquisar e pensar antes. Assim que eu tiver um tempo eu penso mais sobre isso. Até lá talvez alguém apareça com uma solução mais ninja.

Claro… valeu mesmo por toda ajuda ate aqui…

@lvbarbosa Resolvi mudando um pouco o dominio…

Dessa forma… eu mantive os getters originais getValue e getValues respectivamente de SingleTransaction e CompositeTransaction e a interface Calculable exige a implementação dos métodos getAmount e getAllValues nas subclasses de Transaction … quando tiver list de valores o método getAllValues implementado trará a lista (que em outras situações também pode ser chamada por getValues quando o objeto for CompositeExpense)… e o método getAmount traz o valor total do Expense conforme for seu tipo… Single…tras o valor do atributo value e Composite tras a soma de todos os Values… e o getAllValues() quando for um SingleExpense, retorna null …

Dessa forma… nao quebro o meu JSP… ao usar ${Expense.amount } e checar ${Expense.allValues }… só quebrará se for usado os getters de cada subclasse nesse cenário de uma lista genérica de Calculable…

Bom … não sei se foi a melhor solução…mas atendeu bem…

Valeu por toda sua ajuda…clareou bastante as ideias…