Visitor Examples:

Java
Java
C#
C#
▸ Visitor Quick Review

A battle of the Legends

To demonstrate the visitor pattern in C#, we're going to take a little spin on the regular use of the pattern.

We're retelling the story of the Greek Hero Heracles, as he finds himself facing off against the seven-headed Hydra, and Cerberus, the guardian of the underworld.

To help simulate the conflict, we'll be creating an attack-type matrix using double-dispatch. This allows us to easily see how effective attacks from two different opponents are. For instance, we usually associate water as being effective against a fire; in our simulated conflict, we can then say that the Hydra might have an edge over Cerberus for those same reasons. This is a popular game mechanic that you might recognize as being similar to the <a href="https://pokemondb.net/type" style="color:inherit;" target="_blank">move effectiveness chart</a> from the Pokemon video games.

Visitor Pattern:

// In this example, we'll be doing things a little differently:
// Our base class with be both the visitor and the visitee!
// Additionally, we'll be implementing it as an abstract class
// although, you should take great care when using an abstract class for the visitor pattern!

public abstract class ElementalType
{
   protected const float EFFECTIVE = 2.0f;
   protected const float NOT_EFFECTIVE = 0.25f;

   // Our base accept visitation method:
   public abstract float Attack(ElementalType defenceType);

   // ===================
   // Elemental types:
   // ===================
   // The methods are virtual, which makes them optional.
   // This way, implementing classes only need to have methods for special values:
   public virtual float GetAttackMultiplier(WaterType attackType) { return 1.0f; }
   public virtual float GetAttackMultiplier(FireType attackType) { return 1.0f; }
   public virtual float GetAttackMultiplier(NormalType attackType) { return 1.0f; }

}

// Our water class:
public class WaterType: ElementalType
{
   // Base accept method:
   public override float Attack(ElementalType defenceType)
   {
       return defenceType.GetAttackMultiplier(this);
   }

   // We want to override Fire, Water, and Normal types:
   public override float GetAttackMultiplier(FireType attackType)
   {
       // A Fire move against water is not effective:
       return this.NOT_EFFECTIVE;
   }

   public override float GetAttackMultiplier(WaterType attackType)
   {
       // A water move against water is not effective:
       return this.NOT_EFFECTIVE;
   }

   public override float GetAttackMultiplier(NormalType attackType)
   {
       // A normal move against water is effective:
       return this.EFFECTIVE;
   }
}

public class FireType: ElementalType
{
   // Base accept method:
   public override float Attack(ElementalType defenceType)
   {
       return defenceType.GetAttackMultiplier(this);
   }

   // We want to override Fire and Water types:
   public override float GetAttackMultiplier(FireType attackType)
   {
       // A Fire move against fire is not effective:
       return this.NOT_EFFECTIVE;
   }

   public override float GetAttackMultiplier(WaterType attackType)
   {
       // A water move against fire is effective:
       return this.EFFECTIVE;
   }
}

public class NormalType: ElementalType
{
   // Base accept method:
   public override float Attack(ElementalType defenceType)
   {
       return defenceType.GetAttackMultiplier(this);
   }

   // We want to override Fire and Water types:
   public override float GetAttackMultiplier(FireType attackType)
   {
       // A Fire move against normal is effective:
       return this.EFFECTIVE;
   }

   public override float GetAttackMultiplier(WaterType attackType)
   {
       // A water move against normal is not effective:
       return this.NOT_EFFECTIVE;
   }
}

Client Code:

using System;

public class Solution
{
   public static void main(string[] args)
   {
       // Create our combatants:
       ElementalType cerberus = new FireType();
       ElementalType hydra = new WaterType();
       ElementalType heracles = new NormalType();

       // Cerberus attacks Hydra:
       float multiplier = cerberus.Attack(hydra);
       Console.WriteLine("Cerberus attacks Hydra and deals " + multiplier + " x damage!");

       // Hydra attacks back:
       multiplier = hydra.Attack(cerberus);
       Console.WriteLine("Hydra attacks back and deals " + multiplier + " x damage!");

       // Heracles attacks Hydra:
       multiplier = heracles.Attack(hydra);
       Console.WriteLine("Heracles attacks Hydra and deals " + multiplier + " x damage!");

       // Cerberus attacks Heracles:
       multiplier = cerberus.Attack(heracles);
       Console.WriteLine("Cerberus attacks Heracles and deals " + multiplier + " x damage!");

       Console.WriteLine("The battle has ended!");
   }
}

Challenge:

  • Using this template, expand on the attack types to include more of the Greek legends, and even go wild and implement a health system to simulate the conflict to the end of the line!

Find any bugs in the code? let us know!