#6 Naucz się C# razem ze mną - Programowanie obiektowe

in #polish7 years ago

Witaj

W ostatnim już artykule o C# (w tej formie). W kolejnych wejdziemy już w świat gamedevu i zaczniemy wykorzystywać wiedzę którą już mamy (nie jest ogromna, ale to, co jak dotąd opisałem powinno wystarczyć i być dobrym punktem wyjścia) w praktyce - tworząc gry. Tymczasem przechodzimy do tematu.
Dzisiaj zajmiemy się programowaniem obiektowym.
Poznasz pojęcie klasy, obiektu, stworzymy metodę, napiszemy konstruktor i tak dalej…
Temat jest obszerny, więc myślę, że warto obrać taktykę od ogółu do szczegółu - szczegóły będziemy poznawać na bieżąco przy okazji następnych wpisów. Zaczynajmy!

Czym jest programowanie obiektowe

Programowanie obiektowe to jeden z wielu paradygmatów programowania…
Zaraz zaraz...para-co?

Paradygmat

Najprościej mówiąc jest to wzorzec postępowania w danej dziedzinie, jeśli chodzi konkretnie o programowanie, to paradygmat określa sposób w jaki patrzymy na dany problem.
W programowaniu obiektowym odzwierciedlamy sposób patrzenia człowieka na świat - wszystko co otacza nas dookoła (obiekty) możemy w jakiś sposób zdefiniować, określić. Przykład - mamy książkę. Jesteśmy w stanie określić: jaki jest jej tytuł, kto ją napisał, ile ma stron, ile kosztowała, czy ma twardą/miękką oprawę, w którym roku ją wydano, jakie wydawnictwo ją wydało.

Klasy

Klasy to foremki do obiektów. Deklaracja klasy wygląda następująco:

class NazwaKlasy
{
    //ciało klasy
}

Korzystając z naszego przykładu klasą jest Książka a instancją (egzemplarzem) naszej klasy jest obiekt będący konkretną książką (Pan Tadeusz Adama Mickiewicza, 434 strony, 29,99 zł, okładka miękka, rok wydania 2017, wydawnictwo XYZ). Takie zmienne wewnątrz klasy nazywamy polami.
Co oprócz cech naszej książki może znaleźć się w naszej “foremce”?

Metody

Dzięki metodom możemy określić, jakie zadania wykona nasza klasa. Naszą książkę możemy przeczytać, więc metoda o nazwie Czytaj będzie pasować tu idealnie.
Metoda cechuje się tym, że może zwracać wartość i przyjmować parametry. Jej deklaracja wygląda tak:

modyfikator_dostępu typ_zwracany Nazwa_Metody(typ_parametru nazwaParametru)
{
//ciało metody
return zwracana_wartość;
}

czyli w akcji:

public string Czytaj(int liczbaStron)
{
    return “Przeczytano “+liczbaStron+” stron”;
}

Wywołanie metody odbywa się poprzez podanie jej nazwy i parametrów. Jeśli nasza metoda coś zwraca, możemy od razu przypisać tę wartość do zmiennej:

typ wynik = nazwaObiektu.NazwaMetody(parametr);

np.

string wynik = ksiazka.Czytaj(20);

pojawił się modyfikator dostępu - określa on, czy nasza zmienna/metoda/itd… jest widoczna poza klasą.

W C# mamy 4 takie modyfikatory:

  • public - coś jest publiczne, mamy do tego nieograniczony dostęp
  • private - dostęp do takich składowych mamy tylko wewnątrz klasy, do której dana składowa należy
  • protected - dostęp wewnątrz klasy do której należy dana składowa oraz wewnątrz klas, które po tej klasie dziedziczą (dziedziczenie?! Co to? Jeszcze do tego dojdziemy, spokojnie)
  • internal - dostęp tylko wewnątrz danej biblioteki
  • protected internal - złączenie dwóch ostatnich modyfikatorów, składowe dostępne są dla danej klasy oraz klas dziedziczących, wewnątrz danej biblioteki.

Jeśli nie określimy modyfikatora dostępu, składowa jest domyślnie prywatna.
Aby móc skorzystać z naszej metody poza klasą użyliśmy modyfikatora public.
Stwórzmy teraz naszą książkę.

   class Ksiazka
    {
        public string autor; //pola klasy
        public string tytul;
        public int strony;
        public int rok;
        public double cena;
        public bool twardaOkladka;
        public string wydawnictwo;
        public string Czytaj(int liczbaStron) //metoda
        {
            return "Przeczytano " + liczbaStron + " stron";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Ksiazka ksiazka = new Ksiazka(); //stworzenie nowej książki
            ksiazka.autor = "Adam Mickiewicz"; 
            ksiazka.tytul = "Pan Tadeusz";
            ksiazka.strony = 434;
            ksiazka.rok = 2017;
            ksiazka.cena = 29.99;
            ksiazka.twardaOkladka = true;
            ksiazka.wydawnictwo = "XYZ";
            Console.WriteLine(ksiazka.autor + " " + ksiazka.tytul + " " + ksiazka.strony + " " + ksiazka.rok + " " + ksiazka.cena + " " + ksiazka.twardaOkladka + " " + ksiazka.wydawnictwo);
            Console.WriteLine(ksiazka.Czytaj(20)); //Wypisanie na ekran tego, co zwróci nam nasza metoda
            Console.ReadLine(); //A to sprawia, że program czeka aż klikniemy enter, i dopiero wtedy się wyłącza
        }
    }

Odpalamy i sprawdzamy czy działa:
img
Wszystko fajnie, ale stworzenie takiej książki wymagało od nas dużo .
Jak zrobić to lepiej?

Konstruktor

Konstruktor jest taką metodą która odpowiada za stworzenie nam nowego egzemplarza klasy.
Deklaracja najprostszego konstruktora wygląda tak:

class NazwaKlasy
{
    
    public NazwaKlasy()
    {
        //tutaj coś może się dziać
    }
}

w naszym wypadku konstruktor będzie wyglądał tak:

class Ksiazka
{
    public Ksiazka(string _autor, string _tytul, int _strony, int _rok, double _cena, bool _twardaOkladka, string _wydawnictwo)
    {
        this.autor = _autor;
        this.tytul = _tytul;
        this.strony = _strony;
        this.cena = _cena;
        this.twardaOkladka = _okladka;
        this.wydawnictwo = _wydawnictwo;
    }
public string autor;
public string tytul;
public int strony;
public int rok;
public double cena;
public bool twardaOkladka
public string wydawnictwo;
}

Jak skorzystać teraz z naszego konstruktora?

Ksiazka ksiazka = new Ksiazka(“Adam Mickiewicz”, “Pan Tadeusz”, 434, 2017, 29.99, true, “XYZ”);
{
        this.autor = _autor;
        this.tytul = _tytul;
        this.strony = _strony;
        this.cena = _cena;
        this.twardaOkladka = _okladka;
        this.wydawnictwo = _wydawnictwo;
    }

Ten fragment ustawia wartość pól klasy na wartości, które przekazaliśmy do konstruktora (żeby odróżnić jedne od drugich, zmienne w których przekazujemy wartości mają “_” przed nazwą).
Poprawmy teraz nasz kod i sprawdźmy, czy działa:

  class Ksiazka
    {
        public Ksiazka(string _autor, string _tytul, int _strony, int _rok, double _cena, bool _twardaOkladka, string _wydawnictwo)
        {
            this.autor = _autor;
            this.tytul = _tytul;
            this.strony = _strony;
            this.rok = _rok;
            this.cena = _cena;
            this.twardaOkladka = _twardaOkladka;
            this.wydawnictwo = _wydawnictwo;
        }
       public string autor;
       public string tytul;
       public int strony;
       public int rok;
       public double cena;
       public bool twardaOkladka;
       public string wydawnictwo;
        public string Czytaj(int liczbaStron)
        {
            return "Przeczytano " + liczbaStron + " stron";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Ksiazka ksiazka = new Ksiazka("Adam Mickiewicz", "Pan Tadeusz", 434, 2017, 29.99, true, "XYZ");
            Console.WriteLine(ksiazka.autor + " " + ksiazka.tytul + " " + ksiazka.strony + " " + ksiazka.rok + " " + ksiazka.cena + " " + ksiazka.twardaOkladka + " " + ksiazka.wydawnictwo);
            Console.WriteLine(ksiazka.Czytaj(20));
            Console.ReadLine();
        }
    }

img2
Działa. Wciąż jednak coś jest nie tak.
Zmienne wewnątrz naszej klasy są publiczne, co oznacza że można zmienić ich wartość w banalny sposób, i to wewnątrz innej klasy, czyli tak:

 ksiazka.autor = "blabla";

(
Tego oczywiście nie chcemy.

Z pomocą przychodzą

Właściwości

Z zewnątrz właściwości wyglądają jak pola, tak naprawdę zachowują się jak metody. Korzystamy z tego w taki sposób (podaję tu skróconą wersję):

public string Autor {get; set; }

get i set to akcesory właściwości - get pozwala na pobranie wartości, a set pozwala na jej ustawienie/zmianę.
W naszym przypadku nie chcemy, żeby ktoś mógł zmienić wartość pól naszej klasy wcześniej opisanym sposobem (ksiazka.autor = "blabla";), więc przed akcesorem set możemy napisać private, lub po prostu ten akcesor pominąć.
Nanieśmy poprawki do naszego kodu i sprawdźmy co się stanie:

 class Ksiazka
    {
        public Ksiazka(string _autor, string _tytul, int _strony, int _rok, double _cena, bool _twardaOkladka, string _wydawnictwo)
        {
            this.Autor = _autor;
            this.Tytul = _tytul;
            this.Strony = _strony;
            this.Rok = _rok;
            this.Cena = _cena;
            this.TwardaOkladka = _twardaOkladka;
            this.Wydawnictwo = _wydawnictwo;
        }
       
        public string Autor { get; }
        public string Tytul { get; }
        public int Strony { get; }
        public int Rok { get; }
        public double Cena { get; }
        public bool TwardaOkladka { get; }
        public string Wydawnictwo { get; }
        public string Czytaj(int liczbaStron)
        {
            return "Przeczytano " + liczbaStron + " stron";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Ksiazka ksiazka = new Ksiazka("Adam Mickiewicz", "Pan Tadeusz", 434, 2017, 29.99, true, "XYZ");
            //ksiazka.Autor = "blabla";
            Console.WriteLine(ksiazka.Autor + " " + ksiazka.Tytul + " " + ksiazka.Strony + " " + ksiazka.Rok + " " + ksiazka.Cena + " " + ksiazka.TwardaOkladka + " " + ksiazka.Wydawnictwo);
            Console.WriteLine(ksiazka.Czytaj(20));
            Console.ReadLine();
        }
    }


Works as expected.
Dla przećwiczenia napiszmy pseudo grę.
Potrzebujemy klasy naszego gracza, któremu będziemy kibicować, klasy przeciwnika, którego zechcemy stłuc, oraz głównej klasy gdzie odbędzie się potyczka, która będzie mieć 12 rund, lecz będzie mogła zakończyć się przed czasem.
Wrzucam od razu cały kod, przeanalizuj go, jeśli chcesz.

  class Player
    {
        public Player(string name, int maxHP, int currentHp, int attackDamage, double chanceToHit)
        {
            this.Name = name;
            this.MaxHP = maxHP;
            this.CurrentHp = currentHp;
            this.AttackDamage = attackDamage;
            this.ChanceToHit = chanceToHit;
        }
        public string Name { get; }
        public int MaxHP { get; }
        public int CurrentHp { get; set; }
        public int AttackDamage { get; }
        public double ChanceToHit { get; }
        public void Attack(Enemy enemy, int attackDamage)
        {
            enemy.CurrentHp -= attackDamage;
        }
    }
   class Enemy
    {
        public Enemy(string name, int maxHP, int currentHp, int attackDamage, int chanceToHit)
        {
            this.Name = name;
            this.MaxHP = maxHP;
            this.CurrentHp = currentHp;
            this.AttackDamage = attackDamage;
            this.ChanceToHit = chanceToHit;
        }
        public string Name { get; }
        public int MaxHP { get; }
        public int CurrentHp { get; set; }
        public int AttackDamage { get; }
        public int ChanceToHit { get; }
        public void Attack(Player player, int attackDamage)
        {
            player.CurrentHp -= attackDamage;
        }
    }
    class Octagon
    {
        static void Main(string[] args)
        {
            Random rnd = new Random();
            Player player = new Player("Heniek", 100, 100, 20, 50);
            Enemy enemy = new Enemy("Janek", 100, 100, 15, 60);
            for(int i=0; i<12; i++)
            {
                if (player.CurrentHp <= 0)
                {
                    Console.WriteLine(enemy.Name + " won");
                    break;
                }
                else if (enemy.CurrentHp <= 0)
                {
                    Console.WriteLine(player.Name + " won");
                    break;
                }
                else
                {
                    if (rnd.Next(0, 100) < player.ChanceToHit)
                    {
                        player.Attack(enemy, player.AttackDamage);
                        Console.WriteLine(player.Name + " deals " + player.AttackDamage + " damage to " + enemy.Name);
                        Console.WriteLine(enemy.Name + " hp:" + enemy.MaxHP + "/" + enemy.CurrentHp);
                    }
                    else
                    {
                        Console.WriteLine(player.Name + " missed");
                    }
                    if (rnd.Next(0, 100) < enemy.ChanceToHit)
                    {
                        enemy.Attack(player, enemy.AttackDamage);
                        Console.WriteLine(enemy.Name + " deals " + enemy.AttackDamage + " damage to " + player.Name);
                        Console.WriteLine(player.Name + " hp:" + player.MaxHP + "/" + player.CurrentHp);
                    }
                    else
                    {
                        Console.WriteLine(enemy.Name + " missed");
                    }
                }
            }
        
            Console.ReadLine();
        }
    }

Użyłem klasy Random, która pozwala nam wygenerować losową liczbę (no… pseudolosową).

Zobaczmy, kto wygra:

Nasz bohater Heniek oklepał przeciwnika. Tym pozytywnym akcentem zakończę dzisiejszy wpis.
Zapraszam do followowania, upvote’owania i komentowania.
Standardowo poniżej znajdziesz linki do poprzednich wpisów z tej serii.
Trzymaj się, hej!

Poprzednie posty z tej serii:
#1 Piszemy pierwszy program
#2 Zmienne
#3 Operatory
#4 Pętle
#5 Tablice

Sort:  

Dobrze napisane

Bardzo fajnie ;)

Congratulations @avenal! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You got a First Reply
Award for the number of upvotes received

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

By upvoting this notification, you can help all Steemit users. Learn how here!