LektionsBloggen: Delegater och Interface

Lektionens datum: 1/7 2008
Lektionens rubrik: Repetition, Arv, Polymorfism, Delegates And Events, MDI (2/2)
Lektionsbloggens fokus: Delegater och Interface

Dagens lektion handlade om Delegater och Interface. Jag tänkte illustrera dessa genom att utgå från gårdagens exempel.

Dagens blogg blir bildlös då jag sitter i skolan och inte har tillgång till ftp-kontot där jag laddar upp bilderna. Men det blir desto fler kodsnuttar istället, håll till godo! :-)

Filhantering
Om du vill hänga med, ladda hem "the LektionsBloggen Solution", packa upp filen och gör en kopia av mappen 080630_MDI_InheritedForms_Polymorfism. Döp om den kopierade mappen och .csproj-filen så att de heter samma sak, exempelvis "delegateSample". Dubbelklicka sedan på LektionsBloggen.sln, högerklicka på solution-nivån i Solution Explorer och välj Add -> Existing Project..., browsa dig fram till och dubbeklicka på csproj-filen. Högerklicka sedan på detta projekt i Solution Explorer och välj Set as startup project. Sådär, då kan vi köra igång.

Städa bort polymorfismen
För att få ett renodlat exempel kring delegater, tar vi nu bort metoden MakeSound i FrmBase och FrmCat. När du gjort detta, gör en kopia av detta projekt och döp om kopian på samma sätt som ovan till InterfaceSample, jag kommer att använda det senare. Ta nu bort MakeSound även i FrmDog. Ta också bort all kod inne i Timer_Tick1-metoden, vi kommer att lägga till kod där senare.

Delegater
En delegat är en slags datatyp vars innehåll kan peka mot en eller flera metoder. För att förbereda metoder som vår delegat senare ska peka till, implementera följande metod i frmCat:

        public void MakeCatSound(int numberOfTimes)
        {
            Sound += "/ ";
            for (int i = 0; i < numberOfTimes; i++)
            {
                Sound += "Mjau ";
            }
        }

Gör sedan en exakt likadan metod i frmDog men låt den heta MakeDogSound och ändra strängen till Woof . Öppna sedan FrmMainWindow.cs, vårt MDI-fönster. Deklarera en delegat enligt nedan (rad 1) utanför klassen och deklarera en instans av delegaten inuti klassen (rad 5):

    public delegate void MakeSound(int numberOfTimes);

    public partial class FrmMainWindow : Form
    {
        public MakeSound OnMakeSound;

        /.../
    }

Delegatdeklarationen (rad 1) innebär att metoder som ska kopplas till denna delegat måste returnera void och parameterlistan måste bestå av en int. Vilket ju stämmer på metoderna vi nyss la till i frmCat och frmDog. Observera att delegatdeklarationen skulle kunne jämföras med en deklaration av en typ (klass, enum etc.), den har alltså i i sig ingenting att göra just med klassen FrmMainWindow. På rad 5 skapar vi en referens av delegatdatatypen MakeSound som vi kallar OnMakeSound. Denna referens är alltså en medlem i klassen FrmMainWindow. Leta nu upp metoden newCatToolStripMenuItem_Click och lägg till följande rad sist:

OnMakeSound += new MakeSound(cat.MakeCatSound);

Det här är den metod som körs när man väljer menyalternativet "Cat" i File-menyn. På raden ovan lägger vi till att cat-klassens metod MakeCatSound ska köras när OnMakeSound anropas (det anropet återkommer vi till senare). En delegat tilldelas alltid med +=-operatorn för att inte andra metoder som kanske redan tilldelats till delegaten ska bli överskrivna/borttagna. Lägg till en likadan rad i metoden newDogToolStripMenuItem_Click som triggas när man väljer menyalternativet "Dog". Ändra dock cat.MakeCatSound till dog.MakeDogSound. Nu ska vi anropa delegaten OnMakeSound, vi gör det enkelt för oss genom att använda en timer som triggar sig själv varje sekund. Implementera Timer_Tick1 enligt följande;

        Random r = new Random(DateTime.Now.Millisecond);
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (OnMakeSound != null)
            {
                //Slumpa ett tal mellan 1 och 5
                int randomNumber = r.Next(1, 6);
                
                //Anropa de metoder som delegaten pekar till
                OnMakeSound(randomNumber);
            }
        }

Innan man anropar en delegat måste man kolla så att den inte är null, det är ju nämligen helt valfritt för andra klasser om de vill lyssna på en händelse eller inte och i just detta fall kan det vara så att användaren inte öppnat några fönster och i så fall pekar delegaten inte på några metoder. Därefter slumpar vi fram ett tal mellan ett och fem, detta kommer att avgöra hur många gånger varje djurljud skrivs ut. Till sist kommer vi till pudelns kärna, anropet till delegaten; Det ser ut som ett helt vanligt metodanrop där metodnamnet är namnet på den referens vi skapat till delegatdatatypen, och om delegaten tar några argument är det här vi skickar in dom, annars blir det en tom parentes. Det som händer när delegaten anropas nu är alltså att alla instanser av FrmCat och FrmDog anropar sin respektive "make sound"-metod och text skrivs ut i fönstren.

Kombinera delegater och polymorfism
Experimentförslag: Jag kommer inte gå igenom detta så detaljerat, men om du ändrar "make sound"-metoderna i FrmCat och FrmDog så att de heter MakeSound samt deklareras med override, samt implementerar en generell MakeSound-metod i FrmBase som deklareras med virtual, så kan du i metoderna för menyklicken deklarera child-referensen som en FrmBase. Det kommer göra att delegaten anropar MakeSound i FrmBase men eftersom den är virtual och har implementationer även i FrmCat och FrmDog så är det de metoderna som kommer att köras. Se gårdagens exempel kring virtual och override

Interface
Nu utgår vi alltså från projektet interfaceSample där MakeSound-metoden endast finns kvar i FrmDog. Anledningen till detta är ett litet trick man kan göra för att skapa ett interface. Öppna FrmDog i kodläge och ta bort override på deklarationen av MakeSound (Det finns ju ingen virtual metod i basklassen längre). Nu till interface-tricket; högerklicka i textytan och välj Refactor -> Extract interface. En dialogruta öppnas där du får namnge interfacet och välja vilka medlemmar i den aktuella klassen som ska kopplas till interfacet. Döp interfacet till IAnimalWithSound och kryssa för MakeSound. Nu skapas helt automatiskt filen IAnimalWithSound.cs med följande innehåll:

using System;
namespace MDI
{
    interface IAnimalWithSound
    {
        void MakeSound();
    }
}

Vi har alltså skapat ett interface som kräver att klasser som implementerar detta interface innehåller en metod som heter MakeSound, returnerar void och inte har några parametrar. Kolla på klassdeklarationen i FrmDog, där har implementationen av MakeSound redan lagts till:

public partial class FrmDog : MDI.FrmBase, MDI.IAnimalWithSound

(Du kan ta bort MDI. på både klassen som ärvs och interfacet som implementeras) Gå nu till FrmCat och skriv in , IAnimalWithSound sist i klassdeklarationen. En liten markering visas under I:et, klicka på den och välj Implement interface 'IAnimalWithSound'. Följande kod genereras ut i filen:

        #region IAnimalWithSound Members

        public void MakeSound()
        {
            throw new NotImplementedException();
        }

        #endregion

Ändra metoden så att den ser ut så här:

        public void MakeSound()
        {
            Sound += "Mjau ";
        }

Nu kommer vi kunna behandla våra fönster oavsett om de är av typen FrmCat eller FrmDog som en IAnimalWithSound. Implementera Timer1_Tick-metoden i FrmMainWindow enligt följande;

        Random r = new Random(DateTime.Now.Millisecond);
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (this.MdiChildren.Length > 0)
            {
                //Slumpa ett tal mellan 0 och antal öppna fönster
                int randomNumber = r.Next(0, this.MdiChildren.Length);

                IAnimalWithSound randomWindow = (IAnimalWithSound)this.MdiChildren[randomNumber];
                randomWindow.MakeSound();
            }
        }

Eftersom vi vet att alla fönster i MdiChildren implementerar IAnimalWithSound kan vi typa om det till ett IAnimalWithSound. Därefter kan vi anropa MakeSound eftersom vi vet att alla klasser som implementerar IAnimalWithSound har en sån metod. Och ja, det är väl ungefär det som är pudelns kärna när man använder sig av interface.

Tankar kring skillnader mellan polymorfism, interface och delegater
Jag är inte helt på det klara med när man egentligen ska använda vad, men en skillnad man kan konstatera är ju att delegatvarianten aldrig ställer några krav på vad metoder den triggar ska heta, den ställer inte heller några krav på att nån överhuvudtaget måste "lyssna" på den, så den känns lite "lösare" på sätt och vis. När det gäller polymorfism så är ju det endast aktuellt om det finns en arvsrelation mellan klasser, så har man inte det men ändå behöver behandla objekt av olika klasser på ett generellt sätt så känns interface som the way to go. När det gäller just mitt exempel känner jag polymorfismens override och virtual känns mest rätt, just för att jag har en arvsrelation mellan klasserna. Jag lär återkomma kring dessa funderingar här på bloggen.

I morgon står det "Generics, anonyma funktioner (1/3)" på schemat. Lektionsbloggen återkommer kring detta!

Ladda hem "the LektionsBloggen Solution"

1 comment :