« September 2004 | Main | February 2005 »

Strategy Pattern

Taking Alastair Patrick’s example of applying patterns to game making I present the strategy pattern. The strategy pattern encapsulates behavior allowing flexibility and object composition. This means a group of classes (as ships) can have some behavior in common, but also room for different flavors without having a lot of overrides and duplicate code. Here’s an example in code. Note, I’ve even written it so the behavior can be changed at runtime!

public interface AttackBehavior
{
    void Attack();
}
public class AttackWithNoWeapon : AttackBehavior
{
    public AttackWithNoWeapon()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public void Attack()
    {
        Console.Out.WriteLine( "No weapon to attack with." );
    }
}
public class AttackWithRedLaser : AttackBehavior
{
    public AttackWithRedLaser()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public void Attack()
    {
        Console.Out.WriteLine( "Firing red lasers." );
    }
}
public class AttackWithReinforcedIceProjectile : AttackBehavior
{
    public AttackWithReinforcedIceProjectile()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public void Attack()
    {
        Console.Out.WriteLine( "Firing Reinforced Ice Projectiles " );
    }
}
public interface FlyBehavior
{
    void Fly();
}
public class FlyWithIonEngines : FlyBehavior
{
    public FlyWithIonEngines()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public void Fly()
    {
        Console.Out.WriteLine( "Moved 4 parsecs." );
    }
}
public class FlyWithNoEngines : FlyBehavior
{
    public FlyWithNoEngines()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public void Fly()
    {
        Console.Out.WriteLine( "Movement determined by gravity and inertia." );
    }
}
public class FlyWithSolarSails : FlyBehavior
{
    public FlyWithSolarSails()
    {
        //
        // TODO: Add constructor logic here
        //
    }
    public void Fly()
    {
        Console.Out.WriteLine( "Low Solar Wind, move .5 parsecs." );
    }
}
public abstract class Ship
{
    protected FlyBehavior flyBehavior = null;
    protected AttackBehavior attackBehavior = null;

    public Ship()
    {
    }

    public abstract void Display();

    public void PerformFly()
    {
        flyBehavior.Fly();
    }
    public void PerformAttack()
    {
        attackBehavior.Attack();
    }
    public void Repair()
    {
        Console.Out.WriteLine( "Repair teams dispatched, 10% improvement." );
    }
    public void SetFlyBehavior( FlyBehavior fb )
    {
        flyBehavior = fb;
    }
    public void SetAttackBehavior( AttackBehavior ab )
    {
        attackBehavior = ab;
    }
}
public class TerranMarkI : Ship
{
    public TerranMarkI()
    {
        flyBehavior = new FlyWithIonEngines();
        attackBehavior = new AttackWithRedLaser();
    }

    public override void Display()
    {
        Console.Out.WriteLine( "This is a Terran Mark I ship." );
    }

}
public class NeiwckronMkI : Ship
{
    public NeiwckronMkI()
    {
        SetAttackBehavior( new AttackWithNoWeapon() );
        SetFlyBehavior( new FlyWithIonEngines() );
    }
    public override void Display()
    {
        Console.Out.WriteLine( "A Neiwckron Mark I ship." );
    }

}
public class TheMain
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Ship s1 = new TerranMarkI();
        s1.PerformAttack();
        s1.PerformFly();

        Ship s2 = new NeiwckronMkI();
        s2.PerformFly();
        s2.SetFlyBehavior( new FlyWithSolarSails() );
        s2.PerformFly();
    }
}

Object Cloning Using Reflection

Today on Alastair Patrick’s blog he shared a way to use reflection to clone objects which I found really interesting. I took the liberty of expanding the example a little so I could better understand the effects deep and shallow cloning had. I also replaced his main() with a NUnit test.

using System;
using System.Reflection;
using NUnit.Framework;

public class DeepCopyAttribute : Attribute
{
}
public class PrototypeCloner
{   
    public object Clone( object prototype )
    {
        object clone = CreateNewInstance( prototype.GetType() );

        CopyPropertyValuesFromPrototype( clone, prototype );

        return clone;
    }
    private object CreateNewInstance(Type type)
    {
        ConstructorInfo defaultConstructor = type.GetConstructor( new Type[0] );

        return defaultConstructor.Invoke(new object[0]);
    }   
    private bool IsPropertyDeepCopied( PropertyInfo property )
    {   
        if( property.GetCustomAttributes( typeof(DeepCopyAttribute), true).Length
            != 0 )
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    private void CopyPropertyValuesFromPrototype(object clone, object prototype)
    {
        foreach (PropertyInfo property in prototype.GetType().GetProperties())
        {
            if (IsPropertyDeepCopied(property))
            {
                object prototypeProperty = property.GetValue(prototype, null);
                object cloneProperty = Clone(prototypeProperty);
                property.SetValue(clone, cloneProperty, null);
            }
            else
            {
                property.SetValue(clone, property.GetValue(prototype, null), null);
            }
        }
    }
}
public abstract class Weapon
{
}
public class LaserDisruptor: Weapon
{
    public float RateOfFire
    {
        get
        {
            return rateOfFire;
        }
        set
        {
            rateOfFire = value;
        }
    }
    private float rateOfFire = 1000;
}
public abstract class Entity {}
public class SpaceMonkey: Entity
{
    public float Speed
    {
        get
        {
            return speed;
        }
        set
        {
            speed = value;
        }
    }
    private float speed = 5;

    private Weapon deepCopyWeapon = new LaserDisruptor();
    [DeepCopyAttribute()]
    public Weapon DeepCopyWeapon
    {
        get
        {
            return deepCopyWeapon;
        }
        set
        {
            deepCopyWeapon = value;
        }
    }
    private Weapon shallowCopyWeapon = new LaserDisruptor();
    public Weapon ShallowCopyWeapon
    {
        get
        {
            return shallowCopyWeapon;
        }
        set
        {
            shallowCopyWeapon = value;
        }
    }
}
[TestFixture]
public class PrototypeClonerTests
{
    [Test]
    public void Clone()
    {
        SpaceMonkey prototypeObject = new SpaceMonkey();

        PrototypeCloner cloner = new PrototypeCloner();
        Entity clonedObject = null;
        clonedObject = (Entity)cloner.Clone(prototypeObject);

        SpaceMonkey clonedMonkey = (SpaceMonkey)clonedObject;

        // Testing for Equality
            
        Assert.AreEqual( prototypeObject.Speed, clonedMonkey.Speed, "Fail1" );

        // -- This is false because it is a Deep Copy property and this class
        // doesn't override the Equals or == operator so the Assert's
        // AreSame and AreEqual tests have the identical result
        Assert.IsFalse(
            prototypeObject.DeepCopyWeapon  == clonedMonkey.DeepCopyWeapon,
            "Fail2" );

        Assert.AreEqual(
            ((LaserDisruptor)prototypeObject.DeepCopyWeapon).RateOfFire,
            ((LaserDisruptor)clonedMonkey.DeepCopyWeapon).RateOfFire, "Fail3" );

        // -- Are equal, as in the same object because it is the Shallow Copy
        Assert.AreEqual( prototypeObject.ShallowCopyWeapon,
            clonedMonkey.ShallowCopyWeapon,
            "Fail4" );

        Assert.AreEqual(
            ((LaserDisruptor)prototypeObject.ShallowCopyWeapon).RateOfFire,
            ((LaserDisruptor)clonedMonkey.ShallowCopyWeapon).RateOfFire, "Fail5" );
            
        // Testing for Same
        // -- Only the DeepCopyAttribute properties should be the same

        // -- Not same because it is not a referance object (float)
        Assert.IsFalse(
            object.ReferenceEquals( prototypeObject.Speed, clonedMonkey.Speed ),
            "Fail6"  );

        // -- Not same because it is a DeepCopyAttribute
        Assert.IsFalse(
            object.ReferenceEquals( prototypeObject.DeepCopyWeapon,
            clonedMonkey.DeepCopyWeapon),
            "Fail7" );

        // -- Not same because it is not a referance object (float)
        Assert.IsFalse( object.ReferenceEquals(
            ((LaserDisruptor)prototypeObject.DeepCopyWeapon).RateOfFire,
            ((LaserDisruptor)clonedMonkey.DeepCopyWeapon).RateOfFire), "Fail8"  );

        // -- The only referance object that isn't a deep copy Are Same
        Assert.AreSame( prototypeObject.ShallowCopyWeapon,
            clonedMonkey.ShallowCopyWeapon,
            "Fail9" );

        // -- Not same because it is not a referance object (float)
        Assert.IsFalse( object.ReferenceEquals(
            ((LaserDisruptor)prototypeObject.ShallowCopyWeapon).RateOfFire,
            ((LaserDisruptor)clonedMonkey.ShallowCopyWeapon).RateOfFire),
            "Fail10"  );
    }
}