OOP – Polymorfism

Inledning

Ett begrepp som är starkt förankrat i del flesta kursplaner är just polymorfism. Detta är ett begrepp som finns inom objektorienterade språk men som kan te sig olika ut beroende på språk. Ordet ”polymorf” härstammar från grekiskan och betyder något i stil med ”mångformig”.

Polymorfa variabler

Detta gäller för variabler som är av referenstyp. Referenstyp innebär att variabeln i sig inte behöver innehålla något värde utan refererar till ett objekt eller en annan variabel. Motsatsen är så kallade variabler av värdetyp. Vanliga variabler av värdetyp är:

  • int
  • string
  • bool
  • char
  • float
  • double
  • Samt alla sammansatta värdetyper som skapas med nyckelordet struct.

Variabler av referenstyp är objekt av klasser som alla skapats med nyckelordet class. Till dessa ingår även alla vektorer. Observera alltså att om ni gör en vektor av någon vanlig värdetyp, t ex int, så är alltså denna vektor av referenstyp.

Vad spelar detta för roll då? Jo när du skickar en variabel av värdetyp mellan metoder, som t ex inparameter eller returparameter, så skickas den ”by value”. Dvs. värdet som variabeln har skickas, inte själva variabeln i sig. Med referensvariabler är det däremot så att det är en referens till ett och samma objekt som skickas runt mellan metoderna. Skulle jag ändra något i referensvariabeln så ändrar jag själva ”originalet”. Vi tar ett kort exempel:

Exempel med värdetyp

Exempel med värdetyp
struct Player
{
    public string name;
}
 
class Program
{
    static void ChangeName(Player p)
    {
        p.name = "Erik";
    }
 
    static void Main(string[] args)
    {
        Player p1 = new Player();
 
        p1.name = "Carl";
        ChangeName(p1);
        Console.WriteLine(p1.name); //skriver ut "Carl"
    }
}

Vårt objekt av Player är nu av värdetyp då den bygger på en struct. När objektet p1 skickas till metoden ChangeName så skapas det i samma ögonblick en kopia. Ändringarna gjorda i metoden påverkar inte objektet p1 på rad 19.

Exempel med referenstyp

Exempel med referenstyp
class Player
{
    public string name;
}
 
class Program
{
    static void ChangeName(Player p)
    {
        p.name = "Erik";
    }
 
    static void Main(string[] args)
    {
        Player p1 = new Player();
 
        p1.name = "Carl";
        ChangeName(p1);
        Console.WriteLine(p1.name); //skriver ut "Erik"
    }
}

Vårt objekt av Player är nu av referenstyp då den bygger på en class. Till metoden skickas nu en referens till objektet p1. Ändringarna gjorda i metoden påverkar nu objektet p1 på rad 19.

Statisk bindning

Hur fungerar då polymorfa variabler? Jo eftersom det finns en arvsmekanik inom OOP så innebär det att ett objekt kan vara av flera typer. Dels är objektet alltid av typen Object eftersom alla klasser i C# indirekt ärver från klassen Object. Klassen är kanske även uppbyggt av en eller flera basklasser och man kan då säga att objektet även är av dessa typer. Detta ger oss friheten att behandla objekt som om de vore av en viss klasstyp fastän de kanske i själva verket är objekt som är skapade från en subklass till klasstypen.

Vi provar med ett exempel:

Exempel på statisk bindning
using System;
using System.Collections.Generic;
using System.Text;
 
namespace Polymorfism1
{
    class Player
    {
        public void Attack()
        {
            Console.WriteLine("A Player attacks!");
        }
    }
 
    class Mage : Player
    {
        public void Attack()
        {
            Console.WriteLine("A Mage attacks with a fireball!");
        }    
    }
 
    class Warrior : Player
    {
        public void Attack()
        {
            Console.WriteLine("A Warrior attacks with a sword!");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            List<Player> players = new List<Player>();
 
            players.Add(new Player());
            players.Add(new Mage());
            players.Add(new Warrior());
 
            foreach (Player p in players)
            {
                p.Attack();
            }
        }
    }
}

Här har vi först en basklass, Player, och två subklasser, Mage och Warrior som bygger på basklassen. På rad 35 skapar vi en List som ska hålla reda på alla våra spelare. Denna lista lagrar objekt av typen Player. På rad 37-39 skapar vi objekt och lägger till dem i listan. Det går alltså bra att lagra en Mage och en Warrior i listan då dessa också är av typen Player.

När vi på rad 41 loop’ar igenom listan så kommer alla objekt i listan att behandlas som om de vore av typen Player. Resultatet blir:

bild

Vad hände egentligen med Attack-metoderna i subklasserna? Jo de körs inte då vi behandlar alla objekt som Player. Nu körs bara implementationen i basklassen, dvs. PlayerAttack.

Vi ska nu visa att objekten som ligger lagrade i listan players faktiskt är en Player, en Mage och en Warrior.

Dynamisk bindning

Statisk bindning är som i exemplet ovan att varje klass/subklass har en alldeles egen implementation av en metod. Vilken metod som ska köras bestäms egentligen redan i det ögonblick som programmet kompileras. I vårt exempel ovan kan man säga att beroende på om jag ser objektet som en Player eller som en Mage så kommer olika implementationer att köras.

Nu ska vi ändra lite och skapa mer dynamik. Vi kommer att markera vissa metoder som virtual och vissa som override.

Exempel dynamisk bindning
using System;
using System.Collections.Generic;
using System.Text;
 
namespace Polymorfism1
{
    class Player
    {
        public virtual void Attack()
        {
            Console.WriteLine("A Player attacks!");
        }
    }
 
    class Mage : Player
    {
        public override void Attack()
        {
            Console.WriteLine("A Mage attacks with a fireball!");
        }    
    }
 
    class Warrior : Player
    {
        public override void Attack()
        {
            Console.WriteLine("A Warrior attacks with a sword!");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            List<Player> players = new List<Player>();
 
            players.Add(new Player());
            players.Add(new Mage());
            players.Add(new Warrior());
 
            foreach (Player p in players)
            {
                p.Attack();
            }
        }
    }
}

Resultatet blir:

bild

Vad händer nu? Jo inte samma sak givetvis. Helt plötsligt körs den implementation av Attack som finns i respektive subklass. Skickar jag en Mage som en Player så körs ändå klassen Mages implementation av Attack! Skickar jag ett objekt som bara är en Player så körs fortfarande Player-klassens implementation.

Detta fungerar eftersom vi använder override på metoden Attack i subklasserna och på så sätt omdefinierar hur den metoden ska fungera. Det spelar sedan ingen roll om vi ser på objekten, rätt implementation kommer ändå att köras. Detta kallas för dynamisk bindning.

För att detta ska fungera så behöver vi deklarera metoden Attack i basklassen som virtual. Det betyder att vi tillåter subklasser att definiera om metoden med en egen betydelse.

Dynamisk bindning kallas även sen bindning. Statisk bindning kallas även tidig bindning. Namnen i sig är nästan mer komplicerade än själva betydelsen av dem.

Videogenomgång

Viktiga begrepp

  • polymorfism
  • referenstyp
  • värdetyp
  • Statisk bindning
  • dynamisk bindning
  • sen bindning
  • tidig bindning

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top