访问者模式(Visitor Pattern)
**访问者模式(Visitor Pattern)**属于行为型模式。
最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
什么是访问者模式?
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
简单来说:多个不同的对象(访问者),想要对多个不同的同一类型对象(被访问元素)做一些列操作。
UML
角色
- 抽象元素(Element)角色:被访问的类的抽象。其中====
抽象出来了一个共同的方法==,该方法(accept(Visitor v)
)是访问者访问的通道。 - 具体元素(ConcreteElement)角色:实现抽象元素角色提供的访问者访问通道
accept(Visitor v)
操作 ,在此方法中执行类Visitor
中的访问对应的方法visit(ConcreteElement e)
,通常这个方法中将当前对象(this
)作为参数以便被访问。 - 对象结构(Object Structure)角色:元素角色的容器。并统一调用了(迭代等)具体元素的方法
accept(Visitor v)
。 - 抽象访问者(Visitor)角色:定义访问具体元素的接口,为每个具体元素类对应一个访问操作,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
补充:该模式中元素应该是稳定的,因为无论是数量还是类的修改都需要涉及所有的访问者类。
应用
使用模板
public class VisitorPattern {
public static void main(String[] args) {
//初始化具体元素
ConcreteElementA elementA = new ConcreteElementA();
ConcreteElementB elementB = new ConcreteElementB();
//初始化具体参观者
ConcreteVisitor visitor = new ConcreteVisitor();
//初始并设置对象结构
ObjectStructure structure = new ObjectStructure();
structure.addElement(elementA);
structure.addElement(elementB);
//对象结构执行参观操作
structure.accept(visitor);
}
}
/
//元素接口
interface Element {
//被访问接口
void accept(Visitor v);
}
/
//具体元素
class ConcreteElementA implements Element {
//具体元素特殊属性与方法
Integer age = 18;
public Integer getAge() {
return age;
}
//具体的被访问接口
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
/
//同上
class ConcreteElementB implements Element {
String name = "Maria";
public String getName() {
return name;
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
/
//抽象的观察者
interface Visitor {
//具体元素A对应的观察方法
void visit(ConcreteElementA elementA);
//具体元素B对应的观察方法
void visit(ConcreteElementB elementB);
}
/
//具体的观察者
class ConcreteVisitor implements Visitor {
//当前观察者对应的该元素的观察方法
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("elementA print age:" + elementA.getAge());
}
//同上
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("elementB print name:" + elementB.getName());
}
}
/
//元素对象的存储结构
class ObjectStructure{
//元素对象存储属性
private final List<Element> elements = new ArrayList<>();
//元素对象增删操作方法
void addElement(Element e){
elements.add(e);
}
void delElement(Element e){
elements.remove(e);
}
//统一被访问方法
void accept(Visitor v){
elements.forEach(x->{
x.accept(v);
});
}
}
为什么要使用访问者模式?
目的:主要将数据结构与数据操作分离
优缺点
优点:
- 符合单一职责原则。
- 优秀的扩展性。方便的拓展访问者,可以同时改动对所有元素的操作。
- 灵活性。根据需求灵活的使用合适的访问者访问一整个结构的元素。
缺点:
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
怎样使用访问者模式?
使用场景
当对集合中固定数量的不同类型数据进行多种操作时,使用访问者模式。
- 对象结构(ObjectStructure)相对稳定,但其操作算法经常变化。
- 对象结构(ObjectStructure)中的对象需要提供多种不同且不相关的操作。
- 需要对一个对象结构(ObjectStructure)中的元素对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
JDK中的应用
java.nio.file.FileVisitor<T>
该类可以被用来遍历文件或者目录。该接口对应了具体访问者(ConcreteVisitor)角色。其中定义了四种固定的抽象方法。
package java.nio.file;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
public interface FileVisitor<T> {
//在访问目录中的条目之前为目录调用。
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException;
//为目录中的文件调用。
FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException;
//为无法访问的文件调用。
FileVisitResult visitFileFailed(T file, IOException exc) throws IOException;
//在访问了目录中的条目及其所有后代之后为目录调用。
FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException;
}
java.nio.file.SimpleFileVisitor<T>
对应了具体元素(ConcreteElement)角色。实现了具体的访问操作。
package java.nio.file;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
import java.util.Objects;
public class SimpleFileVisitor<T> implements FileVisitor<T> {
protected SimpleFileVisitor() {
}
@Override
public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
throws IOException
{
Objects.requireNonNull(dir);
Objects.requireNonNull(attrs);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(T file, BasicFileAttributes attrs)
throws IOException
{
Objects.requireNonNull(file);
Objects.requireNonNull(attrs);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(T file, IOException exc)
throws IOException
{
Objects.requireNonNull(file);
throw exc;
}
@Override
public FileVisitResult postVisitDirectory(T dir, IOException exc)
throws IOException
{
Objects.requireNonNull(dir);
if (exc != null)
throw exc;
return FileVisitResult.CONTINUE;
}
}
java.nio.file.Files
对应了对象结构(Object Structure)角色,在其中定义了对每种不同的文件的具体访问方式。
package java.nio.file;
...
public final class Files {
public static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor) throws IOException {
//创建文件树
try (FileTreeWalker walker = new FileTreeWalker(options, maxDepth)) {
FileTreeWalker.Event ev = walker.walk(start);
//循环遍历文件树
do {
FileVisitResult result;
// 根据文件类型执行不同的访问操作
switch (ev.type()) {
case ENTRY :
IOException ioe = ev.ioeException();
if (ioe == null) {
assert ev.attributes() != null;
//具体的访问执行语句
result = visitor.visitFile(ev.file(), ev.attributes());
} else {
result = visitor.visitFileFailed(ev.file(), ioe);
}
break;
case START_DIRECTORY :
result = visitor.preVisitDirectory(ev.file(), ev.attributes());
if (result == FileVisitResult.SKIP_SUBTREE ||
result == FileVisitResult.SKIP_SIBLINGS)
walker.pop();
break;
case END_DIRECTORY :
result = visitor.postVisitDirectory(ev.file(), ev.ioeException());
if (result == FileVisitResult.SKIP_SIBLINGS)
result = FileVisitResult.CONTINUE;
break;
default :
throw new AssertionError("Should not get here");
}
if (Objects.requireNonNull(result) != FileVisitResult.CONTINUE) {
if (result == FileVisitResult.TERMINATE) {
break;
} else if (result == FileVisitResult.SKIP_SIBLINGS) {
walker.skipRemainingSiblings();
}
}
ev = walker.next();
} while (ev != null);
}
return start;
}
}
方法FileTreeWalker.Event.file()
中所获取的对象file
即为具体元素(ConcreteElement)角色,如类sun.nio.fs.WindowsPath
。
class FileTreeWalker implements Closeable {
...
static class Event {
private final EventType type;
private final Path file;
...
Path file() {
return file;
}
}
}
package sun.nio.fs;
...
class WindowsPath extends AbstractPath {
}
而接口java.nio.file.Path
本身即为这些元素的抽象元素(Element)角色。
package java.nio.file;
...
public interface Path
extends Comparable<Path>, Iterable<Path>, Watchable
{
FileSystem getFileSystem();
...
}
引用一句敬告:
访问者模式是把双刃剑,只有当你真正需要它的时候,才考虑使用它。有很多的程序员为了展示自己的面向对象的能力或是沉迷于模式当中,往往会误用这个模式,所以一定要好好理解它的实用性。必须做到使用一种模式是因为了解它的优点,不使用一种模式是因为了解它的弊端;而不是使用一种模式是因为不了解它的弊端,不使用一种模式是因为不了解它的优点。
————————————————
版权声明:本文为CSDN博主「独醉F」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43622131/article/details/107029708
访问者模式是把双刃剑,只有当你真正需要它的时候,才考虑使用它。有很多的程序员为了展示自己的面向对象的能力或是沉迷于模式当中,往往会误用这个模式,所以一定要好好理解它的实用性。必须做到使用一种模式是因为了解它的优点,不使用一种模式是因为了解它的弊端;而不是使用一种模式是因为不了解它的弊端,不使用一种模式是因为不了解它的优点。
————————————————
版权声明:本文为CSDN博主「独醉F」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43622131/article/details/107029708