Objekti - dedovanje

Objekti - dedovanje

Avtor: Matija Lokar

Pogosto uporabljani postopki I

  • Imamo razred Ulomek

    • Denimo, da z ulomki zelo pogosto izvajamo določen postopek

      • npr. jim spremenimo predznak
  • Napišemo ustrezno metodo
  • Vedno, ko želimo ulomkom spreminjati predznak, moramo napisati to metodo

    • Naredimo iz nje knjižnico – "zaprimo v svoj razred"
  • Ampak to ni "objektno"

    • Znanje spreminjanja predznaka je lastno ulomku– objektu
    • Ulomek (objekt) naj bi znala spremniti predznak

      • Odzvati se metodi SpremeniPredznak :

        •  nekUlomek.SpremeniPredznak()
  • Imamo dostop do knjižnice z razredom Ulomek

    • Ni problem, dopišemo metodo
  • Popravljati dobro preizkušen razred?

    • ... Do not fix, until you are completely desperate ...
  • Kaj pa, če izvorne kode nimamo?

Dedovanje

  • "razširjanje" objektov
  • Specializacija

    • Vozilo

      • Motor je "specializacija" vozila, tudi kolo, avto
      • Imajo vse lastnosti vozila + (vsak zase) še nekatere njim lastne lastnosti
  • Naredimo načrt za razred boljših ulomkov

    • So ulomki!
    • Boljši: Znajo spremeniti predznak
  •  public class BoljsiUlomek : Ulomek 
  • Vsaka boljši ulomek "zna" vse, kar zna običajni ulomek iz razreda Ulomek in morda še kaj
  • Ima ista stanja/lastnosti

    • Morebiti tudi dodatna

Razred Ulomek

public class Ulomek {
    private int st;
    private int im;

    public Ulomek() {
      st = 1;
      im = 1;
    }
    public Ulomek(int s, int i) : this() {
      // s / i,
      // ce i == 0, s / 1
      this.NastaviStevec(s);
      this.NastaviImenovalec(i);
    }
    public void PovecajStevec(int x) {
      this.NastaviStevec(this.PovejStevec() + x);
    }
    public int PovejStevec() {
      return this.st;
    }
    public int PovejImenovalec() {
      return this.im;
    }
    public void NastaviStevec(int x) {
      this.st = x;
    }
    public void NastaviImenovalec(int y) {
      if (y != 0) {
          int ste = this.PovejStevec();
          if (ste > 0) {
              this.im = y;
          }else {
              this.im = -y;
              this.NastaviStevec(-1 * ste);
          }
      }
    }
    public Ulomek Pomnozi (Ulomek ul) {
      int st = this.PovejStevec() * ul.PovejStevec();
      int im = this.PovejImenovalec() * ul.PovejImenovalec();
      Ulomek rez = new Ulomek(st, im);
      return rez;
    }
}

Razred BoljsiUlomek

public class BoljsiUlomek : Ulomek {
  // naredimo nov razred "boljših ulomkov"
   public void SpremeniPredznak() {
      // Vsak boljši ulomek si zna
      // spremeniti predznak
      Ulomek pom = new Ulomek(-1, 1);
      this.Pomnozi(pom); // lahko tudi brez this     // metoda Pomnozi je podedovana iz razreda Ulomek
    }
}



  • metoda Pomnozi je podedovana iz razreda Ulomek

Razred BoljsiUlomek

public class BoljsiUlomek : Ulomek {
  // naredimo nov razred "boljših ulomkov"
   public void SpremeniPredznak2() {
      // Vsak boljši ulomek si zna
      // spremeniti predznak
      this.NastaviStevec(-1*this.PovejStevec());
    // metodi VrniStevec in NastaviStevec
     // sta podedovani iz razreda Ulomek
   }
}



  • metodi VrniStevec in NastaviStevec sta podedovani iz razreda Ulomek

Uporaba Boljšega ulomka

public class Test4 {
   public static void Main(string[] args)   {
      BolsiUlomek boUl;
      boUl = new BoljsiUlomek(); // "ustvarimo" boljši ulomek
       Ulomek navadniUl = new Ulomek();
       // spremenimo predznak navadnega ulomka
       navadniUl.Pomnozi(new Ulomek(-1,1)); // "čaramo"
       boUl.SpremeniPredznak(); // pri "boljših" ulomkih je zadeva elegantnejša
   }
}

Dedovanje

  • "razširjanje" objektov
  • :
  • Naredimo načrt za razred boljših ulomkov

    • Si znajo spremeniti predznak
  • public class BoljsiUlomek : Ulomek
  • Vsaka boljši ulomek "zna" vse, kar znajo "navadni" ulomki (torej tisti iz razreda Ulomek ) in morda še kaj
  • Ima ista stanja/lastnosti

    • Morebiti tudi dodatna

Dedovanje

  • Izpeljava novih razredov iz obstoječih
  • Uvajanje dodatnih metod
  • Lahko tudi dodatne lastnosti (podatki)
  • Primer:

    • matrike

      • Seštevanje, odštevanje, množenje
    • Kvadratne matrike / class KvMatrike : Matrike

      • Ker je razred izpeljan – ni potrebno na novo pisati metod za seštevanje, odštevanje, množenje
      • Možne dodatne operacije

        • Inverz, deljenje, …

Dedovanje - zgled

Sestavi razred Zlatnik, ki deduje iz razreda Kovanec. Zlatnik ima poleg osnovnih lastnosti kovanca podano še gostoto materiala, iz katerega je izdelan, čistočo zlata (podano z realnim številom med 0 in 1) in vrednost čistega zlata na masno enoto. Razred naj pozna tudi metodo double Vrednost() , ki vrne vrednost zlatnika.

Dedovanje

  • "razširjanje" objektov
  • :
  • Naredimo načrt za razred Zlatnik

    • Kovanec s posebnimi lastnostmi
  •  public class Zlatnik : Kovanec 
  • Vsaka zlatnik "je in zna" vse, kar zna kovanec in morda še kaj
  • Vrednost čistega zlata je enaka za VSE zlatnike

    • razredna spremenljivka (static )

Zlatnik

public class Zlatnik : Kovanec {
  private static double vrednostCistegaZlata = 125.4; // za vse zlatnike je to enako!
  private double gostota;
  private double cistost;
  // "SET" metode
  public void NastaviGostoto(double x) { }
  public void NastaviCistost(double x) { }
  public static void NastaviVrednostCistegaZlata(int v) { } // vrednost lahko spreminjamo tudi, ce se ni nobenega zlatnika!
  // "GET" metode
  public double PovejGostoto() { }
  public double PovejCistost() { }
  public static double PovejVrednostCistegaZlata() { } // vrednost lahko zvemo tudi, ce se ni nobenega zlatnika!
  // toString
  public override string ToString() { }   // podedovana toSTring metoda nam ne ustreza!
  // "znanje"
  public double Vrednost() {}
}

Konstruktorji

  • Konstruktorji se ne dedujejo
  • Lahko jih pa kličemo
  • Ključna beseda base
  • public Zlatnik() : base() {
        ...
    }
  • public Zlatnik(int r, int v) : base(r, v) {
        ...
    }

Zgled – "polovični" ulomki

  • Ulomki, le da imajo imenovalec vedno 2!
  • public PolovicniUlomek : Ulomek  {
  • Kje bodo težave?
  • Zagotoviti, da je imenovalec vedno 2!
  • Preprečiti spreminjanje imenovalca

    • Podedovana metoda NastaviImenovalec
  • Skriti je ne moremo!

Razred PolovicniUlomek

public class PolovicniUlomek : Ulomek {
   public PolovicniUlomek() : base(1,2) {
    // osnovni ulomek je 1 / 2
    // ni kode, ker vse naredi že "nadrejeni" konstruktor
   }
   public PolovicniUlomek(int st) : base(st, 2) {
     // Ulomek st / 2
   }
   public void NastaviImenovalec(int x) {
     // ne naredimo nič, ker im. Uporabnik ne
     // sme spremeniti!
   }
   // metode ToString ne bomo pisali na novo, saj
   // smo zadovoljni s podedovano!
}

Prekrivanje/predefiniranje

  • Če napišemo kar

    • public void NastaviImenovalec(int x) {
    • Obvestilo (opozorilo)
    • 'SplosnaKnjiznica.PolovicniUlomek.NastaviImenovalec(int)' hides inherited member 'SplosnaKnjiznica.Ulomek.NastaviImenovalec(int)'. Use the new keyword if hiding was intended.
    • Načeloma zadeve delajo, a ...
  • Metodo eksplicitno skrijemo (hide)

    • public new void NastaviImenovalec(int x) {

      • Nov pomen besedice new

ToString

  • Tudi pri ToString bi lahko ravnali tako
public new string ToString() {
  return this.PovejStevec() + " / " +
          this.PovejImenovalec();
}
  • A, če poskusimo z

    • Console.WriteLine(new Ulomek());
  • vidimo, da ne dela prav!
  • Za ToString obvezno napišemo
public override string ToString() {
  return this.PovejStevec() + " / " +
          this.PovejImenovalec();

Override

  • Če enako poskusimo z
public override void NastaviImenovalec(int x) {
    // ne naredimo nič, ker im. uporabnik ne
    // sme spremeniti!
}
  • Dobimo napako

    • 'SplosnaKnjiznica.PolovicniUlomek.NastaviImenovalec(int)': cannot override inherited member 'SplosnaKnjiznica.Ulomek.NastaviImenovalec(int)' because it is not marked virtual, abstract, or override (CS0506)
  • Za uporabo override pri metodi NastaviImenovalec v razredu PolovicniUlomek bi morala biti metoda NastaviImenovalec v razredu Ulomek posebej označena

Razred PolovicniUlomek

  • Seveda verjetno zgodba ni še zaključena
  • Metode PristejUlomek , PomnoziUlomek , ...
  • Pozor – podedovane metode vračajo enak tip kot prej
  • V razredu Ulomek

    • public Ulomek Pristej(Ulomek x) { ...
  • V nekem programu

    • PolovnicniUlomek nov = new PolovicniUlomek();
    • nov.Pristej(new Ulomek(2,3))

      • Rezultat je tipa Ulomek !

Razmerje med tipom in izpeljanim tipom

  • Mimogrede: Kaj bo s tem
  • PolovicniUlomek neki = nov.Pristej(new Ulomek(2,3));
  • Bo šlo?

    • Cannot implicitly convert type 'SplosnaKnjiznica.Ulomek' to 'SplosnaKnjiznica.PolovicniUlomek'. An explicit conversion exists (are you missing a cast?)
    • PolovicniUlomek je izpeljani tip – zato bodo morda določene informacije napačne (in naše bodo! – imenovalec 3!!) in prevajalnik se hoče "znebiti odgovornosti".
  • PolovicinUlomek neki = (PolovicniUlomek)nov.Pristej(new Ulomek(2,3));

Razmerje med tipom in izpeljanim tipom

  • Med izvajanjem
(dedovanje_21.png)


  • Še dobro, saj smo naredili neumnost!

Razmerje med tipom in izpeljanim tipom

  • Kaj pa

    • Ulomek x = new PolovicniUlomek(3);
    • Gre! (polimorfizem)
    • Vsak PolovicniUlomek je hkrati tudi Ulomek !
  • Polimorfizem

    • Večličnost
    • Nastopanje v več oblikah
    • Zelo močan koncept, a o njem žal ne bo časa govoriti

Dedovanje

  • Hierarična zgradba razredov
  • Vrhnji objekt

    • Object
    • Iz njega izpeljani vsi drugi razredi
    • Če ne napišemo : …
    • : Object
    • Ima metodo ToString()
    • Zato jo "imajo" vsi razredi
  • "specializacija" objektov

Zgradba

(dedovanje_24.png)

Iz muhe slon

  • Ne, iz muhe ne bomo naredili slona, ampak pri nas zajci postanejo ovce
  • Pri nas se je oglasil bogati Rus, ki je bil tako navdušen nad našim programom za vodenje farme zajcev, da hoče, da mu zanj pripravimo podoben program

    • Posebno nad metodo MeNapojiti() je bil navdušen!
  • A žal on ne vodi farme zajcev, ampak farmo ovac.
  • Poleg tega, da ovce redi za zakol, prodaja tudi njihove kožuhe, zato bi rad, da vodimo še barvo njihovega kožuha.

Osnova: razred ZajecNov

Kakšne spremembe so potrebne?

(dedovanje_26.png)

Spremembe: razred ZajecNov konstruktorji

Kakšne spremembe so potrebne?

(dedovanje_27.png)

Koda

    // konstruktorji

    public ZajecNov()
    {
      this.spol = true; // vsem zajcem na zaèetku doloèimo m. spol
      this.masa = 1.0; // in tehtajo 1kg
      ZajecNov.kolikoZajcev++; // NAREDILI SMO NOVEGA ZAJCA
      this.serijska = ZajecNov.osnovaSerijska +
                                 ZajecNov.kolikoZajcev;
    }
    public ZajecNov(bool spol, double masa) : this()
    {
      //this(); // poklicali smo konstruktor Zajec() - ta
      // bo že poveèal število zajcev + naredil
      // nj. ser. številko
      this.SpremeniTezo(masa); // uporabimo metodo za sprem.
      this.spol = spol;
    }

Spremembe: razred ZajecNov get/set metode

Kakšne spremembe so potrebne?

// get/set metode
    public double PovejTezo()
    {
      // težo bomo povedali le na 0.5 kg natanèno
      int tezaKg = (int)this.masa;
      int decim = (int)((this.masa - tezaKg) * 10);
      if (decim < 3) return tezaKg + 0.0;
      if (decim < 8) return tezaKg + 0.5;
      return tezaKg + 1.0;
    }

    public bool SpremeniTezo(double novaTeza)
    {
      // smislena nova teza je le med MIN_TEZA in MAX_TEZA
      if ((novaTeza > MIN_TEZA) && (novaTeza <= MAX_TEZA)){
        this.masa = novaTeza;
        return true; // sprememba uspela
      }
      // v nasprotnem primeru NE spremenimo teže
      // in javimo, da spremembe nismo naredili
      return false;
    }

Spremembe: razred ZajecNov get/set metode

  • Kakšne spremembe so potrebne?
    // get/set metode

    public string PovejSerijsko()
    {
      return this.serijska;
    }

    // public void spremeniSerijsko(String s) { TO IZLOČIMO;
    // KER SE SER_ŠT NE SPREMINJA!

    public bool JeSamec()
    {
      return this.spol;
    }

    // ker se spol naknadno NE spremeni, metode za
    // spreminjanje spola sploh ne ponudimo uporabniku!


  • Dodati metode za barvo

Spremembe: razred ZajecNov ostale metode

Kakšne spremembe so potrebne?

    // Ostale metode

    public double Vrednost(double cenaZaKg)
    {
      // pove vrednost zajca
      // zajce tehtamo na dogovorjeno natanènost
      return cenaZaKg * this.PovejTezo();
    }

    public bool MeNapojiti()
    {
      // ugotovimo, èe ga je smiselno
      // napojiti, da bomo "ujeli" naslednjih pol kg
      // dejanska teža je veèja od "izmerjene"
      return (this.masa - this.PovejTezo() > 0);
    }
  }

Potrebne spremembe

  • Napačne lastnosti

    • OsnovaSerijska ni OK
    • Prav tako MAX_TEZA
  • Dodati lastnosti

    • Barva kožuha
    • Get/set metodi

      • denimo, da si tudi ovce barvajo kožuhe
  • Popraviti konstruktor
  • Dodati metodo ToString

    • V razredu ZajecNov smo nanjo pozabili!

public class Ovca : ZajecNov {
    private string barva;
    public const int MAX_TEZA = 100; // maksimalna teža
    private static string osnovaSerijska = "OVCA_";

    public Ovca() : base() {
      barva = "bela";
    }

    // get/set metodi za barvo

}

Uporaba

public class Ovce {
  public static void main(String[] ar) {
     Ovca z1 = new Ovca();
      Console.WriteLine("teža: " + z1.PovejTezo());
      z1.SpremeniTezo(12);
      Console.WriteLine("Sedaj sem tezka: "  + z1.PovejTezo()); }
}

Problemi

(dedovanje_34.png)


  • Kaj je problem s težo ?
  • Poskusimo pogledati konstanto MAX_TEZA ?

    • Console.WriteLine(Ovca.MAX_TEZA)
    • OK!
  • Kaj vrne metoda SpremeniTezo(12) !

    • False!
    • Uporabi se konstanta iz razreda Zajec_Nov , saj metoda SpremeniTezo nič ne ve o kakšni novi konstanti!
    • Kako rešiti to?

Private, public, protected

  • Zaradi private tudi v izpeljanem razredu ne moremo do komponent

    • Čeprav smo jih podedovali
  • Protected
  • Izpeljani razredi lahko neposredno dostopajo do spremenljivk
  • Ampak tako ali tako smo rekli, da je bolje dostopati preko metod tudi znotraj razreda!

"Umazane" podrobnosti

  • Dostop do komponent nadrejenega razreda
  • serijska ponovno!
  • ker zaradi private ne moremo do serijska iz ZajecNov
  • PovejSerijska() na novo
  • enaka že v ZajecNov
  • Razlog: nova spremenljivka serijska
  • če ne navedemo - metoda bi vračala serijsko iz zajcev!!

Zgled – Oseba

  public class Oseba{
    private string ime;
    private string priimek;

    public Oseba() {
      ime = "Janez";
      priimek = "Novak";
    }
    public Oseba(string i, string p) : this() {
      this.NastaviIme(i);
      this.NastaviPriimek(p);
    }
             public bool NastaviPriimek(string p) {
      priimek = p;       // this ni potreben!
                        return true;
    }
    public bool NastaviIme(string ime) {
      this.ime = ime;  // this je potreben!
                return true;
    }
    public string VrniIme () {
      return this.ime;  // this ni potreben
    }
    public string VrniPriimek () {
      return this.priimek;  // this ni potreben
    }
    // Pri metodi posebej povemo, da jo lahko prekrijemo
    public virtual string Inicialke()  {
      return this.ime[0] + "." + this.priimek[0] + ".";
    }
  }

Dedovanje – Student

  • Iz razreda Oseba izpeljemo razred Student
  • Študent pozna poleg imena ter priimka še vpisno številko (int)
public class Student : Oseba{
  private int vpisna;

  public Student() : base()  {
    // Sedaj smo nastavili ime in priimek na "Janez Novak"
     // Nastavimo še vpisno številko na 1
    this.vpisna = 0;
  }
  public Student(string i, string p) : base(i, p)  {
    // Sedaj smo nastavili ime in priimek
    // Nastavimo še vpisno številko na 1
    this.vpisna = 0;
  }
  public Student(string i, string p, int vpisna) : this(i, p)  {
    // Sedaj smo nastavili ime in priimek
    // Nastavimo še vpisno številko
    this.vpisna = vpisna;
  }

  // Ustvarimo študenta iz osebe (uporabimo ime in priimek osebe)
  public Student(Oseba o, int vpisna) : this(o.VrniIme(), o.VrniPriimek())  {
    // Sedaj smo nastavili ime in priimek
    // Nastavimo še vpisno številko
    this.vpisna = vpisna;
  }

  public int VrniVpisno()  {
    return this.vpisna;
  }

  // Vpisno številko nastavimo le, če je pozitivna
  public bool NastaviVpisno(int vpisna) {
    if(vpisna > 0) {
      this.vpisna = vpisna;
      return true;
    }
    // else stavek ni potreben
    return false;
  }

  // Metodo Inicialke v razredu Student prekrijemo.
  // Želimo izpis oblike "student i.p.", kjer sta i in p prvi črki imena in priimka.
  // Prekrijemo lahko le metode, ki so v baznem razredu označene s
  // ključno besedo virtual
  public override string Inicialke() {
    string osnova = base.Inicialke();
    //Spredaj dodajmo niz "student"
    return "student" + osnova;
  }
}

Dedovanje – Student (nadaljevanje)

  • Študent pozna vse metode iz razrede Oseba, dodatno pa še metodi za nastavljanje in branje vrednosti vpisne številke
  • Do spremenljivk ime in priimek v baznem razredu ne moremo dostopati direktno (nivo dostopa!) ampak le preko javnih metod (konstruktor, get/set metode)

Dedovanje – Student (uporaba)

Metoda Inicialke je prekrita – obnaša se torej drugače kot v razredu Oseba.

  public static void Main(String[] argc) {
    // Ustvarimo osebi in študenta
    Oseba janez = new Oseba();
    Oseba micka = new Oseba(“Micka”, “Nekdo”);
    Student janezS = new Student ();
    Student mickaS = new Student (micka.VrniIme(), micka.VrniPriimek());
    Student micka1 = new Student (micka, 1);
    Student mojca = new Student (“Mojca”, “Ančimer”, 2);

    // Izpišemo inicialke
    Console.WriteLine(janez.Inicialke());
    // Pokliče se prekrita metoda!
    Console.WriteLine(micka1.Inicialke());

    // Poglejmo na studenta kot na osebo in mu izpišemo inicialke
    Oseba oMicka = (Oseba) mickaS;
    // Pokliče se prikrita metoda!
    Console.WriteLine(oMicka.Inicialke());
  }
(dedovanje_42.png)

Razlika med new in override

  • V razred Oseba dodajmo še (ni virtual)

      public string Inicialke1()  {
          return this.ime[0] + "." + this.priimek[0] + ".";
      }
  • V razred Student pa dodajmo še (z new lahko skrijemo vse metode)
public new string Inicialke1() {
    string osnova = base.Inicialke();
    //Spredaj dodajmo niz "student"
    return "student" + osnova;
}

Dedovanje – Student (uporaba)

  • Metoda Inicialke je prekrita – obnaša se torej drugače kot v razredu Oseba.
  • Metoda Inicialke1 skrije metodo Inicialke1 iz razreda Oseba.
public static void Main(String[] argc) {
    Oseba janez = new Oseba();
    Student janezS = new Student ();

    Console.WriteLine("\n Izpišemo inicialke objekta tipa Osebe");
    Console.WriteLine(janez.Inicialke());
    Console.WriteLine(janez.Inicialke1());

    Console.WriteLine("\n Izpišemo inicialke objekta tipa Student");
    Console.WriteLine(janezS.Inicialke());
    Console.WriteLine(janezS.Inicialke1());

    Console.WriteLine("\n  Kaj pa, če iz janezS naredimo (s cast) spet objekt tipa Oseba");
    Oseba janezO = (Oseba)janezS;
    Console.WriteLine(janezO.Inicialke());
    Console.WriteLine(janezO.Inicialke1());
}
(dedovanje_44.png)
0%
0%