Klasy w PowerShell cz. 2


W poprzednim artykule na ten temat omówiłem właściwości, które są kluczowymi elementami każdej klasy w PowerShellu. Umożliwiają one przechowywanie danych w jej instancji. Funkcją klasy jest jednak nie tylko składowanie informacji. Równie ważne jest manipulowanie tymi danymi w ramach cyklu życia danego obiektu. Takie akcje, które klasa pozwala wykonywać na obiekcie, to metody.

Metody

Metody są wyspecjalizowanymi funkcjami wykonywanymi na danym obiekcie oraz na informacjach, które on przechowuje. Wywołuje się je z tego obiektu, na którym mają być wykonane. Dzięki temu mają dostęp do informacji, które są w nim zawarte, a niekoniecznie do danych z innego obiektu tej samej klasy.

Możemy sobie wyobrazić sytuację, w której obiektem będzie na przykład… pralka. Ma ona określone właściwości, takie jak gabaryty, liczba obrotów na minutę, temperatura prania oraz metody – pranie, płukanie, wirowanie. Pralka wykonuje akcje na samej sobie, nie może więc wykonać prania w innej pralce. Oznacza to, że metoda jest integralną częścią instancji danej klasy, gdyż tylko na niej może wykonywać swoje działania. To właśnie odróżnia metody od klasycznych funkcji, do której przekazujemy określone obiekty, jakimi będziemy manipulować.

Wróćmy zatem do wcześniejszego przykładu zmiany nazwiska z naszej pseudoklasy. Stworzyliśmy funkcję, która przyjmowała dwa parametry: obiekt pracownika oraz nowe nazwisko. Sama funkcja wykonywała dwie proste czynności – przypisywała wartość do właściwości Nazwisko, a następnie zwracała cały obiekt. W skrypcie musieliśmy natomiast we własnym zakresie zadbać o to, aby przypisać zwróconą wartość do odpowiedniej zmiennej:

Zanim zaimplementujemy w naszej docelowej klasie Pracownik metodę zmiany nazwiska, sprawdźmy, jak definiuje się metodę:

Składnia jest dość mocno zbliżona do definicji zwykłej funkcji w PowerShellu. Mamy więc:

  1. zwracany typ danych – informuje o tym, jakiego typu będą dane zwracane przez metodę; jeśli natomiast żadne dane nie są zwracane, podajemy void jako typ;
  2. nazwę metody;
  3. parametry wraz z typami, jeśli są znane; nie definiujemy ich za pomocą bloku param(), jak wykonujemy to w funkcjach.

Nasza metoda zmiany nazwiska wraz z definicją całej klasy wyglądać będzie następująco:

W metodzie pojawia się zmienna $this, która jest słowem kluczowym oznaczającym odniesienie do aktualnej instancji obiektu. W uproszczeniu reprezentuje siebie samego. Za jej pomocą obiekt może uzyskać dostęp do swoich właściwości i metod.

Nasz metoda wykonuje tylko jedną czynność – przypisania wartości o typie String do właściwości Nazwisko. Podobnie jak w przypadku dostępu do właściwości danego obiektu, metody wywołujemy po znaku kropki, który oddziela zmienną przechowującą obiekt od jej nazwy. Zmianę nazwiska wykonamy następująco:

Porównując teraz sposób zmiany nazwiska za pomocą metod oraz zwykłej funkcji, możemy zauważyć, że korzystanie z klas jest dużo bardziej intuicyjne i jednoznacznie definiuje nam wykonywaną czynność wraz z odpowiednimi obiektami.

Cały czas możemy też bezpośrednio przypisać wartość do właściwości:

Na czym polega wobec tego przewaga metod nad bezpośrednim dostępem do właściwości? Otrzymujemy możliwość wykonania dodatkowych akcji, np. walidacji wprowadzonych danych. Jeśli chcielibyśmy się upewnić, że nazwisko rozpoczyna się od wielkiej litery, a każda kolejna jest mała, to definicja naszej metody mogłaby wyglądać następująco:

W wyniku wywołania tej metody wartość właściwości Nazwisko rozpoczynać się będzie od wielkiej litery, a każda kolejna będzie mała, niezależnie od tego, w jakim formacie podano dane wejściowe:

Właściwości i metody statyczne

W poprzednim artykule wspomniałem o tym, że obiekt przechowuje dane w swoich właściwościach. Każdy obiekt zawiera własne i unikatowe informacje. Metody natomiast operują na danych w nim przechowywanych. Tak jak pokazał wcześniejszy przykład, pralka nie może włączyć prania w innym urządzeniu.

Istnieje jednakże specjalny typ właściwości, który umożliwia przechowywanie takiej samej wartości dla wszystkich obiektów. Sprawdźmy to na przykładzie naszego pracownika. Każdy obiekt pracownika posiada pracodawcę o takiej samej nazwie. Spróbujmy więc utworzyć specjalny typ właściwości, który dla każdego obiektu będzie taki sam. Służy do tego słowo kluczowe static:

Zaletą takiego rozwiązania jest możliwość sprawdzenia wartości tej właściwości, jeszcze przed zainicjowaniem obiektu. Wywołujemy ją za pomocą [NazwaKlasy]::NazwaWłaściwości.

Jeśli chcemy sprawdzić wartość właściwości poprzez zainicjalizowany wcześniej obiekt, musimy zrealizować to podobnie. Z tym, że zamiast nazwy klasy musimy podać zmienną przechowującą obiekt:

Właściwości statycznych nie możemy natomiast wywoływać za pomocą znaku kropki, gdyż nie otrzymamy żadnego wyniku:

Sprawdźmy jeszcze wartość właściwości dla drugiego obiektu, który zainicjalizujemy:

Co możemy zrobić w przypadku zmiany nazwy pracodawcy? Czy musimy zmieniać całą klasę i inicjalizować obiekty od nowa? Wartość właściwości statycznych możemy zmieniać w analogiczny sposób jak przy zmianie wartości dowolnej innej właściwości. Należy jednakże pamiętać, że wartość jest taka sama dla wszystkich obiektów. Oznacza to, że nawet jeśli zmienimy informacje, które przechowuje wywołując ją z poziomu jednego obiektu, to ulegną one zmianie także dla wszystkich już zainicjalizowanych, a także tych, które powstaną w przyszłości.

Wartość właściwości statycznej możemy także zmienić, odwołując się do niej poprzez nazwę klasy:

Takie ustawienie wartości może być łatwiejsze w zrozumieniu i od razu informuje nas o tym, że wartość zmieni się dla całej klasy, a co za tym idzie – również dla każdej instancji obiektu. Możliwość odwoływania się do właściwości statycznych poprzez konkretną instancję obiektu jest jedynie ułatwieniem składniowym języka.

Metody statyczne, podobnie jak zwykłe metody, umożliwiają wykonywanie zdefiniowanych wcześniej akcji. Najważniejszą różnicą jest to, że nie wywołuje się jej w kontekście żadnego konkretnego obiektu tej klasy. Służą one z reguły do obsługi właściwości statycznych. Nie możemy zatem wykorzystywać w niej zmiennej $this, gdyż nie reprezentuje ona żadnego obiektu w tym kontekście. Metody statyczne mają dostęp tylko do właściwości statycznych oraz innych metod statycznych.

Definicja metody statycznej wygląda analogicznie do definicji zwykłej metody, z tą różnicą, że dodajemy słowo kluczowe static:

Utworzę teraz metodę zmieniającą nazwę pracodawcy, która będzie weryfikować, czy nowa nazwa nie jest taka sama jak poprzednia.

Wynik jej działania można sprawdzić poniżej:

Właściwości i metody ukryte

Właściwości i metody ukryte definiowane są za pomocą słowa kluczowego hidden. Zdefiniowanie ich w ten sposób powoduje, że nie są widoczne po użyciu cmdlet’u Get-Member. Utworzymy zatem właściwość ukrytą informującą o tym, czy pracownik jest zatrudniony, oraz metodę ukrytą zmieniającą status zatrudnienia:

Wynik działania Get-Member widać poniżej:

Jeśli dodamy atrybut -Force, otrzymamy w wyniku także metody i właściwości ukryte:

Wywołanie metody ukrytej wykonujemy analogicznie do zwykłej metody. Nie zostanie jedynie podpowiedziana jej nazwa:

Słowo kluczowe hidden nie jest tym samym co private w językach programowania takich jak C#. W PowerShellu nie istnieją właściwości prywatne.

Konstruktory

Do tej pory po zainicjowaniu obiektu przypisywaliśmy wartości do właściwości bezpośrednio. Nie korzystaliśmy w związku z tym z dodatkowych możliwości, które oferują nam metody. A co jeśli chcielibyśmy wykonać metodę podczas tworzenia nowej instancji z klasy?

Istnieje specjalny typ metody, który wykonywany jest tylko podczas tworzenia obiektu. Jego nazwa musi być taka sama jak nazwa klasy. W funkcjach w PowerShellu analogiczną strukturą do konstruktora będzie blok Begin, który także wykonywany jest tylko jeden raz.

Napiszemy więc najprostszy konstruktor, którego jedynym zadaniem będzie wyświetlenie informacji o utworzeniu obiektu:

Utworzenie nowego obiektu z klasy spowoduje wypisanie na ekran zdefiniowanej przez nas informacji:

Podstawowym zastosowaniem konstruktora jest zainicjowanie obiektu oraz wypełnienie jego właściwości odpowiednimi wartościami. Oznacza to, że zamiast przypisywać wartości poszczególnym wartościom obiektu, możemy to wykonać od razu przy jego tworzeniu. Taki konstruktor musi być tak zdefiniowany, aby przyjmował w argumentach odpowiednie wartości, które zostaną wykorzystane do zainicjowania go danymi. W przypadku naszej klasy Pracownik jego definicja może wyglądać następująco:

Utworzenie nowego obiektu z tak zdefiniowanym konstruktorem wymaga przekazania odpowiednich wartości w atrybutach:

Należy pamiętać, że po zdefiniowaniu jedynego konstruktora, który przyjmuje określone argumenty, nie będzie można utworzyć obiektu bez ich podania:

Otrzymujemy komunikat o błędzie przeciążania konstruktora dla zerowej ilości argumentów. Można zdefiniować wiele konstruktorów, dzięki czemu takie tworzenie obiektu będzie możliwe do wykonania. Przeciążanie metod i konstruktorów to już temat kolejnej części tego cyklu.

Do zapamiętania

  1. Operator „.” (kropka) służy do dostępu do właściwości i metod w instancji danego obiektu.
  2. Operator „::” (podwójny dwukropek) służy do dostępu do właściwości i metod statycznych w danej klasie.
  3. Istnieją trzy rodzaje właściwości i metod: publiczne, statyczne i ukryte.
  4. Właściwości statyczne mają taką samą wartość dla każdej instancji obiektu.
  5. Dostęp do właściwości statycznych możliwy jest bez inicjalizowania obiektu.
  6. Metody statyczne mają dostęp tylko do metod i właściwości statycznych.
  7. Za pomocą słowa kluczowego $this obiekt może dostać się do swoich właściwości i metod.
  8. Konstruktory umożliwiają wykonanie określonych akcji przy inicjalizacji obiektu.
  9. Podstawowym zastosowaniem konstruktora jest zainicjowanie obiektu.

Co dalej?

W następnym artykule omówię przeciążanie metod i konstruktorów, typy wyliczeniowe oraz dziedziczenie.

Wszystkie części:

  1. Klasy w PowerShell
  2. Klasy w PowerShell cz. 2

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

6 komentarzy do “Klasy w PowerShell cz. 2

  • Pargo

    Przydatny tekst. Czekam na kontynuację. Proszę jednak o wyjasnienie: napisałeś w artykule że nie istnieją w powershellu właściwości prywatne po czym w „do zapamietania” napiałeś ze istnieją 3 rodzaje wlasciwosci: publiczne, statyczne i prywatne.
    Pozdrawiam

    • Adam Mrowicki
      Adam Mrowicki Autor wpisu

      W poprzedniej części pisałem już o właściwościach, nie wspominałem jednak o ich typie. Były to właściwości publiczne. Przy każdej definicji właściwości, jeśli nie podamy bezpośrednio jej typu, utworzymy właściwość publiczną. W tej części opisuję kolejne typy: statyczne i ukryte – w „Do zapamiętania” wkardł się błąd przy nazwie drugiego typu. Miałem oczywiście na myśli właściwości ukryte, a nie prywatne.

      • Pargo

        Dziękuję za wyjaśnienie i czekam na kolejne wpisy nt. klas w powershell. Możesz podać gdzie można poczytać na temat klas w PS coś więcej?

        • Adam Mrowicki
          Adam Mrowicki Autor wpisu

          Najwięcej informacji będzie oczywiście w dokumentacji: about_Classes oraz about_Classes_and_DSC. Drugi link zawiera więcej informacji związanych z DSC, ale znajduje się tam kilka fajnych przykładów z życia wziętych (wykorzystywanych właśnie w DSC).
          Na blogu Hey, Scripting Guy! znajduje się kilka wpisów w tym temacie: np. tu, tu lub ogólnie po tagu tutaj.