我经常在我的代码中使用访问者模式。当类层次结构实现了访问者时,我将其用作instanceof
和cast的替代方法。然而,它导致了一些非常尴尬的代码,我想改进它。
考虑一下人为的案例:
interface Animal {
void accept(AnimalVisitor visitor);
}
class Dog implements Animal {
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
class Cat implements Animal {
void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
}
interface AnimalVisitor {
default void visit(Cat cat) {};
default void visit(Dog dog) {};
}
在大多数情况下,仅针对狗做某些事情(例如)我实现了一个在visit
方法中实现逻辑的访问者 - 正如模式所预期的那样。
但是,我希望从访客那里返回一只可选的狗以便在外面使用。
在这些情况下,我最终得到了一些非常丑陋的代码:
List<Dog> dogs = new ArrayList<>();
animal.accept(new AnimalVisitor() {
void visit(Dog dog) {
dogs.add(dog);
}
}
Optional<Dog> possibleDog = dogs.stream().findAny();
我无法直接在访问者中分配possibleDog
,因为它不是最终变量,因此列表。
这是非常丑陋和低效的,只是为了满足有效终结的要求。我对替代品的想法感兴趣。
我考虑的替代方案:
将访问者转换为可以返回值的通用
interface Animal {
<T> T accept(AnimalVisitor<T> visitor);
}
interface AnimalVisitor <T> {
default Optional<T> visit(Dog dog) { return Optional.empty(); }
default Optional<T> visit(Cat cat) { return Optional.empty(); }
}
创建一个包含大部分代码的抽象访问者,并且可以简单地扩展以直接设置可选
abstract class AnimalCollector implements AnimalVisitor <T> {
private Optional<T> result = Optional.empty;
protected void setResult(T value) {
assert !result.isPresent();
result = Optional.of(value);
}
public Optional<T> asOptional() {
return result;
}
}
使用流构建器而不是列表
Stream.Builder<Dog> dogs = Stream.builder();
animal.accept(new AnimalVisitor() {
void visit(Dog dog) {
dogs.accept(dog);
}
}
Optional<Dog> possibleDog = dogs.build().findAny();
但我发现这些特别优雅。它们涉及很多样板,只是为了实现基本的asA
逻辑。我倾向于在我的代码中使用第二个解决方案来保持使用清洁。我错过了一个更简单的解决方案吗?
为了清楚起见,我对#34;使用instanceof和casts&#34;的某些变体的答案不感兴趣。我意识到它可以在这个微不足道的情况下工作,但我考虑的情况对访问者的使用相当复杂,包括访问复合材料和代表,这使得投射不切实际。
答案 0 :(得分:1)
通过将问题中的AnimalCollector
分成两部分,我认为我们可以创建出非常简洁的内容。
(这些示例基于问题中原始的AnimalVisitor
接口-使用诸如void visit(Cat cat);
这样的方法)。
“收集到可选”部分可以很好地提取到独立类中:
public class OptionalCollector<T> {
private Optional<T> result = Optional.empty();
public void setResult(T value) {
result = Optional.of(value);
}
public Optional<T> asOptional() {
return result;
}
}
不过,另一种可能性是有点“ lambda访问”样式编码。一些静态工厂方法使访问者可以轻松定义,而无需在匿名内部类中声明方法。
这些是一些示例工厂方法:
import java.util.function.Consumer;
public class AnimalVisitorFactory {
static AnimalVisitor dogVisitor(Consumer<Dog> dogVisitor) {
return new AnimalVisitor() {
@Override
public void visit(Dog dog) {
dogVisitor.accept(dog);
}
};
}
static AnimalVisitor catVisitor(Consumer<Cat> catVisitor) {
return new AnimalVisitor() {
@Override
public void visit(Cat cat) {
catVisitor.accept(cat);
}
};
}
}
然后可以将这两个部分结合起来:
import static AnimalVisitorFactory.*;
OptionalCollector<Dog> collector = new OptionalCollector<>();
animal.accept(dogVisitor(dog -> collector.setResult(dog)));
Optional<Dog> possibleDog = collector.asOptional();
这超出了问题中案例所需要的范围,但是请注意,可以使用流利的API风格进一步完善该思想。在dogVisitor()
接口上也使用类似的catVisitor()
和AnimalVisitor
默认方法,然后可以将几个lambda链接在一起以构建更完整的访问者。
答案 1 :(得分:1)
我知道您明确要求不使用instanceof
或cast
的解决方案,但实际上我认为在这种特殊情况下,您想实现逻辑以过滤特定子类型,这可能是值得的考虑并类似于您的仿制药方法,这并不难看恕我直言:
// as mentioned in the comment above I removed the Optional return types
interface AnimalVisitor<T> {
T visit(Dog dog);
T visit(Cat cat);
}
public class AnimalFinder<A extends Animal> implements AnimalVisitor<A> {
final Class<A> mAnimalClass;
public AnimalFinder(Class<A> aAnimalClass) {
this.mAnimalClass = aAnimalClass;
}
@Override
public A visit(Dog dog) {
if (dog != null && mAnimalClass.isAssignableFrom(dog.getClass())) {
return mAnimalClass.cast(dog);
} else {
return null;
}
}
@Override
public A visit(Cat cat) {
if (cat != null && mAnimalClass.isAssignableFrom(cat.getClass())) {
return mAnimalClass.cast(cat);
} else {
return null;
}
}
}
现在,您可以简单地重用AnimalFinder
并提供您感兴趣的类型作为参数:
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
AnimalVisitor<Dog> dogFinder = new AnimalFinder<>(Dog.class);
System.out.println(Optional.ofNullable(dog.accept(dogFinder)));
System.out.println(Optional.ofNullable(cat.accept(dogFinder)));
// using AnimalFinder there is actually no need to implement something like DogPrinter
// simply use a Consumer or a lambda expression
Optional.ofNullable(dog.accept(dogFinder)).ifPresent(d -> System.out.println("Found dog" + d));
Optional.ofNullable(cat.accept(dogFinder)).ifPresent(d -> System.out.println("Found dog" + d));
}
从我的角度来看,此解决方案具有一些优点:
Animal
类型,则需要在其中添加新的访问方法的单个地方莫里斯当然是正确的。您可以简单地用简单的AnimalFinder
代替Predicate
访问者(实际上几乎是Guava's Predicates.instanceOf
的作用):
public static <A, T> Predicate<A> instanceOf(final Class<T> aClass) {
return a -> (a != null && aClass.isAssignableFrom(a.getClass()));
}
并像这样使用它来过滤Optional
或Stream
:
System.out.println(Optional.ofNullable(dog).filter(instanceOf(Dog.class)));
这甚至具有更高的可重用性(不仅限于Animal
,重复代码更少,只要您拥有Optional
或Stream
,就可以使用。
答案 2 :(得分:1)
在这种情况下,我认为您不需要访客;它使代码变得多余。我宁愿使用选择器:
public class AnimalSelector<T extends Animal> {
private final Class<T> clazz;
public AnimalSelector(Class<T> clazz) {
this.clazz = clazz;
}
public T select(Animal a) {
if (clazz.isInstance(a)) {
return clazz.cast(a);
} else {
return null;
}
}
}
现在您可以编写如下内容:
Dog dog = new AnimalSelector<>(Dog.class).select(animal);
Cat cat = new AnimalSelector<>(Cat.class).select(animal);
答案 3 :(得分:1)
实际上,您不需要返回虚空的AnimalVisitor
。根据您的代码并维持Visitor模式,我将通过以下方式进行操作。
动物界面。
public interface Animal {
default <T extends Animal> Stream<T> accept(AnimalVisitor visitor) {
try {
return visitor.visit(this).stream();
} catch (ClassCastException ex) {
return Stream.empty();
}
}
}
猫狗派生类。
public class Dog implements Animal {
@Override
public String toString() {
return "Fido";
}
}
public class Cat implements Animal {
@Override
public String toString() {
return "Felix";
}
}
AnimalVisitor界面。
public interface AnimalVisitor<T extends Animal> {
Optional<T> visit(T animal);
}
将它们放在一起。
public class AnimalFinder {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
/*
* The default/old way
* AnimalVisitor<Dog> dogFinder = new AnimalVisitor<Dog>() {
* @Override
* public Optional<Dog> visit(Dog animal) {
* return Optional.of(animal);
* }
* };
*
* Or lambda expression
*
* AnimalVisitor<Dog> dogFinder = (animal) -> Optional.of(animal);
*
* Or member reference
*/
AnimalVisitor<Dog> dogFinder = Optional::of;
Optional fido = dog.accept(dogFinder).findAny();
Optional felix = cat.accept(dogFinder).findAny();
System.out.println(fido); // Optional[Fido]
System.out.println(felix); // Optional.empty
felix.ifPresent(a -> System.out.printf("Found %s\n", a));
fido.ifPresent(a -> System.out.printf("Found %s\n", a)); // Found Fido
}
}
答案 4 :(得分:0)
我试用了通用访客解决方案。它实际上并不太糟糕 - 主要的丑陋是当访问者不需要返回值时使用Optional<Void>
。
我仍然对任何更好的选择感兴趣。
public class Visitor {
interface Animal {
<T> Stream<T> accept(AnimalVisitor<T> visitor);
}
static class Dog implements Animal {
@Override
public String toString() {
return "Fido";
}
@Override
public <T> Stream<T> accept(AnimalVisitor<T> visitor) {
return visitor.visit(this).stream();
}
}
static class Cat implements Animal {
@Override
public String toString() {
return "Felix";
}
@Override
public <T> Stream<T> accept(AnimalVisitor<T> visitor) {
return visitor.visit(this).stream();
}
}
interface AnimalVisitor<T> {
default Optional<T> visit(Dog dog) {
return Optional.empty();
}
default Optional<T> visit(Cat cat) {
return Optional.empty();
}
}
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
AnimalVisitor<Dog> dogFinder = new AnimalVisitor<Dog>() {
@Override
public Optional<Dog> visit(Dog dog) {
return Optional.of(dog);
}
};
AnimalVisitor<Void> dogPrinter = new AnimalVisitor<Void>() {
@Override
public Optional<Void> visit(Dog dog) {
System.out.println("Found dog " + dog);
return Optional.empty();
}
};
System.out.println(dog.accept(dogFinder).findAny());
System.out.println(cat.accept(dogFinder).findAny());
dog.accept(dogPrinter);
cat.accept(dogPrinter);
}
}
答案 5 :(得分:0)
如何?
interface Animal {
<T> T accept(AnimalVisitor<T> visitor);
}
static class Dog implements Animal {
public <T> T accept(AnimalVisitor<T> visitor) {
return visitor.visit(this);
}
}
static class Cat implements Animal {
public <T> T accept(AnimalVisitor<T> visitor) {
return visitor.visit(this);
}
}
interface AnimalVisitor<T> {
default T defaultReturn(){ return null; }
default T visit(Cat cat) { return defaultReturn(); };
default T visit(Dog dog) { return defaultReturn(); };
interface Finder<T> extends AnimalVisitor<Optional<T>> {
@Override default Optional<T> defaultReturn(){ return Optional.empty(); }
}
interface CatFinder extends Finder<Cat> {
@Override default Optional<Cat> visit(Cat cat){
return Optional.ofNullable(find(cat));
}
Cat find(Cat cat);
CatFinder FIND = c -> c;
}
interface DogFinder extends Finder<Dog> {
@Override default Optional<Dog> visit(Dog dog){
return Optional.ofNullable(find(dog));
}
Dog find(Dog dog);
DogFinder FIND = d -> d;
}
}
然后使用它...
Animal a = new Cat();
Optional<Cat> o = a.accept((CatFinder)c -> {
//use cat
return c; //or null to get an empty optional;
});
//or if you just want to always return the cat:
Optional<Cat> o = a.accept(CatFinder.FIND);
如果需要,可以更改find
以返回布尔值,因此它就像一个谓词。
答案 6 :(得分:0)
我会尝试使用由访问者管理的侦听器来分解逻辑。当访客找到一条狗时,它想归还它,它会呼叫所有注册的侦听器来报告该狗。反过来,听众将负责对要报告的狗进行任何您想做的事情。通过将报告的狗聚合逻辑移出访客实现,这可以使实现更整洁。