【设计模式与游戏完美开发】访问者模式

本文介绍了访问者模式,以统计敌人防御平均值和士兵攻击平均值为例,说明一般做法会改动原有类、违背开闭原则且提高耦合度。而使用访问者模式可避免这些问题,还可通过接口抽象化进一步降低耦合,最后给出更复杂例子及思考问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GoF:“定义一个能够在一个对象结构中对于所有元素执行的操作。访问者让你可以定义一个新的操作,而不必更改到被操作元素的类接口”(一般我打出GoF就说明我不是很理解)

简单来说,访问者是一个考虑了所有“被访问者”可能会需要的接口(功能),被访问者需要放入访问者的它所需的接口中进行处理

处理的问题如下:

假设有Enemy敌人类 和 Solider士兵类 它们均继承于Character基类。

现在需求是统计每一个敌人的防御力求平均值 和 统计每一个士兵的攻击力求平均值 至于求出的数据放哪可以先不管。

一般做法是在士兵类Solider 和 敌人类Enemy中 分别写一个方法来进行处理对应的问题,这样就会改动了2个原有类,违背了开闭原则,而且可能还会提高耦合度。假设又有一个类似的需求出现,那还需要再继续增加原有类接口进行处理!最终原有类会越来越庞大变得难以维护。

用访问者模式就可以避免这种情况,对照这个需求创建一个访问者类VistorA,内部有两个方法如下:

VisitSolider(List<Solider> soliderList){  ...   }

VisitEnemy(List<Enemy> enemyList){  ...  }

调用时: VistorA  va = new VistorA();

va.VisitSolider( GetSoliderList() );//GetSoliderList()获取所有士兵

va.VisitEnemy( GetEnemyList() );//GetEnemyList()获取所有敌人

假设又有一个类似的需求,需要访问原有类Enemy或Solider ,而且也同样是访问所有士兵和敌人,此时你就可以再创一个VistorB来处理这个需求,而VistorA和VistorB是十分类似的,为此我们可以写一个接口Vistor来进行抽象化,每一个需求VistorX访问者都要继承于Vistor,让实现和调用分离开来,从而降低耦合(这就是面向接口编程思想)

变为如下:

Vistor vistor = new VistorA()

vistor.VistorSolider(soliderList)

vistor.VistorEnemy(enemyList)

假设要转为 新需求VistorB的方式,代码只需改动上面的VistorA变为VistorB就OK了!因为A和B都是实现Vistor接口的方法,其方法名和方法所需参数是一样的,这就保证了我们只需要改动一个地方就OK了,当然上面这种举例还是太浅了,等到具体遇到项目问题时,就会理解面向接口的好处(或者说是面向抽象的好处)


2020年6月22日02:16:56更新  比上方的例子稍微复杂点的例子,可以对比着2种的访问模式,都是适用于不同需求而言的,没有根本上的利弊之分,我认为这些操作都是具体和抽象之间的转化而已,下面例子是被访问者的方法调用交给访问者进行。

public interface IVistor
{
   void VisitA(BeVisitA beVisit); 
   void VisitB(BeVisitB beVisit); 
}

public class FirstVistor : IVistor
{
  //(4) [依赖具体调用] 执行被访问者A的具体方法
  public void VisitA(BeVisitA beVisit)
  {
     beVisit.Run();  
  }
  public void VisitB(BeVisitB beVisit)
  {
     beVisit.Sleep();  
  }
}
public class SecondVistor : IVistor
{
  //其实IVistor可以只写一个接口Visit(IBeVisit beVisit),
  //不过要写if判断和强转对应类型进行调用具体类方法。如 if(beVisit is BeVisitA){ ((BeVisitA)beVisit).Walk();  } 
  //这会消耗一定性能
  public void VisitA(BeVisitA beVisit)
  {
     beVisit.Walk();  
  }
  public void VisitB(BeVisitB beVisit)
  {
     beVisit.Look();  
  }
}

public interface IBeVisit
{
   void VisitedByWho(IVistor vistor);
}
public class BeVisitA : IBeVisit
{
   //实现接口方法,外部遍历调用IBeVisit接口时进入到此,从抽象变成具体调用,然后再变成抽象调用
   public void VisitedByWho(IVistor vistor)
   {
       //(3)[抽象调用] 访问者VisitA接口方法,传递被访问者this,由具体访问者进行访问this
       vistor.VisitA(this);
       //假设我直接在这里调用Run() 或 Walk()方法,访问者的意义就不存在了
       //访问者意义在于,选择执行哪个具体方法,比如FirstVistor选择执行Run,SecondVistor选择执行Walk,
        //当然你也可以弄个ThirdVistor执行Run()和Walk()。
       //很明显,如果你没有vistor辅助,你需要新增BeVisitA接口,破坏内部封闭原则。
   }
   public void Run()
   {
      //...
   }
   public void Walk()
   {
      //...
   }
}
public class BeVisitB : IBeVisit
{
   public void VisitedByWho(IVistor vistor)
   {
       vistor.VisitB(this);
   }
   public void Sleep()
   {
      //...
   }
   public void Look()
   {
      //...
   }
}

void Main()
{
    List<IBeVisit> list = new List<IBeVisit>();//(1)依赖倒置[抽象]
    list.Add(new BeVisitA());
    list.Add(new BeVisitB());
    
    IVistor vistor = new FirstVistor();
    foreach(var v in list)
    {
        //(2)[抽象调用]调用IBeVisit接口方法VisitedByWho,由Visitor访问者决定如何进行调用v的方法
        v.VisitedByWho(vistor);    
    }
}

上方例子注释(1)、(2)、(3)、(4)按顺序看,整个调用流程,抽象->(具体)->抽象->具体,  第二步的具体是指从调用IBeVisit接口方法VisitedByWho,程序转到具体的IBeVisit接口实现类进行,其他的应该都很好懂。

思考:将旧例子Enemy和Solider魔改,但依然保持一部分会如何,需求改变为访问所有角色(敌人和士兵)调用它们的共同角色接口DoSomething()。看看会出现啥问题。

public interface ICharacter
{
   void DoSomething();
}

public class Enemy : ICharacter
{
   public void DoSomething()
   {
       Fly();
   }

   public void Fly() 
   {
     //...
   }
}
public class Solider : ICharacter
{
   public void DoSomething()
   {
       Run();
   }

   public void Run() 
   {
     //...
   }
}

public interface IVistor
{
   void VisitAll(List<ICharacter> list);
}


public class FirstVistor : IVistor
{
   public void VisitAll(List<ICharacter> list)
   { 
       foreach(var v in list)
       {
           v.DoSomething();
           //写到这里时我突然发现,是的,这个接口无意义,vistor没有起到任何作用
           //除非这个访问者还进行了其他特别的操作 如:调用v.DoSomething之后,假设DoSomething
           //有返回值bool, 判断bool是否为false,为false则继续执行其他操作。如此一来就有了变化
       }
   }
}

void Main()
{
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值