Avec Java 8, j'ai ce code:
if(element.exist()){
// Do something
}
Je veux convertir au style lambda,
element.ifExist(el -> {
// Do something
});
avec une méthode ifExist
comme celle-ci:
public void ifExist(Consumer<Element> consumer) {
if (exist()) {
consumer.accept(this);
}
}
Mais maintenant, j'ai d'autres cas à appeler:
element.ifExist(el -> {
// Do something
}).ifNotExist(el -> {
// Do something
});
Je peux écrire un ifNotExist
similaire, et je veux qu'elles s'excluent mutuellement (si la condition exist
est vraie, il n'est pas nécessaire de vérifier ifNotExist
, car parfois, la méthode exist () prend tellement de travail à vérifier), mais je dois toujours vérifier deux fois. Comment puis-je éviter cela?
Peut-être que le mot "exist" fait que quelqu'un comprend mal mon idée. Vous pouvez imaginer que j'ai aussi besoin de méthodes:
ifVisible()
ifEmpty()
ifHasAttribute()
Beaucoup de gens ont dit que c'était une mauvaise idée, mais:
Dans Java 8, nous pouvons utiliser lambda forEach au lieu d'une boucle traditionnelle for
. En programmation for
et if
sont deux commandes de flux de base. Si nous pouvons utiliser lambda pour une boucle for
, pourquoi utiliser lambda pour if
mauvaise idée?
for (Element element : list) {
element.doSomething();
}
list.forEach(Element::doSomething);
Dans Java 8, il y a Optional
avec ifPresent, semblable à mon idée de ifExist:
Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);
Et en ce qui concerne la maintenance et la lisibilité du code, que pensez-vous si le code suivant contient de nombreuses clauses répétitives simples if
?
if (e0.exist()) {
e0.actionA();
} else {
e0.actionB();
}
if (e1.exist()) {
e0.actionC();
}
if (e2.exist()) {
e2.actionD();
}
if (e3.exist()) {
e3.actionB();
}
Comparer aux:
e0.ifExist(Element::actionA).ifNotExist(Element::actionB);
e1.ifExist(Element::actionC);
e2.ifExist(Element::actionD);
e3.ifExist(Element::actionB);
Ce qui est mieux? Et, oups, avez-vous remarqué que dans le code de clause if
traditionnel, il y a une erreur dans:
if (e1.exist()) {
e0.actionC(); // Actually e1
}
Je pense que si nous utilisons lambda, nous pouvons éviter cette erreur!
Comme cela correspond presque mais pas vraiment Facultatif, vous pourriez peut-être reconsidérer la logique:
Java 8 a une expressivité limitée:
Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);
System.out.println(element.orElse(DEFAULT_ELEM));
Ici, la map
pourrait restreindre l'affichage de l'élément:
element.map(el -> el.mySpecialView()).ifPresent(System.out::println);
Java 9:
element.ifPresentOrElse(el -> System.out.println("Present " + el,
() -> System.out.println("Not present"));
En général, les deux branches sont asymétriques.
C'est ce qu'on appelle 'interface fluide' . Modifiez simplement le type de retour et return this;
pour vous permettre d’enchaîner les méthodes:
public MyClass ifExist(Consumer<Element> consumer) {
if (exist()) {
consumer.accept(this);
}
return this;
}
public MyClass ifNotExist(Consumer<Element> consumer) {
if (!exist()) {
consumer.accept(this);
}
return this;
}
Vous pourriez avoir un peu plus sophistiqué et retourner un type intermédiaire:
interface Else<T>
{
public void otherwise(Consumer<T> consumer); // 'else' is a keyword
}
class DefaultElse<T> implements Else<T>
{
private final T item;
DefaultElse(final T item) { this.item = item; }
public void otherwise(Consumer<T> consumer)
{
consumer.accept(item);
}
}
class NoopElse<T> implements Else<T>
{
public void otherwise(Consumer<T> consumer) { }
}
public Else<MyClass> ifExist(Consumer<Element> consumer) {
if (exist()) {
consumer.accept(this);
return new NoopElse<>();
}
return new DefaultElse<>(this);
}
Exemple d'utilisation:
element.ifExist(el -> {
//do something
})
.otherwise(el -> {
//do something else
});
Vous pouvez utiliser une seule méthode prenant deux consommateurs:
public void ifExistOrElse(Consumer<Element> ifExist, Consumer<Element> orElse) {
if (exist()) {
ifExist.accept(this);
} else {
orElse.accept(this);
}
}
Puis appelez-le avec:
element.ifExistOrElse(
el -> {
// Do something
},
el -> {
// Do something else
});
(1) Vous semblez mélanger différents aspects - flux de contrôle et logique de domaine .
element.ifExist(() -> { ... }).otherElementMethod();
^ ^
control flow method business logic method
(2) Il est difficile de savoir comment les méthodes après une méthode de flux de contrôle (telle que ifExist
, ifNotExist
) devraient se comporter. Devraient-ils toujours être exécutés ou être appelés uniquement sous la condition (similaire à ifExist
)?
(3) Le nom ifExist
implique une opération de terminal. Il n'y a donc rien à renvoyer - void
. Un bon exemple est void ifPresent(Consumer)
de Optional
.
J'écrirais une classe complètement séparée qui serait indépendante de toute classe concrète et de toute condition spécifique.
L'interface est simple et comprend deux méthodes de flux de contrôle sans contexte: ifTrue
et ifFalse
.
Il existe plusieurs façons de créer un objet Condition
. J'ai écrit une méthode de fabrique statique pour votre instance (par exemple element
) et votre condition (par exemple Element::exist
).
public class Condition<E> {
private final Predicate<E> condition;
private final E operand;
private Boolean result;
private Condition(E operand, Predicate<E> condition) {
this.condition = condition;
this.operand = operand;
}
public static <E> Condition<E> of(E element, Predicate<E> condition) {
return new Condition<>(element, condition);
}
public Condition<E> ifTrue(Consumer<E> consumer) {
if (result == null)
result = condition.test(operand);
if (result)
consumer.accept(operand);
return this;
}
public Condition<E> ifFalse(Consumer<E> consumer) {
if (result == null)
result = condition.test(operand);
if (!result)
consumer.accept(operand);
return this;
}
public E getOperand() {
return operand;
}
}
De plus, nous pouvons intégrer Condition
dans Element
:
class Element {
...
public Condition<Element> formCondition(Predicate<Element> condition) {
return Condition.of(this, condition);
}
}
Le modèle que je promeut est:
Element
;Condition
;Condition
;Element
;Element
.Obtention de Condition
by Condition.of
:
Element element = new Element();
Condition.of(element, Element::exist)
.ifTrue(e -> { ... })
.ifFalse(e -> { ... })
.getOperand()
.otherElementMethod();
Obtention de Condition
by Element#formCondition
:
Element element = new Element();
element.formCondition(Element::exist)
.ifTrue(e -> { ... })
.ifFalse(e -> { ... })
.getOperand()
.otherElementMethod();
Pour d'autres méthodes de test, l'idée reste la même.
Element element = new Element();
element.formCondition(Element::isVisible);
element.formCondition(Element::isEmpty);
element.formCondition(e -> e.hasAttribute(ATTRIBUTE));
C'est une bonne raison de repenser la conception du code. Aucun des 2 extraits est génial.
Imaginez que vous ayez besoin de actionC
dans e0.exist()
. Comment la référence à la méthode Element::actionA
sera-t-elle modifiée?
Ce serait redevenu un lambda:
e0.ifExist(e -> { e.actionA(); e.actionC(); });
sauf si vous enveloppez actionA
et actionC
dans une seule méthode (ce qui peut sembler horrible):
e0.ifExist(Element::actionAAndC);
Le lambda est maintenant moins lisible que le if
.
e0.ifExist(e -> {
e0.actionA();
e0.actionC();
});
Mais combien ferions-nous pour faire cela? Et combien d'efforts allons-nous déployer pour maintenir tout cela?
if(e0.exist()) {
e0.actionA();
e0.actionC();
}
Si vous effectuez une vérification simple sur un objet, puis exécutez certaines instructions en fonction de la condition, une approche consiste à définir une carte avec un prédicat comme clé et une expression souhaitée comme valeur, par exemple.
Map<Predicate<Integer>,Supplier<String>> ruleMap = new HashMap<Predicate<Integer>,Supplier<String>>(){{
put((i)-> i<10,()->"Less than 10!");
put((i)-> i<100,()->"Less than 100!");
put((i)-> i<1000,()->"Less than 1000!");
}};
Nous pourrions ultérieurement diffuser la carte suivante pour obtenir la valeur lorsque le prédicat renvoie true, ce qui pourrait remplacer tout le code if/else
ruleMap.keySet()
.stream()
.filter((keyCondition)->keyCondition.test(numItems,version))
.findFirst()
.ifPresent((e)-> System.out.print(ruleMap.get(e).get()));
Puisque nous utilisons findFirst (), cela équivaut à if/else if/else if ......