Grensesnitt og klassearv

Etter man er kommet i gang med bruk av klasser i hvilket som helst objektorientert språk, er det helt naturlig å fortsette med grensesnitt.

Klasser brukes når man skal lage noe (altså et objekt), mens grensesnitt er passende når det er snakk om å holde på med noe (altså en aktivitet).

Eksempel på grensesnitt for biler, motorsykler, o.s.v.:

public interface IKjørbar
{
   void gass();
   void brems();
   void sving();
}

Dette er et enkelt grensesnitt for alt som kan kjøres. Gassing, bremsing, og svinging er da opplagt funksjonalitet man trenger.

Grensesnitt har mye til felles med abstrakte klasser:

public abstract class Kjøretøy
{
}

Men bruker man abstrakte klasser i stedet for grensesnitt kan man fort få problemer. Et problem er at man må arve fra klassen for å bruke den og i C# kan man kun arve fra èn klasse.

Derfor er det bedre å bruke den abstrakte klassen til noe annet:

public abstract class Kjøretøy : IKjørbar, IComparable, ICloneable
{
   public int Årsmodell
   {
      get
      {
         return årsmodell;
      }
   }
   private int årsmodell;

   public string Kubikk
   {
      get
      {
         return kubikk;
      }
   }
   private string kubikk;

   public string Navn
   {
      get
      {
         return navn;
      }
   }
   private string navn;

   public int AntallHjul
   {
      get
      {
         return antallHjul;
      }
   }
   private int antallHjul;

   public string Farge
   {
      get
      {
         return farge;
      }
   }
   private string farge;

   public int Hestekrefter
   {
      get
      {
         return hestekrefter;
      }
   }
   private int hestekrefter;

   public Kjøretøy(int årsmodell, string kubikk, string navn, int antallHjul, string farge, int hestekrefter)
   {
      this.årsmodell = årsmodell;
      this.kubikk = kubikk;
      this.navn = navn;
      this.antallHjul = antallHjul;
      this.farge = farge;
      this.hestekrefter = hestekrefter;
   }

   public int CompareTo(object objekt)
   {
      Kjøretøy kjøretøy = (Kjøretøy) objekt;

      if (årsmodell < kjøretøy.Årsmodell)
      {
         return -1;
      }
      else if (årsmodell == kjøretøy.Årsmodell)
      {
         if (navn.CompareTo(kjøretøy.Navn) < 0)
         {
            return -1;
         }
         else if (navn.CompareTo(kjøretøy.Navn) == 0)
         {
            if (antallHjul < kjøretøy.AntallHjul)
            {
               return -1;
            }
            else if (antallHjul == kjøretøy.AntallHjul)
            {
               if (hestekrefter < kjøretøy.Hestekrefter)
               {
                  return -1;
               }
               else if (hestekrefter == kjøretøy.Hestekrefter)
               {
                  return 0;
               }
               else
               {
                  return 1;
               }
            }
            else
            {
               return 1;
            }
         }
         else
         {
            return 1;
         }
      }
      else
      {
         return 1;
      }
   }

   public abstract object Clone();
   public abstract void gass();
   public abstract void brems();
   public abstract void sving();
}

Her er mesteparten av det som er relevant for alle kjøretøy tatt med.

Og siden IComparable og ICloneable er tatt med som grensesnitt som skal støttes av denne klassen er det nå lett å sammenligne forskjellige kjøretøy samt ta kopier.

For å få bruk for det som er i denne klassen må man arve fra den. To eksempler på mer spesialiserte klasser:

public class Bil : Kjøretøy
{
   public DateTime FristEUKontroll
   {
      get
      {
         return fristEUKontroll;
      }
   }
   private DateTime fristEUKontroll;

   public Bil(int årsmodell, string kubikk, string navn, int antallHjul, string farge, int hestekrefter, DateTime fristEUKontroll) : base(årsmodell, kubikk, navn, antallHjul, farge, hestekrefter)
   {
      this.fristEUKontroll = fristEUKontroll;
   }

   override public object Clone()
   {
      return new Bil(Årsmodell, Kubikk, Navn, AntallHjul, Farge, Hestekrefter, fristEUKontroll);
   }

   override public string ToString()
   {
      return
         Årsmodell + " " + Navn + ", " +
         Kubikk + ", " +
         AntallHjul + " hjul, " +
         Farge + ", " +
         Hestekrefter + " HK, " +
         "EU-OK til og med " + fristEUKontroll.Day + "." + fristEUKontroll.Month + "." + fristEUKontroll.Year;
   }

   public override void gass()
   {
      Console.WriteLine("Bilen gasser.");
   }

   public override void brems()
   {
      Console.WriteLine("Bilen bremser.");
   }

   public override void sving()
   {
      Console.WriteLine("Bilen svinger.");
   }
}
public class Motorsykkel : Kjøretøy
{
   bool Kjede
   {
      get
      {
         return kjede;
      }
   }
   private bool kjede;

   public Motorsykkel(int årsmodell, string kubikk, string navn, int antallHjul, string farge, int hestekrefter, bool kjede) : base(årsmodell, kubikk, navn, antallHjul, farge, hestekrefter)
   {
      this.kjede = kjede;
   }

   override public object Clone()
   {
      return new Motorsykkel(Årsmodell, Kubikk, Navn, AntallHjul, Farge, Hestekrefter, kjede);
   }

   override public string ToString()
   {
      return
         Årsmodell + " " + Navn + ", " +
         Kubikk + ", " +
         AntallHjul + " hjul, " +
         Farge + ", " +
         Hestekrefter + " HK, " +
         (kjede ? "har kjede" : "har ikke kjede");
   }

   public override void gass()
   {
      Console.WriteLine("Motorsykkelen gasser.");
   }

   public override void brems()
   {
      Console.WriteLine("Motorsykkelen bremser.");
   }

   public override void sving()
   {
      Console.WriteLine("Motorsyklisten lener seg til en side.");
   }
}

Det er selvsagt alltid rom for forbedringer. Kanskje vil man spesialisere klassen for motorsykler enda mer i tilfelle man skal registrere sykler som har sidevogn.

For å teste grensesnittet samt klassene kan man kjøre følgende:

class Program
{
   private static List<IKjørbar> mineKjøretøy;
 
   static void Main(string[] args)
   {
      // Opprett kjøretøy
      mineKjøretøy = new List<IKjørbar>();
      mineKjøretøy.Add(new Motorsykkel(1995, "600 F", "Suzuki GSX", 2, "svart/oransje", 86, true));
      mineKjøretøy.Add(new Bil(1993, "1.3 XLi", "Toyota Corolla", 4, "hvit", 86, new DateTime(2017, 10, 31)) );

      // Sjekk at sortering virker
      listKjøretøy("Før sortering:");
      mineKjøretøy.Sort();
      listKjøretøy("Etter sortering:");

      // Sjekk at grensesnittmetoder virker
      testKjøretøy("Tester kjøretøyene:");

      Console.ReadLine();
   }

   private static void listKjøretøy(string melding)
   {
      Console.WriteLine(melding);

      for (int k = 0; k < mineKjøretøy.Count; k++)
      {
         Console.WriteLine(mineKjøretøy[k]);
      }

      Console.WriteLine();
   }

   private static void testKjøretøy(string melding)
   {
      Console.WriteLine(melding);

      for (int k = 0; k < mineKjøretøy.Count; k++)
      {
         mineKjøretøy[k].gass();
         mineKjøretøy[k].sving();
         mineKjøretøy[k].brems();
      }

      Console.WriteLine();
   }
}

.. som da gir:

Alt ser ut til å være i orden. Sorteringen virker, og metodene fra grensesnittet gir tilpasset innhold avhengig av om det er bil eller motorsykkel.

TBC

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *