Статические свойства и методы
Мы также можем присвоить метод самому классу. Такие методы называются статическими.
В объявление класса они добавляются с помощью ключевого слова static , например:
class User < static staticMethod() < alert(this === User); >> User.staticMethod(); // true
Это фактически то же самое, что присвоить метод напрямую как свойство функции:
class User < >User.staticMethod = function() < alert(this === User); >;
Значением this при вызове User.staticMethod() является сам конструктор класса User (правило «объект до точки»).
Обычно статические методы используются для реализации функций, которые будут принадлежать классу в целом, но не какому-либо его конкретному объекту.
Звучит не очень понятно? Сейчас все встанет на свои места.
Например, есть объекты статей Article , и нужна функция для их сравнения.
Естественное решение – сделать для этого статический метод Article.compare :
class Article < constructor(title, date) < this.title = title; this.date = date; >static compare(articleA, articleB) < return articleA.date - articleB.date; >> // использование let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; articles.sort(Article.compare); alert( articles[0].title ); // CSS
Здесь метод Article.compare стоит «над» статьями, как средство для их сравнения. Это метод не отдельной статьи, а всего класса.
Другим примером может быть так называемый «фабричный» метод.
Скажем, нам нужно несколько способов создания статьи:
- Создание через заданные параметры ( title , date и т. д.).
- Создание пустой статьи с сегодняшней датой.
- …или как-то ещё.
Первый способ может быть реализован через конструктор. А для второго можно использовать статический метод класса.
Такой как Article.createTodays() в следующем примере:
class Article < constructor(title, date) < this.title = title; this.date = date; >static createTodays() < // помним, что this = Article return new this("Сегодняшний дайджест", new Date()); >> let article = Article.createTodays(); alert( article.title ); // Сегодняшний дайджест
Теперь каждый раз, когда нам нужно создать сегодняшний дайджест, нужно вызывать Article.createTodays() . Ещё раз, это не метод одной статьи, а метод всего класса.
Статические методы также используются в классах, относящихся к базам данных, для поиска/сохранения/удаления вхождений в базу данных, например:
// предположим, что Article - это специальный класс для управления статьями // статический метод для удаления статьи по id: Article.remove();
Статические методы недоступны для отдельных объектов
Статические методы могут вызываться для классов, но не для отдельных объектов.
Например. такой код не будет работать:
// . article.createTodays(); /// Error: article.createTodays is not a function
Статические свойства
Новая возможность
Эта возможность была добавлена в язык недавно. Примеры работают в последнем Chrome.
Статические свойства также возможны, они выглядят как свойства класса, но с static в начале:
class Article < static publisher = "Илья Кантор"; >alert( Article.publisher ); // Илья Кантор
Это то же самое, что и прямое присваивание Article :
Article.publisher = "Илья Кантор";
Наследование статических свойств и методов
Статические свойства и методы наследуются.
Например, метод Animal.compare в коде ниже наследуется и доступен как Rabbit.compare :
class Animal < constructor(name, speed) < this.speed = speed; this.name = name; >run(speed = 0) < this.speed += speed; alert(`$бежит со скоростью $.`); > static compare(animalA, animalB) < return animalA.speed - animalB.speed; >> // Наследует от Animal class Rabbit extends Animal < hide() < alert(`$прячется!`); > > let rabbits = [ new Rabbit("Белый кролик", 10), new Rabbit("Чёрный кролик", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Чёрный кролик бежит со скоростью 5.
Мы можем вызвать Rabbit.compare , при этом будет вызван унаследованный Animal.compare .
Как это работает? Снова с использованием прототипов. Как вы уже могли предположить, extends даёт Rabbit ссылку [[Prototype]] на Animal .
Так что Rabbit extends Animal создаёт две ссылки на прототип:
- Функция Rabbit прототипно наследует от функции Animal .
- Rabbit.prototype прототипно наследует от Animal.prototype .
В результате наследование работает как для обычных, так и для статических методов.
Давайте это проверим кодом:
class Animal <> class Rabbit extends Animal <> // для статики alert(Rabbit.__proto__ === Animal); // true // для обычных методов alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
Итого
Статические методы используются для функциональности, принадлежат классу «в целом», а не относятся к конкретному объекту класса.
Например, метод для сравнения двух статей Article.compare(article1, article2) или фабричный метод Article.createTodays() .
В объявлении класса они помечаются ключевым словом static .
Статические свойства используются в тех случаях, когда мы хотели бы сохранить данные на уровне класса, а не какого-то одного объекта.
class MyClass < static property = . ; static method() < . >>
Технически, статическое объявление – это то же самое, что и присвоение классу:
MyClass.property = . MyClass.method = .
Статические свойства и методы наследуются.
Для class B extends A прототип класса B указывает на A : B.[[Prototype]] = A . Таким образом, если поле не найдено в B , поиск продолжается в A .
Задачи
Класс расширяет объект?
важность: 3
Как мы уже знаем, все объекты наследуют от Object.prototype и имеют доступ к «общим» методам объекта, например hasOwnProperty .
class Rabbit < constructor(name) < this.name = name; >> let rabbit = new Rabbit("Rab"); // метод hasOwnProperty от Object.prototype alert( rabbit.hasOwnProperty('name') ); // true
Но что если мы явно напишем «class Rabbit extends Object» – тогда результат будет отличаться от обычного «class Rabbit» ?
Ниже пример кода с таким наследованием (почему он не работает? исправьте его):
class Rabbit extends Object < constructor(name) < this.name = name; >> let rabbit = new Rabbit("Кроль"); alert( rabbit.hasOwnProperty('name') ); // Ошибка
Сперва давайте разберёмся, почему код не работает.
Причина становится очевидна, если мы попытаемся запустить его. Унаследованный конструктор класса должен вызывать super() . В противном случае «this» будет не определён.
class Rabbit extends Object < constructor(name) < super(); // надо вызвать конструктор родителя, когда наследуемся this.name = name; >> let rabbit = new Rabbit("Кроль"); alert( rabbit.hasOwnProperty('name') ); // true
Но это ещё не все.
Даже после исправления есть важное различие между «class Rabbit extends Object» и class Rabbit .
Как мы знаем, синтаксис «extends» устанавливает 2 прототипа:
- Между «prototype» функций-конструкторов (для методов)
- Между самими функциями-конструкторами (для статических методов).
В случае с class Rabbit extends Object это значит:
class Rabbit extends Object <> alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true
Таким образом, Rabbit предоставляет доступ к статическим методам Object через Rabbit , например:
class Rabbit extends Object <> // обычно мы вызываем Object.getOwnPropertyNames alert( Rabbit.getOwnPropertyNames() ); // a,b
Но если явно не наследуем от объекта, то для Rabbit.__proto__ не установлено значение Object .
class Rabbit <> alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) alert( Rabbit.__proto__ === Function.prototype ); // как у каждой функции по умолчанию // ошибка - нет такой функции у Rabbit alert( Rabbit.getOwnPropertyNames() ); // Ошибка
Таким образом, в этом случае у Rabbit нет доступа к статическим методам Object .
Кстати, у Function.prototype также есть «общие» методы, такие как call , bind и т. д. Они в конечном итоге доступны в обоих случаях, потому что для встроенного конструктора Object Object.__proto__ === Function.prototype .
Пример на картинке:
Короче говоря, есть два отличия:
| class Rabbit | class Rabbit extends Object |
|---|---|
| – | необходимо вызвать super() в конструкторе |
| Rabbit.__proto__ === Function.prototype | Rabbit.__proto__ === Object |
NEWOBJ.ru → Введение в ООП с примерами на C# →
Статический метод ( static ) класса – метод, не имеющий доступа к состоянию (полям) объекта, то есть к переменной this .
Для объявления статического метода используется ключевое слово static :
private static float SquareGeron (float a, float b, float c) < /* … */ >
Статический метод может быть вызван как через экземпляр класса, так и через имя класса. Например, из методов класса Triangle мы можем обратиться к статическому методу SquareGeron следующими способами:
class Triangle < public void SomeMethod () < // 1 - Неявно через объект, то есть через переменную this. SquareGeron (1,2,2); // 2 - Явно через объект, то есть через переменную this. this.SquareGeron (1,2,2); // 3 - Через имя класса, без использования объекта. Square.SquareGeron (1,2,2); >>
Аналогично, извне класса, при условии, что мы сделаем метод SquareGeron открытым:
Square sq = new Square(); // 2 - Через объект. sq.SquareGeron (1,2,2); // 3 - Через имя класса. Square.SquareGeron(1,2,3);
Подчеркнем, что во всех перечисленных случаях метод не имеет доступа к переменной this , то есть даже при вызове через экземпляр, ему не передается, как для обычных методов, параметр this , он не знает, какой именно объект его вызвал и, соответственно, у него нет доступа к полям объекта. Также, как следствие, из статического метода нельзя вызвать нестатический метод того же класса для вызванного объекта. При этом, статический метод, как и любой другой метод класса, имеет доступ к полям и методам объектов этого класса независимо от их уровня видимости:
class Vector < private float x; private float y; public Vector() < >private static float Length (float x0, float y0, float x1, float у1) < /* . */ >public float Length () < // Из нестатического метода можно вызывать статический метод. return Length (0, 0, x, y); >public static Vector Sum (Vector a, Vector b) < Vector sum = new Vector(); // Статические метод, как и нестатический, // имеет доступ к полям, в том числе закрытым (private) // объектов того же класса: sum.x = a.x + b.x; sum.y = a.y + b.y; return sum; >>
Слово «статический» используется в том смысле, что статические методы не относятся к динамике объекта, не используют и не меняют его состояния.
Вспомним, что мы уже использовали статический метод для вычисления квадратного корня Math.Sqrt . Класс System.Math реализует в виде статических методов и другие математические функции. Однако есть множество причин, почему в большинстве случаев следует избегать использования статических методов.
Программисты, не имеющие опыта ООП, часто начинают широко использовать статические методы как способ программировать на объектно-ориентированном языке в процедурном стиле. Действительно, для статических методов их класс – лишь способ синтаксической группировки. Более того, использование статических методов – это всегда в некотором смысле отход от ООП, так как он делает невозможным использование всех ключевых элементов объектно-ориентированного программирования: абстрактных типов данных, наследования, полиморфизма. Сформулируем следующее правило: в первом приближении статическими следует делать только 1) небольшие 2) вспомогательные 3) закрытые ( private ) методы класса. Практически всегда методы, не удовлетворяющие приведенному правилу и не обращающиеся к полям объекта, можно и нужно вынести в отдельный класс. Например, если бы метод SquareGeron был большим, то следовало бы создать класс SquareGeronCalculator и создать там открытый метод Calc .
§ 25. Статические поля. Аналогично тому, как статический метод не привязан к объекту, мы можем объявить статическое поле, не являющееся частью никакого объекта, которое будет создаваться в одном экземпляре и будет доступно из любого объекта класса или через имя класса.
Например, следующий код считает число объектов типа Point , созданных с момента запуска приложения:
class Point < private static long newCount; public Point() < // Эквивалентно newCount++, но корректно работает в многопоточном окружении. Interlocked.Increment(ref newCount); >>
Экземпляр статической переменной создается автоматически до первого ее использования (когда именно – не регламентируется), а при создании экземпляров класса память для статических переменных не выделяется. Таким образом, в приведенном примере в некоторый момент времени после запуска приложения и до вызова команды увеличения значения newCount будет создан ровно один экземпляр этой переменной, а каждый создаваемый объект Point будет увеличивать ее значение в своем конструкторе.
Применительно к статическим полям также можно сформулировать правило: в первом приближении следует избегать использовать статические полей.
Причин тут много, в частности, создаются неявные связи между объектами одного класса. Также статические переменные не обрабатываются сборщиком мусора и существуют до закрытия программы, поэтому в крупных приложениях статические поля могут «связать» ощутимый объем памяти, освобождаемый только при закрытии программы.
Тем не менее, статические поля, как и статические методы, используются достаточно широко. Рассмотрим следующий типичный пример.
Положим, мы хотим реализовать авторизацию пользователей, то есть управлять доступностью функциональных возможностей приложения в зависимости от роли текущего пользователя. Для этого в приложении фиксируется перечень возможностей ( features ) и при вызове соответствующих методов, выполняется проверка, разрешен ли текущему пользователю доступ к запрашиваемой функциональной возможности. В первом приближении мы могли бы задать уникальное название для каждой функциональной возможности и привязывать к текущему пользователю перечень строк – список разрешенных возможностей. Тогда авторизация могла бы выглядеть следующим образом:
public class ACL // access control list < // Список возможностей, доступных текущему пользователю. private string[] currentUserAllowedFeatures; public void Authorize (string feature) < // Если у пользователь нет запрашиваемой возможности… if (!currentUserAllowedFeatures.Contains (feature)) // …формируем исключение. throw new Exception (); >> // Использование: public class SomeClass < private ACL acl; // Объект acl создается и инициализируется // где-то при запуске программы. public SomeClass (ACL acl) < this.acl = acl; >public void DoSomeJob () < // Сначала проверяем, если ли у текущего пользователя доступ // к указанной возможноти (ADMINISTRATOR). // Если доступа нет, исключение прервет выполнение метода. acl.Authorize (“ADMINISTRATOR”); // . >>
Проблема этого решения в том, что легко допустить ошибку в написании названия функциональной возможности. Однако мы можем перечислить допустимые названия как открытые неизменяемые статические поля некоторого класса:
public Feature < public static readonly string Administrator = “ADMINISTRATOR”; public static readonly string Guest = “GUEST”; // . >// Фрагмент из предыдущего листинга: public void DoSomeJob ()
Такое решение лучше, но оно все еще позволяет нам передать в метод Authorize произвольную строку, не используя класс Feature . Проанализируйте следующий код:
public Feature < private string code; private Feature (string code) < this.code = code; >public string GetCode() < return code; >public static readonly Feature Administrator = new Feature (“ADMINISTRATOR); public static readonly Feature Guest = new Feature (“GUEST”); // . > //class ACL public void Authorize (Feature feature) < if (!currentUserAllowedFeatures.Contains (feature.GetCode())) throw new Exception (); >// class SomeClass public void DoSomeJob ()
Теперь экземпляры класса Feature представляют отдельные функциональные возможности, но так как единственный конструктор этого класса объявлен как закрытый ( private ), то они не могут быть созданы извне класса. В статических открытых полях сохраняем фиксированный перечень экземпляров этого же класса. Таким образом, в метод Authorize мы передаем объект типа Feature , но не можем создать его самостоятельно, а используем перечень фиксированных, «зашитых» в классе Feature объектов.
Мы можем пойти еще дальше, сохраняя в классе ACL не массив строк, а массив объектов Features :
class ACL < private Feature[] currentUserAllowedFeatures; public void Authorize (Feature feature) < for (int i = 0; i < currentUserAllowedFeatures.Length; i++) < if (currentUserAllowedFeatures[i].GetCode() == feature.GetCode()) return; >throw new Exception (); > >
Такое решение делает безопасным не только передачу параметра в Authorize , но и формирование списка currentUserAllowedFeatures , который теперь также гарантированно не будет содержать произвольных строк.
В заключение, повторимся, что хотя механизм статических полей и методов широко применяется в современной практике объектно-ориентированного программирования, использовать эти возможности следует крайне осмотрительно. Дополнительные аргументы против статических методов и полей мы рассмотрим в последующих главах.
Вопросы и задания
Что такое статические методы, статические поля?
Верно ли говорить о статических классах или статических объектов? В чем их отличие (если верно) от неизменяемых классов и объектов?
Статический метод не имеет доступ к полям объекта, но имеет ли он доступ к полям объекта того же класса, переданного в параметрах этого метода?
В каком порядке следует использовать ключевые слова public/private и static ?
Сравните следующие поля класса:
public class SomeClass < static int field1 = 1; const int field2 = 2; readonly int field3 = 3; static readonly int field4 = 4; int field5 = 5; >
Когда происходит выделение памяти, создаются ли экземпляры полей для каждого экземпляра объекта, возможно ли изменение значений полей? Какие еще есть отличия между ними?
Статические поля и методы часто применяются для реализации объектов-одиночек 35 . Объект-одиночка ( singleton ) – объект, который должен существовать в программе в одном экземпляре. К примеру, это может быть объект, хранящий глобальные параметры приложения. Разберите следующие реализации:
// 1 вариант public class Settings < public string TempWorkspacePath() < /* . */ >public static Settings Singleton = new Settings(); > // Использование: string path = Settings.Singleton.TempWorkspacePath(); // 2 вариант public class Settings < public string TempWorkspacePath() < /* . */ >private static Settings singleton; private Settings () < singleton = new Settings(); >public Settings Get () < if (singleton == null) singleton = new Settings(); return singleton; >> // Использование: string path = Settings.Get().TempWorkspacePath();
Объясните, почему вторая реализация лучше? Почему во втором варианте используется закрытый ( private ) конструктор? Почему не используется ключевое слово readonly для поля singleton ?
* Объясните почему использование newCount++ , вместо метода Interlocked.Increment может привести к ошибкам (неверном подсчету).
* Изучите механизм перечислений в C# ( enum ).
- 35. Отметим попутно, что шаблон «одиночка» в современной практике часто стремятся заменять на шаблон «инверсии управления».↩︎
Статические методы — PHP: Введение в ООП
Статические методы — это почти то же самое, что и статические свойства, только методы.
class User private static $table = 'users'; public static function getTable() return self::$table; > > User::getTable(); // users
Статические методы, как и свойства, не принадлежат объектам, они — часть класса. Следовательно, из статического метода невозможно получить доступ к объекту (ведь нет никакого объекта) через $this . Внутри него $this просто не существует. Статические методы могут обращаться к другим статическим методам, статическим свойствам или константам, используя self .
Как я уже упоминал в предыдущем уроке, статические методы часто используют для доступа к приватным статическим свойствам. Причём, как геттеры, так и сеттеры, которые нужны редко, но все же бывают нужны.
Но есть ещё один способ использования статических методов, не связанный со статическими свойствами. Их используют как способ создать объект вместо прямого вызова конструктора через оператор new .
Как вы помните, PHP (как, впрочем, и любой динамический язык) позволяет иметь ровно один конструктор для класса. В случае таких данных, как время, это — серьёзное ограничение, потому, что нельзя одним конструктором описать все возможные способы создания дат, которые используются в коде.
$date = new DateTime('2000-01-01');
В стандартной библиотеке PHP есть класс DateTime , который принимает на вход строчку определённого формата и возвращает соответствующий объект. А что, если в нашей программе формат времени другой? А если у нас вообще нет строчки, а есть отдельные числа? Естественным желанием было бы иметь разные конструкторы под разные задачи. Их у нас нет, но зато есть статические методы, которых можно создать столько, сколько нужно.
// Специальная библиотека для работы с датами будет рассматриваться в следующем курсе $vancouverTimeRightNow = Carbon::now('America/Vancouver'); //implicit __toString() $noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); $internetWillBlowUpOn = Carbon::create(2038, 01, 19, 3, 14, 7, 'GMT');
Как видно из кода выше, статические методы имеют разные сигнатуры, но внутри они, так или иначе, вызывают конструктор, передавая туда уже подготовленные параметры. Конструктор можно вызывать двумя способами: первый — использовать полное имя класса, второй — через self . Второй способ предпочтительнее просто потому, что позволяет не дублировать имя класса.
class Carbon public static function now($timezone = '') return new self(date("Y-m-d H:i:s"), $timezone); > >
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Когда использовать статические методы
В обсуждениях к посту (перевод) о именованных конструкторах прозвучало мнение, что статические методы плохи и их вообще не стоит использовать. На мой взгляд, это слишком большое обобщение.
Статические методы по сути своей просто способ организации глобальных функций в пространства имен. Использование пространств имен, я думаю, вы согласитесь — хороший тон. Что касается глобальных функций — мы используем их всегда, встроенные функции PHP составляют основу нашего кода.
Основная проблема здесь — отсутствие совместно используемого глобального состояния. Вот пример из прошлого поста:
В данном примере возвращаемый результат свободен от побочных эффектов и вполне предсказуем, т.к. зависит только от аргументов, подаваемых на вход. Каждый раз при вызове метода вам будет возвращен идентичный результат (объект Time со значением 11:45), вне зависимости от состояния системы, контекста или чего-либо еще.
Другой пример:
И снова — результат предсказуем, Calculator::sum(1, 2); предоставляет нам сервис, не имеющий состояния, и не зависящий ни от чего, кроме аргументов. Более того, эта реализация не может быть полиморфной или иметь различные имплементации, т.к. любой результат кроме 3 будет ошибкой. Да, вы можете изменить внутреннюю реализацию метода, улучшив алгоритм сложения чисел, но это не должно никак отражаться на результате его использования.
Возьмем обратный пример, на этот раз с состоянием:
Пример элементарный, но в более сложных ситуациях это может быть не столь доходчиво. Представьте, что два разработчика используют в своем коде счетчики. Когда они тестируют свое решение изолированно — нет никаких проблем. Но после интеграции их решений счетчик начинает работать не так, как ожидалось, потому что используется глобальное состояние, вместо того, чтобы воспользоваться отдельным экземпляром счетчика.
Абстракция
Возможно, вы все еще чувствуете неприятие против кода, вроде Calculator::sum($x, $y) , т.к. мы не можем сымитировать или расширить его. Но не стоит забывать, что это довольно низкий уровень абстракции. Вы также не сможете сымитировать и расширить оператор + в PHP, но я не думаю, что вы когда-либо чувствовали потребность в этом. Если вам нужен более высокий уровень абстракции, то композиция — ваш верный спутник. Но хочу заметить, между Calculator::sum($x, $y) и + есть довольно интересное различие, первый может вот так, а второй нет:
Это все может показаться избыточным, но не стоит забывать про функции и статические методы, ведь они могут быть очень полезны при правильном их применении.
Часть 1: Как использовать именованные конструкторы в PHP
- Веб-разработка
- PHP
- Программирование