Главная > Powershell > Классы в Powershell. Ключевое слово static

Классы в Powershell. Ключевое слово static

Эта статья – продолжение предыдущей, посвящённой атрибутам членов класса, в частности сегодня поговорим про статические члены класса.

Особенностью статических членов является возможность использования их без создания экземпляра класса.

Скорее всего вы уже не раз сталкивались со статическими свойствами и методами классов .NET Framework, по крайней мере постоянные читатели моего блога неоднократно встречали их в моих статьях (все статьи, где упоминаются классы .NET Framework помечены тегом .NET).

Вызываются статические члены через символ двойного двоеточия после указания имени класса. Например:

PS C:\> [DateTime]::DaysInMonth(2016, 2)
29

PS C:\> [Math]::PI
3,14159265358979

Как не трудно догадаться, DaysInMonth() – это статический метод класса DateTime, вычисляющий количество дней в заданном месяце определённого года, а PI – не что иное как статическое свойство класса Math, хранящее значение числа Пи.

Как видно из примеров, для того, чтобы узнать количество дней в месяце, или вспомнить число Пи, нам не нужно создавать экземпляры классов – мы напрямую обращаемся к соответствующим методам и свойствам.

Статические члены класса, так же как и скрытые, по умолчанию не выводятся через командлет Get-Member. Для того, чтобы их вывести нужно указать параметр –Static.

Статические свойства – это, как правило, неизменяемые значения. Например:

PS C:\> [Math] | Get-Member -MemberType Properties -Static

   TypeName: System.Math

Name MemberType Definition
---- ---------- ----------
E    Property   static double E {get;}
PI   Property   static double PI {get;}

Обратите внимание, что в фигурных скобках указано get. Т.е. свойство доступно только для чтения.

Следует иметь в виду, что это не обязательно константа, а именно неизменяемое пользователем значение. Например, статический метод

[DateTime]::Now

покажет текущую дату и время с точностью до секунды, а через секунду – уже другое значение, т.е. мы можем это значение только прочитать, но не изменить.

К сожалению, в создаваемых нами классах, нет штатной возможности задать свойство, доступное только для чтения, но я нашёл “лайфхак”, который позволяет это исправить Smile. Конечно, не совсем правильно (не в смысле не корректно, а в с смысле “не по понятиям”), но на крайний случай пойдёт. Поделюсь в одной из следующих статей Smile. Остаётся надеться в следующих версиях Powershell добавят штатную возможность делать свойства, доступные только для чтения.

Для нашего класса, описывающего робота, статическим свойством может быть свойство, содержащее дату создания класса, эдакое начало времён для всех роботов из этого класса. Статические члены класса задаются при помощи ключевого слова static. Заодно исправим свойство, хранящее дату создания конкретного экземпляра класса, так как до сих пор его нужно было прописывать вручную:

# Дата создания объекта (для каждого своя)
[DateTime]$Birthday = (Get-Date)

# Статическое свойство
# Дата создания класса (у всех экземпляров этого класса одинаковая)
static [DateTime]$Inception = (Get-Date)

Однако, нужно учитывать, что здесь переменная $Inception хранит дату не именно создания класса, а дату первого обращения к классу, т.е. если сразу после создания класса создать экземпляр этого класса, то дата создания экземпляра класса будет совпадать с датой создания самого класса:

PS C:\> $Verter = New-Object Robot

PS C:\> $Verter.Birthday
5 апреля 2017 г. 11:34:37

PS C:\> $Verter::Inception
5 апреля 2017 г. 11:34:37

(Так как свойство Inception – статическое свойство, обращаться к нему нужно не через точку, а через два двоеточия.)

Для более корректных значений можно после создания класса, сразу обратиться к свойству Inception, и уже потом создавать экземпляры класса (для того, чтобы не засорять вывод можно вывести значение в Out-Null):

[Robot]::Inception | Out-Null

Как видно, мы обращаемся к свойству класса не создавая экземпляры этого класса. Если убрать командлет Out-Null в любой момент можно узнать дату создания класса. Как увидим ниже, значение этого свойства будет одинаковым для всех экземпляров класса.

Создаём двух роботов с небольшим интервалом:

$Walle = New-Object Robot
Start-Sleep -Seconds 10
$Eva = New-Object Robot

Теперь, если проверить свойства роботов…

PS C:\> $Walle.Birthday
5 апреля 2017 г. 11:59:08

PS C:\> $Walle::Inception
5 апреля 2017 г. 11:57:26

PS C:\> $Eva.Birthday
5 апреля 2017 г. 11:59:18

PS C:\> $Eva::Inception
5 апреля 2017 г. 11:57:26

мы увидим, что робот Walle на 10 секунд старше робота Eva (у каждого своё значение свойства Birthday), но отсчёт времени (если так можно выразиться) они ведут с одной даты (свойство Inception – одинаковое).

Если нас всех (человеков) рассматривать как экземпляры класса, то можно сказать, что свойство Inception для всех нас – это дата Рождества Христова.

И если нам взбредёт в голову переписать историю, то лёгким движением руки одной строкой можно изменить эту дату:

PS C:\> [Robot]::Inception = '2000-01-01'

PS C:\> $Walle::Inception
1 января 2000 г. 0:00:00

PS C:\> $Eva::Inception
1 января 2000 г. 0:00:00

 

Ко всем создаваемым нами классам, Powershell автоматически добавляет несколько статических методов,  среди которых есть метод New(), который можно использовать для создания объектов, наравне с командлетом New-Object. Классический способ, через New-Object работает немного быстрее:

PS C:\> (Measure-Command {[Robot]::new()}).Milliseconds
26
PS C:\> (Measure-Command {New-Object Robot}).Milliseconds
4

PS C:\> (Measure-Command {[Robot]::new()}).Milliseconds
28
PS C:\> (Measure-Command {New-Object Robot}).Milliseconds
3

Хотя как сказал недавно saw-friendship “в целом ситуация не так однозначна”:

PS C:\> (Measure-Command {[Robot]::new()}).Milliseconds
3
PS C:\> (Measure-Command {New-Object Robot}).Milliseconds
3

Удалось словить даже такую ситуацию:

PS C:\> (Measure-Command {[Robot]::new()}).Milliseconds
4
PS C:\> (Measure-Command {New-Object Robot}).Milliseconds
9

Так что для создания объектов можно использовать оба эти способа.

На сегодня всё. В следующих статьях продолжим осваивать классы в Powershell.

Напоследок как выглядит наш класс на данном этапе развития:

Class Robot
{
    # Свойства
    [string]$Name
    [int]$Id
    
    # Дата создания объекта (для каждого своя)
    [DateTime]$Birthday = (Get-Date)

    # Скрытое свойство
    hidden [int]$StepCount

    # Статическое свойство
    # Дата создания класса (у всех экземпляров этого класса одинаковая)
    static [DateTime]$Inception = (Get-Date)
    
    # Метод, вызывающий улыбку
    Smile()
    {
        Write-Host ':)'
    }
    
    # Метод - шаг
    Go([int]$Step) 
    { 
        Write-Host ('-'*$Step)
        $this.StepCount += $Step 
    }
}
Реклама
Рубрики:Powershell Метки: ,
  1. Андрей
    09/04/2017 в 15:42

    Приветствую, Сергей!
    Понравился Ваш курс статей о классах в PoSh 5.
    Нашел немного неудобств реализации, вполне возможно, что Вы меня подправите.

    Пример будет следующим. Пробую использовать PoSh + Selenium.
    В соответствии с паттерном PageObjects желательно разнести классы фреймворка, тестируемых сайтов и, собственно, тестов.
    Т.е. создание объектов и работа с ними будет происходить из другого скрипта, нежели тот, в котором описан класс.

    Итак. Есть модуль фреймворка в котором описаны различные предустановки-настройки, в том числе:

    using namespace OpenQA.Selenium;
    $global:EnterKey = @(
    [Keys]::Enter
    )

    class OpenDriver
    {
    static [IWebDriver] OpenChrome() {
    [IWebDriver] $ChromeDriver = New-Object Chrome.ChromeDriver;
    $ChromeDriver.Manage().Window.Maximize();
    return $ChromeDriver;
    }
    static [IWebDriver] OpenFF() {
    [IWebDriver] $FirefoxDriver = New-Object Firefox.FirefoxDriver;
    $FirefoxDriver.Manage().Window.Maximize();
    return $FirefoxDriver;
    }
    static [IWebDriver] OpenIE() {
    [IWebDriver] $InternetExplorerDriver = New-Object IE.InternetExplorerDriver;
    $InternetExplorerDriver.Manage().Window.Maximize();
    return $InternetExplorerDriver;
    }
    }

    function NewOpenDriver(){
    $global:OpenDriver = New-Object OpenDriver
    }

    По нему первые неудобства — вопросы (можно ли улучшить?) :
    1. Для того, чтобы была возможность создать объект из другого скрипта, пришлось добавить функцию NewOpenDriver() (про данное ограничение прочитал: https://powershell.org/2014/09/08/powershell-v5-class-support), хотя в данном случае можно было вообще обойтись одной короткой функцией.
    2. Всем функциям приходится присваивать «static», что обидно :), иначе они не видны из другого скрипта.

    Модуль pages, содержит работу с web-страницами:
    using namespace OpenQA.Selenium;

    [IWebDriver] $GoogleMainPageDriver = $null;
    class GoogleMainPage
    {
    GoogleMainPage([IWebDriver] $driver) {
    [IWebDriver] $script:GoogleMainPageDriver = $driver;
    }

    static [IWebElement] SearchInput(){
    return $script:GoogleMainPageDriver.FindElementById(«lst-ib»);
    }

    static [void] SearchForText([string] $text) {
    [GoogleMainPage]::SearchInput().SendKeys($text);
    [GoogleMainPage]::SearchInput().SendKeys($global:EnterKey);
    }
    }

    function NewGoogleMainPage([IWebDriver] $driver){
    $global:GoogleMainPage = New-Object GoogleMainPage ($driver)
    }

    3. Здесь основная «засада» с использованием переменной/атрибута $GoogleMainPageDriver. Можно объявить ее в классе, но тогда обращение к ней хотят вида $this.GoogleMainPageDriver причем так не работает для функций static.
    4. Эстетически не нравится мне этот длинный вариант вызова:
    [GoogleMainPage]::SearchInput().SendKeys(), но по другому, увы, не получилось, а хотелось бы что-то вроде: SearchInput.SendKeys().

    Собственно сам тест:
    using namespace OpenQA.Selenium;
    Add-Type -Path «.\WebDriver.dll»
    $env:PATH += «;.\» #в рабочей папке лежит кроме WebDriver.dll еще и драйвер (chromedriver.exe)
    NewOpenDriver
    [IWebDriver] $ChromeDrv = $OpenDriver::OpenChrome()
    $ChromeDrv.Navigate().GoToURL(«https://www.google.ru»)
    NewGoogleMainPage($ChromeDrv)
    $GoogleMainPage::SearchForText(«Music»)

    Заранее спасибо за комментарии.

    • 10/04/2017 в 12:32

      К сожалению, я не знаком с Selenium, поэтому не совcем понял о чём идёт речь, но пара комментариев у меня есть…

      1. Что значит улучшить? Что Вас не устраивает? Как по мне очень аккуратненький класс 🙂 Разве что точку с запятой в конце строки можно не ставить.
      2. «Всем функциям приходится присваивать «static», что обидно» — чего обидно? Вы имеете что-то против статических членов? 🙂

      3. Да, this и static вместе не работают, пока не придумал можно-ли здесь что-то сделать

      4. «Эстетически не нравится мне этот длинный вариант вызова:
      [GoogleMainPage]::SearchInput().SendKeys(), но по другому, увы, не получилось, а хотелось бы что-то вроде: SearchInput.SendKeys().» —
      не знаю получится, или нет, но попробуйте назначить алиас команде: http://bit.ly/2nwNPzm

      ‘$env:PATH += «;.\» #в рабочей папке лежит кроме WebDriver.dll еще и драйвер (chromedriver.exe)’ — если я правильно понял, Вы текущую папку добавляете в переменную окружения PATH, так? Т.е. при каждом запуске скрипта будет разрастаться переменная PATH. Оно-то хоть и действует только до конца сессии, но мне кажется всё равно не есть хорошо. И вообще я не понял, зачем это делать.

  2. Андрей
    10/04/2017 в 17:09

    Собственно Selenium было «о наболевшем», можно взять любой простой пример, хотя бы тот, что в Вашей статье и попробовать поработать с этим классом из другого скрипта.
    1. Неприятно, что для создания объекта при этом приходится использовать аппарат функций, надеюсь, что поправят в следующем релизе.
    2. Обидно, что, например, функции Вашего скрипта также не получится использовать из другого, без добавления в них static.
    3. В вашем примере будут аналогичные проблемы с функцией Go. В общем попробуйте со своим классом, немного свыкнетесь с моей «болью»)
    4. Алиасы, возможно, сработают, но задавать их для всех классов, как-то утомительно.

    Насчет добавления пути — это на самом деле еще один скрипт «preload», запускается лишь один раз, сразу после старта PoSh. Нужно как раз, чтобы был доступ к драйверам браузера. Просто показалось, что упоминать целых 4 скрипта в одном вопросе перебор 🙂

  3. Андрей
    12/04/2017 в 09:52

    Хотя) Не удобно только при использовании аппарата модулей.
    Если Ваш класс оформить как 1.ps1, а в скрипте 2.ps1 задотсорсить его вызов:

    . .\1.ps1
    $Verter = New-Object Robot
    $Verter.Birthday
    $Verter.Smile()
    $Verter.Go(5)

    все нормально отрабатывает.
    Сорри, если навел панику 🙂

    • 12/04/2017 в 14:10

      Круто! Даже стыдно немного, что сам не додумался 🙂

  1. No trackbacks yet.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: