描述
- 使数据结构和元素进行分离,增加元素,不影响数据结构
- 即,一个对象要操作多个元素,多个元素与操作者(访问者角色)分离,访问者定义具体元素角色聚合访问者。
角色
- 抽象访问者角色:定义访问元素的行为,参数就是访问者元素。要求元素类的个数不能改变(与具体元素角色个数一致)。
- 具体访问者角色:给出访问元素类的具体行为。
- 抽象元素角色:定义了一个接收访问者的方法,每个元素可被访问者访问。
- 具体元素角色:实现抽象元素角色,提供访问元素的具体实现。
- 对象结构角色:定义数据对象结构,可以理解为具有容器性质或者符合复合对象特效的角色,包含了一组元素,供访问者使用。
代码
public class Test {
public static void main(String[] args) {
// 把配套元素提取到一块,与操作对象去耦合
// 吃什么
Home home = new Home();
home.add(new Fruit());
// 让访问者去吃
home.action(new SomeonePerson());
// 配套元素2
Home home2 = new Home();
home2.add(new Fruit());
home2.add(new Vegetable());
// 访问者去吃
home2.action(new OtherPerson());
}
}
// 抽象访问类角色
interface Person {
// 吃水果
void eat(Fruit fruit);
// 吃蔬菜
void eat(Vegetable vegetable);
}
// 抽象元素角色,参数就是访问者对象
interface Food {
// 接收访问者访问
void receive(Person person);
}
// 具体元素角色,访问者具体对这个元素干啥
class Fruit implements Food {
@Override
public void receive(Person person) {
System.out.println("水果维生素");
// 实现双分派
person.eat(this);
}
}
// 具体元素角色,访问者具体对这个元素干啥
class Vegetable implements Food {
@Override
public void receive(Person person) {
System.out.println("蔬菜维生素");
person.eat(this);
}
}
// 具体抽象类对象
class SomeonePerson implements Person {
@Override
public void eat(Fruit fruit) {
System.out.println("某人在吃水果");
}
@Override
public void eat(Vegetable vegetable) {
System.out.println("某人在吃蔬菜");
}
}
// 具体抽象类对象
class OtherPerson implements Person {
@Override
public void eat(Fruit fruit) {
System.out.println("其它人在吃水果");
}
@Override
public void eat(Vegetable vegetable) {
System.out.println("其它人在吃蔬菜");
}
}
// 对象结构类
class Home {
// 集合对象
private List<Food> foodList = new ArrayList<>();
// 添加元素
public void add(Food food) {
foodList.add(food);
}
// 执行
public void action(Person person) {
foodList.forEach(food -> food.receive(person));
}
}
优点
- 扩展性好,可在不改变对象原有元素结构情况下,添加新元素,比如:添加一个主食元素类。
- 可复用好,通用的配套元素可给与多个访问者,对象结构类统一action(Person person)执行元素。
- 每个访问者的类,功能比较单一。
缺点
- 对象结构比较固定,每增加一个新的元素,都要在每一个抽象和具体访问者对象中添加操作方法。
- 违背依赖倒置原则,访问者类依赖了具体类,而不是抽象类。
使用场景
- 对象结构基本稳定,结构中的对象操作算法不相关或经常变动的程序。
扩展
分派
-
分派:
- Map map = new HashMap();
- map 是静态类型Map,实际类型HashMap。根据对象的类型而对方法进行的选择,就是分派。
-
分为两种:
- 静态分派:发送在编译时期,静态类型信息进行分派,比如方法重写(多态)。
class Test1{
public static void main(String[] args) {
A a = new B();
a.haha();
}
}
class A {
void haha() {
System.out.println("a");
}
}
class B extends A {
void haha() {
System.out.println("b");
}
}
- 动态分派:发送在运行时期,动态地置换掉某个方法,比如方法重载。
class Test1{
public static void main(String[] args) {
Test1 test1 = new Test1();
A a = new A();
A b = new B();
A c = new C();
test1.exc(a);
test1.exc(b);
test1.exc(c);
}
void exc(A a) {
System.out.println("a");
}
void exc(B b) {
System.out.println("b");
}
void exc(C c) {
System.out.println("c");
}
}
- 双分派:
- 在单分派的基础上(消息接收者的运行时区分),还要根据参数的运行时区分。
- 静态分派(重写)+ 动态分派(重载)
class Test1{
public static void main(String[] args) {
Test1 test1 = new Test1();
A a = new A();
A b = new B();
A c = new C();
a.re(test1);
b.re(test1);
c.re(test1);
}
void exc(A a) {
System.out.println("a");
}
void exc(B b) {
System.out.println("b");
}
void exc(C c) {
System.out.println("c");
}
}
class A {
void re(Test1 test1){
// 实现双分派(重载+重写)
test1.exc(this);
}
}
class B extends A {
void re(Test1 test1){
// 实现双分派(重载+重写)
test1.exc(this);
}
}
class C extends A {
void re(Test1 test1){
// 实现双分派(重载+重写)
test1.exc(this);
}
}