Categories
C#

Wprowadźmy klasy

Wpis [part not set] z 8 w serii C# - podstawy

Nie, nie, nie – spokojnie, nie zamierzam wchodzić w tematykę społeczną, a na pewno nie w tym miejscu. Chcę za to dodać do naszego prostokątnego przekroju stalowe pręty, żebyśmy wreszcie mogli zacząć liczyć coś sensownego. W tym celu musimy porzucić dotychczasowy sposób pisania – nie sprostałby wymagań rosnącej funkcjonalności.

Jeżeli zetknąłeś się z programowaniem w ramach studiów budowlanych to prawdopodobnie klepałeś kod w myśl paradygmatu imperatywnego, gdzie program to ciąg sekwencji wykonywanych jedna po drugiej, zgodnie z założeniami programisty. Choć wątpię żebyś zastanawiał się nad istotą tej pracy – raczej chciałeś jak najszybciej skończyć zajęcia, żeby zająć się tym co wtedy wydawało się ważniejsze.

C# ma wytuatuowane na czole jestem obiektowy i choć to za słaba podpowiedź do gry ‘zgadnij kim jestem’ to warto zrozumieć na podstawowym poziomie koncept obiektowości – będziemy z niego korzystać przez cały czas. Postaram się go przedstawić na przykładzie naszego zadania – przekroju prostokątnego ze zbrojeniem.

Patrząc na jeden, konkretny przekrój w środku przęsła belki widzimy jeden prostokąt i kilka/kilkanaście prętów. W jaki sposób opisujemy te cześci składowe? Zacznijmy od przypomnienia poprzedniej części, czyli opiszmy przekrój.

Betonowy przekrój prostokątny w zakresie wyznaczania momentu bezwładności:

  • ma szerokość,
  • ma wysokość,
  • ma klasę betonu,
  • zwraca pole powierzchni,
  • zwraca moment bezwładności.

Co to za zabawy linigwistyczne zapytasz. Język programowania to właśnie język, kierujący się określoną logiką. Jak to będzie wyglądało w przypadku pręta stalowego w przekroju:

  • ma średnicę,
  • ma klasę stali,
  • ma położenie, lokalizację w ramach przekroju,
  • zwraca pole powierzchni.

Z premedytacją używałem dwóch czasowników: ma oraz zwraca.

  • Ma to podstawowa wartość, która określa, doprecyzowuje nasz element, która jest właściwością.
  • Zwraca to wyróżnik czegoś wynikowego, uzyskanego z operacji matematycznych na właściwościach lub ‘umiejętność’ danego obiektu. Zwraca to moje określenie metody, czyli funkcji, która coś robi.

Tak patrzę na betonowy przekrój oraz pręt w nim zawarty i trudno mi znaleźć cechy wspólne. Oba mogą zwrócić pole powierzchni, ale to trochę za mało, żeby wrzucić je do jednego worka – to są dwa oddzielne, logiczne byty. Fajnie by było zaimplementować nasz opis i właśnie w tym celu potrzebujemy konceptu klasy, czyli definicji jakiegoś elementu, obiektu. Spójrz na nową klasę ConcreteSection:

    public class ConcreteSection
    {
        // Properties
        public double Width { get; set; }
        public double Height { get; set; }
        public string ConcreteClass { get; set; }

        // Methods
        public double GetArea()
        {
            return Width * Height;
        }
        public double GetMomentOfInertia()
        {
            return (Width * Math.Pow(Height, 3)) / 12;
        }
    }

A jak by wyglądała klasa pręta stalowego?

    public class Rebar
    {
        // Properties
        public double Diameter { get; set; }
        public string SteelClass { get; set; }
        public double Location { get; set; }

        // Methods
        public double GetArea()
        {
            return Math.PI * Diameter * Diameter / 4.0;
        }
    }

Klasa to taki stempelek z ziemniaka określający kształt utworzonych przy jej pomocy obiektów. Definicja danej klasy jest jedna, natomiast obiektów powstałych przy jej pomocy – jeden/wiele/wedle potrzeb. Mając ziemniaczany stempelek możesz narobić tyle odbitek na ile wystarczy Ci tuszu i kartek/ścian, mając klase możesz stworzyć tyle obiektów na ile wystarczy Ci pamięci RAM w Twoim komputerze, czyli duuuuuużo.

Tylko jak stworzyć nowe odbitki z naszych pieczątek? Służy do tego słowo kluczowe new, spójrz proszę:

public static void Main()
{
    ConcreteSection section = new ConcreteSection();
    Rebar rebar = new Rebar();
}

Zastosowanie new wyraża intencję programisty – stwórz nowy obiekt danej klasy. Powyższa inicjalizacja nie jest powiązana z przypisaniem wartości do nowych obiektów – w konstruktorze klasy nie przekazujemy żadnych wartości. Mamy zwykłe ConcreteSection section = new ConcreteSection();

Konstruktor to wyjątkowa metoda/funkcja, której możemy użyć do początkowego ustawienia nowo powstałego obiektu. Na chwilę obecną nasze obiekty są golutkie, spójrz:

Brak klasy betonu, brak wysokości, brak szerokości. Próba wywołania metod obliczających pole powierzchni lub moment bezwładności zwróci zero, może to nie tragedia, ale już null’owa wartość klasy betonu może skończyć się wysypaniem programu. Nasze obiekty są zakalcowate, niezdatne do bezpiecznego spożycia. Istnieje kilka szkół inicjalizacji obiektów, my zrobimy to w bezpieczny sposób, oczekując podania wszystkich właściwości na etapie tworzenia nowych instancji klas. To dobry moment, żebyś dodał do swojego projektu nowe klasy. W tym celu naciśnij PPM na nazwie Twojego projektu widocznego w oknie po prawej stronie Visual Studio, rozwiń opcję Add i wybierz Class. Alternatywnie możesz skorzystać ze skrótu Shift + Alt + C.

W nowym oknie wpisz nazwę klasy: ConcreteSection, dodaj rozszerzenie pliku .cs i wybierz Add.

Pora na napisanie definicji klasy, proponuję żeby wyglądała następująco:

using System;

namespace PierwszaKonsola
{
    public class ConcreteSection
    { 
        // Properties
        public double Width { get; set; }
        public double Height { get; set; }
        public string ConcreteClass { get; set; }

        // Constructor
        public ConcreteSection(double width, double height, string concreteClass)
        {
            Width = width;
            Height = height;
            ConcreteClass = concreteClass;
        }

        // Methods
        public double GetArea()
        {
            return Width * Height;
        }
        public double GetMomentOfInertia()
        {
            return (Width * Math.Pow(Height, 3)) / 12;
        }
    }
}

Dodaj kolejną klasę do projektu, tym razem o nazwie Rebar:

using System;

namespace PierwszaKonsola
{
    public class Rebar
    {
        // Properties
        public double Diameter { get; set; }
        public string SteelClass { get; set; }
        public double Location { get; set; }

        // Constructor
        public Rebar(double diameter, string steelClass, double location)
        {
            Diameter = diameter;
            SteelClass = steelClass;
            Location = location;
        }

        // Methods
        public double GetArea()
        {
            return Math.PI * Diameter * Diameter / 4.0;
        }
    }
}

Mając zdefiniowane w projekcie dwie klasy zastąp proszę zawartość pliku Program.cs poniższą treścią:

using System;

namespace PierwszaKonsola
{
    public class Program
    {
        public static void Main()
        {
            ConcreteSection section = new ConcreteSection(30, 60, "C30/37");

            Console.WriteLine(section.Height);
            Console.ReadLine();
        }
    }
}

Program wyświetli w konsoli wartość 60. Może Cię kusić żeby stosować tę technikę podglądania obiektów częściej, ale nie rób tego. Zamiast wyświetlania w konsoli wartości, które Cię interesują spraw, aby program wstrzymał wykonywanie w zadanym przez Ciebie punkcie. W tym celu musisz zdefiniowac punkt debugowania, najprościej poprzez najechanie kursorem myszy na wyszarzony pasek po lewej stronie edytora:

i kliknięcie LPM – w tym momencie kropka stanie się czerwona. Uruchom teraz Twój program z poziomu VS – naciśnij F5.

W konsoli nie ma oczekiwanej wartości wysokości przekroju, co więcej focus przeszedł na Visual Studio, które mruga zachęcająco wskazując linijkę, przed którą zatrzymane zostało wykonywanie:

Właśnie wkroczyłeś w świat debugowanie, czyli podglądania w jakim stanie są obiekty w danym momencie wykonywania. To standardowe narzędzie potrzebne do zrozumienia i wyeliminowania bugów, czyli błędów w aplikacji. Zetknąłeś się z naprawdę potężną mocą, której pełne opanowanie chwilę potrwa. Na ten moment wiedz, że podstawowe opcje znajdują się na górnym pasku VS:

W obecnej sytuacji interesuje nas przede wszystkim przechodzenie do kolejnej linijki, które najłatwiej zrealizować poprzez naciśnięcie F10. Aplikacja przejdzie wtedy do kolejnej linijki kodu a my będziemy mogli sprawdzić efekty wykonania poprzedniej. Właśnie w ten sposób upewniłem się, że konstruktor poprawnie przekazał argumenty do właściwości naszego obiektu – można z niego korzystać.

Hej, pamiętasz nasze poprzednie podejście do konsoli? To w którym pobieraliśmy od użytkownika szerokość i wysokość przekroju a następnie obliczaliśmy i wyświetlaliśmy moment bezwładności. Tamto podejście łamało zasadę ograniczania odpowiedzialności mieszając w jednej klasie różne bajki – pobieranie danych od użytkownika i wyznaczanie momentu bezwładności. Spójrz na zmodyfikowany przykład wykorzystujący utworzoną dzisiaj klasę ConreteSection:

using System;

namespace PierwszaKonsola
{
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Podaj szerokość przekroju:");
            string widthFromUser = Console.ReadLine();
            double? width = GetDoubleFromUser(widthFromUser);
            if (!width.HasValue)
                return;
            Console.WriteLine("Podaj wysokość przekroju:");
            string heightFromUser = Console.ReadLine();
            double? height = GetDoubleFromUser(heightFromUser);
            if (!height.HasValue)
                return;

            ConcreteSection section = new ConcreteSection(width.Value, height.Value, "C30/37");

            string message1 = string.Format("Moment bezwładności wynosi {0:N2}", section.GetMomentOfInertia());
            Console.WriteLine(message1);
            Console.ReadLine();
        }

        public static double? GetDoubleFromUser(string inputValue)
        {
            double result;
            if (double.TryParse(inputValue, out result))
                return result;
            else
            {
                Console.WriteLine($"Wartość {inputValue} nie może zostać zamieniona na liczbę. Koniec działania programu.");
                return null;
            }
        }
    }
}

Jest lepiej, oddelegowaliśmy wyznaczanie momentu bezwładności odpowiedniemu obiektowi zapewniając wczesniej, że będzie on posiadał komplet danych do wykonania działania (konstruktor przekazujący wartości). I tak będziemy robić w dalszych częściach – wykorzystamy naszą wiedzę domenową w celu nazwania, zdefiniowania obiektów, których potrzebujemy, określimy ich właściwości oraz umiejętności (metody) a następnie połączymy stosownie do potrzeb w elegancką aplikację. No, może czasami to będzie elegancja ciosana udarowym Bosh’em, ale hej – pamiętasz gdzie byłeś przed stworzeniem swojej pierwszej aplikacji konsolowej?

Series Navigation

Leave a Reply

Your email address will not be published. Required fields are marked *