Objekti - dostop

Objekti - dostop

Avtor: Matija Lokar

Dostop do stanj objekta

  • Možnost, da neposredno dostopamo do stanj/lastnosti objekta NI NAJBOLJŠI!

    • Ne le, da ni najboljši, je CENZURA
  • Nobene kontrole nad pravilnostjo podatkov o objektu!

    • rjavko.masa = -3.2;
    • Ker ne moremo vedeti, ali so podatki pravilni -vsi postopki po nepotrebnem bolj zapleteni
    • Objekt naj sam poskrbi, da bo v pravilnem stanju
  • Težave, če moramo kasneje spremeniti način predstavitve podatkov o objektu

Zgled

public class TestClan{
    public static void Main(string[] args) {
      Clan novClan = new Clan();
      novClan.ime = "Katarina";
      novClan.leto_vpisa = 208;

Dodajmo metodo

Ni problem, napisali bomo metodo, ki bo vnos preverjala

public class Clan {
    public string ime;
    public string priimek;
    public int leto_vpisa;
    public string vpisna_st;

    … // konstruktorji in metode kot prej!
    public bool SpremeniLetoVpisa(int leto) {
      if ((2000 <= leto) && (leto <= 2020)) {
        this.leto_vpisa = leto;
        return true; //leto je smiselno, popravimo stanje objekta in vrnemo true
       }
       return false; // leto ni smiselno, ne spremenimo nič in vrnemo false
    }
}

Uporaba metode

public class TestKlub {
  public static void Main(string[] args) {
     Clan novClan = new Clan();
     novClan.ime = "Katarina";
     novClan.leto_vpisa = 2007;
     novClan.SpremeniLetoVpisa(208);
     novClan.SpremeniLetoVpisa(2008);
     novClan.leto_vpisa = 20008;
  }
}

Sprememba razreda

  • Imamo  if (enClan.leto_vpisa > drugClan.leto_vpisa) { … 
  • Spremenimo razred Clan, tako, da vodimo datum vpisa
public class Clan {
    public string ime;
    public string priimek;
    public Datum datum_vpisa;
    public string vpisna_st;

    public Clan() {
    ime = "Ne vem";
    priimek = "Ne vem";
    datum_vpisa = new Datum();
    vpisna_st = "Ne vem";
    }
}
(dostop_5.png)

Sprememba razreda

    public Clan(string i, string p, Datum d, string v) : this() {
        ime = i;
        priimek = p;
        datum_vpisa = d;
        vpisna_st = v;
    }
    public string Inicialke() {
        return this.ime[0] + "." + this.priimek[0] + ".";
    }
    public Clan Kopija() {
        Clan nov = new Clan();
        nov.ime = this.ime;
        nov.priimek = this.priimek;
        nov.datum_vpisa = this.datum_vpisa.Kopija();
        nov.vpisna_stevilka = this.vpisna_stevilka;
        return nov;
    }

Sprememba razreda

public Clan(string i, string p, Datum d, string v) : this() {
    ime = i;
    priimek = p;
    datum_vpisa = d;
    vpisna_st = v;
}

public string Inicialke() {
    return this.ime[0] + "." + this.priimek[0] + ".";
}

public Clan Kopija() {
    Clan nov = new Clan();
    nov.ime = this.ime;
    nov.priimek = this.priimek;
    nov.datum_vpisa = this.datum_vpisa.Kopija();
    nov.vpisna_stevilka = this.vpisna_stevilka;
    return nov;
  }

Sprememba razreda

public void Izpis() {
    Console.WriteLine("Clan:\n" + this.ime + " " + this.priimek + " " +
       this.Datum_vpisa.OpisDat() + " (" + this.vpisna_st + ")\n");
}
public string Opis() {
    return this.ime + " " + this.priimek + " " +
       this.datum_vpisa.OpisDat() + " (" + this.vpisna_st + ");
}
public bool SpremeniLetoVpisa(int l) {
    if ((2000 <= leto) && (leto <= 2020)) {
       this.datum_vpisa.leto = l;
       return true; //leto je smiselno, popravimo stanje objekta in vrnemo true
       }
       return false; // leto ni smiselno, ne spremenimo nič in vrnemo false
    }
}

Način programiranja

  • Seveda zaradi spremembe if (enClan.leto_vpisa > drugClan.leto_vpisa) { … 
  • ne deluje več!
  • Kako popraviti?

Že v prvotnem razredu

public class Clan {
    public string ime;
    public string priimek;
    public int leto_vpisa;
    public string vpisna_st;

    public bool SpremeniLetoVpisa(int leto) {
      if ((2000 <= leto) && (leto <= 2020)) {
        this.leto_vpisa = leto;
        return true;
      }
      return false; // leto ni smiselno, ne spremnimo nič in vrnemo false
    }
    public int VrniLetoVpisa() {
      return this.leto_vpisa;
    }
}
(dostop_10.png)

Ob spremembi razreda Clan

  • Le metodo

       public int VrniLetoVpisa() {
          return this.leto_vpisa;
       }
  • Zamenjamo z

       public int VrniLetoVpisa() {
          return this.datum_vpisa.leto;
       }
(dostop_11.png)

Dostopi do stanj

  • ime_objekta.stanje
  • Zakaj je to lahko problem?

    • "zunanji" uporabnik nastavi napačno vrednost

      • z1.masa = -100.10;
    • Uporabnik pozna predstavitev

      • Kasneje je ni mogoče spremeniti
  • Načini dostopa

    • public
    • private

      • Ostale pozabimo
    • protected
    • internal
  • public string serijska; private Datum datumRojstva;
  •  private double masa; public int[] tab;
  • bool spol;  Izogibajmo!

public

  • Znotraj razreda NI omejitev, vedno (ne glede na način dostopa) je možen dostop do komponent.
  • public

    • Do lastnosti lahko dostopajo vsi, od kjerkoli (iz katerihkoli datotek (razredov))

      • ime_objekta.lastnost
    • public int javnaLastnost; // v razredu MojObjekt 
    • Kdorkoli naredi objekt vrste MojObjekt

      • MojObjekt x = new MojObjekt();
    • lahko dostopa do javnaLastnost

      • x.javnaLastnost

private

  • Do lastnosti ne more dostopati nihče, razen metod znotraj razreda

    • Ko pišemo načrt razreda
    • this.lastnost
    • lastnost
  • private int privatnaLastnost; // v razredu MojObjekt
  • Če kdo naredi objekt vrste MojObjekt

    • MojObjekt x = new MojObjekt();
  • Pri poskusu dostopa do privatnaLastnost

    • x.privatnaLastnost
    • Prevajalnik javi napako

Razred Zajec

public class Zajec {
   public string serijska; // serijska stevilka zajca
   public bool spol; // true = moski, false = zenska
   private double masa; // masa zajca ob zadnjem pregledu
}
(dostop_15.png)

Koda

public class Zajčnik {
  public static void Main(string[] ar) {
     Zajec z1 = new Zajec();
     z1.serijska = "1238-12-0“;
     z1.spol = false;
     z1.masa = 0.12;
     z1.masa = z1.masa + 0.3;
     Console.WriteLine("Zajec ima ser. št.:" + z1.serijska);
   }
}

Razred Zajec 2

 public class Zajec2 {
   public string serijska; // serijska stevilka zajca
   public bool spol; // true = moski, false = zenska
   private double masa; // masa zajca ob zadnjem pregledu

  public SpremeniTezo(double x) {
    this.masa = x;
  }
}
public class Zajčnik {
  public static void Main(string[] ar) {
     Zajec z1 = new Zajec2();
     z1.serijska = "1238-12-0“;
     z1.spol = false;
     z1.SpremeniTezo(0.11);
     z1.masa = 0.12;
     z1.masa = z1.masa + 0.3;
     Console.WriteLine("Zajec ima ser. št.:" + z1.serijska);
   }
}

Dostop do stanj/lastnost

  • Kako uporabiti:

    • Metode za dostop do stanj

      • "get" metode
    • Metode za nastavljanje stanj

      • "set" metode
  • Zakaj je boljši dostop preko metod kot neposredno

    • Možnost kontrole pravilnosti!
    • Možnost kasnejše spremembe načina predstavitve (hranjenja podatkov)

Dostop do stanj/lastnost

Zakaj je boljši dostop preko metod kot neposredno

  • Možnost oblikovanja pogleda na podatke

    • Podatke uporabniku posredujemo drugače, kot jih hranimo (datum – interno je mesec število (1, 2, ...), "navzven" kot ime ("januar", "februar" ...)
  • Dostop do določenih lastnosti lahko omejimo

    • Npr. spol lahko nastavimo le, ko naredimo objekt (kasneje ne, saj se ne spreminja ... če odmislimo kakšne operacije, določene vrste živali ... seveda.)
    • Hranimo lahko tudi določene podatke, ki jih uporabnik sploh ne potrebuje ..

Nastavitve stanj/podatkov

  • Nastavitve stanj

    • "prireditveni stavek"
  • zajcek.SpremeniTezo(2.5);

    • V bistvu isto kot zajcek.masa = 2.5;
    • A metoda spremeniTezo lahko PREVERI, če je taka sprememba teže smiselna!
  • zajcek.SpremeniTezo(-12.5);

    • V bistvu isto kot zajcek.masa = -12.5;
    • A tu bomo lahko PREPREČILI postavitev lastnosti objekta v napačno stanje!

Razred Zajec - SpremeniTezo

public void SpremeniTezo(double novaTeza) {
   // smislena nova teza je le med 0 in 10 kg
   if ((0 < novaTeza) && (novaTeza <= 10))
        this.masa = novaTeza;
   // v nasprotnem primeru NE spremenimo teže
}

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

SpremeniTezo

  • Imamo lahko OBE metodi?

    • NE
    • Imata enak podpis (ime + tipi parametrov)
    • Tip rezultata NI del podpisa!
  • Metoda je seveda lahko bolj kompleksna – denimo vemo, da se teža ne more spremeniti bolj kot za 15%
public bool SpremeniTezo(double novaTeza) {
    // smislena nova teza je le med 0 in 10 kg
    // in če ni več kot 15% spremembe od zadnjič
    int sprememba = (int)(0.5 + (100 * Math.abs(this.masa – novaTeza) / this.masa);

    if ((0 < novaTeza) && (novaTeza <= 10) && (sprememba <= 15) ){
        masa = novaTeza; // this.masa ... Lahko pa this spustimo!
    return true; // sprememba uspela
    }
    // v nasprotnem primeru NE spremenimo teže
    // in javimo, da spremembe nismo naredili
    return false;
}

Teža in konstruktor

  • Seveda je smiselno, da zagotovimo, da je teža ustrezna že ves čas!

    • Pozor na začetno stanje: konstruktor!
    • Tudi v konstruktorju preverimo, če se uporabnik "obnaša lepo"
  • Pogosto na to pozabimo

    • Zajec neki = new Zajec("X532", true, 105);
    • 105 kilogramskega zajca verjetno ni, a uporabnik je pozabil na decimalno piko v 1.05
    • Zato je kontrola smiselnosti podatkov potrebna tudi v konstruktorjih!

SpremeniSpol, SpremeniSerijsko

  • Kaj pa SpremeniSpol

    • Če niste v kakšni čudni industriji ali razvoju, je ta metoda odveč ;-))
  • Morda tudi metoda SpremeniSerijsko

    • Pozor na konstruktor brez parametrov
    • Serijska je "NEDOLOČENO"
  • Metoda SpremeniSerijsko naj pusti spreminjati le take serijske številke!
public bool SpremeniSerijsko(string seStev) {
   // sprememba dopustna le, če serijske štev. ni
   if (this.serijska.Equals("NEDLOČENO")) {
     this.serijska = seStev;
     return true;
   }
   return false; // ne smemo spremniti že obstoječe!
}

Dostop do stanj

  • Poizvedba

    • "spremenljivka" v izrazu
  • zajcek.PovejTezo()

    • Da bomo lahko izvedeli težo zajca
    • Najenostavneje v telesu metode le return this.masa;
    • Lahko stvar "oblikujemo" – recimo, da bomo uporabniku vedno povedali le težo na pol kilograma, mi pa bomo interno stvar vodili natančneje

Razred Zajec - povejTezo

public double PovejTezo() {
   return this.masa; // ali return masa
}
  • Ali pa prilagodimo podatke

    • Težo povemo na 0.5 kg natančno
    • 2.721 -> 2.5, 2.905 -> 3, 2.502 -> 2.5, ...
    • Vzamemo težo v celih kg in pogledamo prvo decimalko decimalnega dela
    • Če je med 3 in 7, prištejemo k celim kg 0.5
    • Če je več kot 7, prištejemo k celim kg 1.0
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;
}

Celotni razred Zajec - 1

public class Zajec {
   private string serijska;
   private bool spol;
   private double masa;

   // konstruktor
   public Zajec() {
     this.spol = true; // vsem zajcem na začetku določimo m. spol
     this.masa = 1.0; // in tehtajo 1kg
     this.serijska = "NEDOLOČENO";
   }
   public Zajec(string serijskaStev):this() {
     this.serijska = serijskaStev;
   }
   public Zajec(string serijskaStev, bool spol, double masa):this() {
     this.serijska = serijskaStev;
     this.SpremeniTezo(masa); // uporabimo metodo za sprem.
     this.spol = spol;
   }

Celotni razred Zajec - 2

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 0 in 10 kg
    if ((0 < novaTeza) && (novaTeza <= 10)){
        masa = novaTeza; // this.masa ... Lahko pa this spustimo!
     return true; // sprememba uspela
    }
    // v nasprotnem primeru NE spremenimo teže
    // in javimo, da spremembe nismo naredili
    return false;
}

Celotni razred Zajec - 3

  public string PovejSerijsko() {
    return this.serijska;
  }
  public void SpremeniSerijsko(string s) {  this.serijska = s;
  }
  public bool JeSamec() {
    return this.spol;
  }

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

} // Zajec

Uporaba razreda Zajec

public class Zajčnik {
  public static void Main(string[] ar) {
     Zajec z1 = new Zajec();
     z1.serijska = "1238-12-0“;
     z1.spol = false;
     z1.masa = 0.12;
     z1.masa = z1.masa + 0.3;
     System.out.println("Zajec ima ser. št.:” + z1.serijska);   } }
public class ZajčnikNov {
  public static void Main(string[] ar) {
     Zajec z1 = new Zajec("1238-12-0", false, 0.12);
     if (z1.SpremeniTezo(z1.PovejTezo() + 0.3))
       Console.WriteLine("Teža je spremenjena na " +  z1.PovejTezo());
     else Console.WriteLine("Teža je nespremenjena!" +  " Prirastek je prevelik! Preveri!");
     Console.WriteLine("Zajec ima ser. št.:" + z1.PovejSerijsko());
   }
}

Ostale metode

  • Poleg get/set metod in konstruktorjev
  • Metode

    • Odzivi objektov
    • "znanje objektov"
  • Objekti tipa string

    • Znajo povedati, kje se v njih začne nek podniz:

      •  "niz".IndexOf("i") 
    • Znajo vrniti spremeniti črke v male in vrniti nov niz:

      • nekNiz.ToLower()
    • Znajo povedati, če so enaki nekemu drugemu nizu:

      • mojPriimek.Equals("Lokar")
  • "naši" razredi

    • Sprogramiramo znanje – ustrezne metode
  • Zajec bo znal povedati svojo vrednost, če mu povemo ceno za kg žive teže

Razred Zajec – dodatne 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);
}

Metoda ToString

Poglejmo si naslednji program:

using System;
using MojaKniznica;

namespace Izpis
{
  class Program
  {
    public static void Main(string[] args)
    {
      Zajec zeko = new Zajec();
      Console.WriteLine("To je zajec: " + zeko);

      Console.Write("Press any key to continue . . . ");
      Console.ReadKey(true);
    }
  }
}
(dostop_32.png)

ToString

  • Očitno objekte lahko tudi izpišemo
  • Gre seveda tudi takole

    • string niz = "Zajec " + zeko + "!"; 
  • Torej se tudi objekti "obnašajo" tako kot int, double ... (se torej pretvorijo v niz)
  • A s pretvorbo nismo ravno zadovoljni, radi bi kakšne bolj smiselne informacije V razredu, ki ga definiramo (recimo Zajec) napišemo metodo ToString
  • Če na določenem mestu potrebujemo niz, a naletimo na objekt, se ta metoda pokliče avtomatično

    • string niz = "Zajec " + zeko.ToString() + "!"; 

Metoda ToString

Isti program, a tako, da je v razredu Zajec metoda ToString:

using System;
using MojaKniznica;

namespace Izpis
{
  class Program
  {
    public static void Main(string[] args)
    {
      Zajec zeko = new Zajec();
      Console.WriteLine("To je zajec: " + zeko);

      Console.Write("Press any key to continue . . . ");
      Console.ReadKey(true);
    }
  }
}
(dostop_34.png)

ToString

  • V razred Zajec smo napisali

      public override string ToString()
        {
        return "Zajec: " + this.PovejSerijsko();
      }
  • Posebnost - override
  • Zaradi "dedovanja"

    • O tem kasneje
    • Z dedovanjem smo avtomatično pridobili metodo ToString (zato je vedno na voljo) Zato override, da "povozimo " podedovano metodo

ToString in Opis

  • Razlika med

    public override string ToString()
    {
      return "Zajec: " + this.PovejSerijsko() ;
    }
  • in

    public string Opis()
    {
      return "Zajec: " + this.PovejSerijsko() ;
    }
  • Metodo Opis moramo poklicati sami, metoda ToString se kliče avtomatsko (če je potrebno)

    • string niz1 = "Zajec " + zeko.ToString() + "!"; 
    • string niz2 = "Zajec " + zeko + "!"; 
    • string niz3 = "Zajec " + zeko.Opis() + "!"; 
  • Vsi trije niz enaki!
  • ToString obstaja, tudi, če jo ne napišemo (a verjetno z vrnjenim nizom nismo najbolj zadovoljni)

Tudi objekti so lahko del razreda

  • Ko sestavljamo razred, kot objektne spremenljivke lahko uporabimo tudi spremenljivke istega razreda
  • Npr.: starsi Zajca
  • Razred Clan
public class Clan {
    private string ime;
    private Clan gaJePriporocil;
    ...

Še malo o zajcih

Preden nadaljujemo

  • Želimo hraniti tudi starše zajca

    public class Zajec {
      private double masa;
      private string serijska;
      private bool spol;
      private Zajec oce;
      private zajec mati;
  • Še konstruktor

Problemi s konstruktorjem

  • Sestavimo privzeti konstruktor

    public Zajec() {
      this.spol = true;
      this.masa = 1.0;
      this.serijska = "NEDOLOČENO";
      this.oce = new Zajec();
      this.mama = new Zajec();
    }
  • Ko stvar prevedemo, je vse v redu. A ob poskusu

    • Zajec rjavko = new Zajec();

Problemi s konstruktorjem

(dostop_40.png)
  • Rekurzija brez ustavitve
  • Tudi konstruktor je metoda

    • Ustavitveni pogoj
    • A pri konstruktorju brez parametrov ni parametrov, ki bi lahko določili ustavitveni pogoj

Kaj storiti

  • "Privzeti" zajec ne pozna staršev
  • oce = null;
  • mama = null;
  • Če malo pomislimo – ko zajca "naredimo", morata starša že obstajati

    • Ju nima smisla ustvarjati na novo
0%
0%