Войти
Android, Windows, Apple, Ликбез. Социальные сети. Драйверы
  • Японские телефоны Новый японский смартфон
  • Lenovo G500S: характеристики, основные особенности
  • Определяем серию продукта видеокарт Nvidia Характеристики карты nvidia 9800 gt
  • А конкуренты у смартфона есть
  • Что такое расширение файла TRZ?
  • Не работает динамик в "айфоне"
  • Различия между ранним и поздним связыванием в Java. Статическое связывание и динамическое связывание Php позднее статическое связывание

    Различия между ранним и поздним связыванием в Java. Статическое связывание и динамическое связывание Php позднее статическое связывание

    Связывание в языке C++

    Двумя основными целями при разработке языка программирования С++ были эффективное использование памяти и скорость выполнения. Он был задуман как усовершенствование языка С, в частности, для объектно-ориентированных приложений. Основной принцип С++: никакое свойство языка не должно приводить к возникновению дополнительных издержек (как по памяти, так и по скорости), если данное свойство программистом не используется. Например, если вся объектная ориентированность С++ игнорируется, то оставшаяся часть должна работать так же быстро, как и традиционный С. Поэтому неудивительно что большинство методов в С++ связываются статически (во время компиляции), а не динамически (во время выполнения).

    Связывание методов в этом языке является довольно сложным. Для обычных переменных (не указателей или ссылок) оно осуществляется статически. Но когда объекты обозначаются с помощью указателей или ссылок, используется динамическое связывание. В последнем случае решение о выборе метода статического или динамического типа диктуется тем, описан ли соответствующий метод с помощью ключевого слова virtual. Если он объявлен именно так, то метод поиска сообщения базируется на динамическом классе, если нет на статическом. Даже в тех случаях, когда используется динамическое связывание, законность любого запроса определяется компилятором на основе статического класса получателя.

    Рассмотрим, например, следующее описание классов и глобальных переменных: class Mammal

    printf («cant speak»);

    class Dog: public Mammal

    printf («wouf wouf»);

    printf («wouf wouf, as well»);

    Mammal *fido = new Dog;

    Выражение fred.speak() печатает «cant speak», однако вызов fido->speak() также напечатает «cant speak», поскольку соответствующий метод в классе Mammal не объявлен как виртуальный. Выражение fido->bark() не допускается компилятором, даже если динамический тип для fido класс Dog. Тем не менее статический тип переменной всего лишь класс Mammal.

    Если мы добавим слово virtual:

    virtual void speak()

    printf («cant speak»);

    то получим на выходе для выражения fido->speak() ожидаемый результат.

    Относительно недавнее изменение в языке С++ добавление средств для распознавания динамического класса объекта. Они образуют систему RTTI (Run-Time Type Identification идентификация типа во время выполнения).

    В системе RTTI каждый класс имеет связанную с ним структуру типа typeinfo, которая кодирует различную информацию о классе. Поле данных name одно из полей данных этой структуры содержит имя класса в виде текстовой строки. Функция typeid может использоваться для анализа информации о типе данных. Следовательно, следующая ниже команда будет печатать строку «Dog» динамический тип данных для fido. В этом примере необходимо разыменовывать переменную-указатель fido, чтобы аргумент был значением, на которое ссылается указатель, а не самим указателем:

    cout << «fido is a» << typeid(*fido).name() << endl;

    Можно также спросить, используя функцию-член before, соответствует ли одна структура с информацией о типе данных подклассу класса, соотносящегося с другой структурой. Например, следующие два оператора выдают true и false:

    if (typeid(*fido).before (typeid(fred)))…

    if (typeid(fred).before (typeid(lassie)))…

    До появления системы RTTI стандартный программистский трюк состоял в том, чтобы явным образом закодировать в иерархии класса методы быть экземпляром. Например, для проверки значения переменных типа Animal на принадлежность к типу Cat или к типу Dog можно было бы определить следующую систему методов:

    virtual int isaDog()

    virtual int isaCat()

    class Dog: public Mammal

    virtual int isaDog()

    class Cat: public Mammal

    virtual int isaCat()

    Теперь для определения того, является ли текущим значением переменной fido величина типа Dog, можно использовать команду fido->isaDog(). Если возвращается ненулевое значение, то можно привести тип переменной к нужному типу данных.

    Возвращая указатель, а не целое число, мы объединяем проверку на принадлежность к подклассу и приведение типа. Это аналогично другой части системы RTTI, называемой dynamic_cast, которую мы вкратце опишем. Если некая функция в классе Mammal возвращает указатель на Dog, то класс Dog должен быть предварительно описан. Результатом присваивания является либо нулевой указатель, либо правильная ссылка на класс Dog. Итак, проверка результата все еще должна осуществляться, но мы исключаем необходимость приведения типа. Это показано в следующем примере:

    class Dog; // предварительное описание

    virtual Dog* isaDog()

    virtual Cat* isaCat()

    class Dog: public Mammal

    virtual Dog* isaDog()

    class Cat: public Mammal

    virtual Cat* isaCat()

    Оператор lassie = fido->isaDog(); теперь выполним всегда. В результате переменная lassie получает ненулевое значение, только если fido имеет динамический класс Dog. Если fido не принадлежит Dog, то переменной lassie будет присвоен нулевой указатель.

    lassie = fido->isaDog();

    … // fido и в самом деле относится к типу Dog

    … // присваивание не сработало

    … // fido не принадлежит к типу Dog

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

    Поскольку подобные проблемы встречаются часто, было найдено их общее решение. Функция шаблона dynamic_cast берет тип в качестве аргумента шаблона и, в точности как функция, определенная выше, возвращает либо значение аргумента (если приведение типа законно), либо нулевое значение (если приведение типа неразрешено). Присваивание, эквивалентное сделанному в предыдущем примере, может быть записано так:

    // конвертировать только в том случае, если fido является собакой

    lassie = dynamic_cast < Dog* > (fido);

    // затем проверить, выполнено ли приведение

    В язык C++ были добавлены еще три типа приведения (static_cast, const_cast и reinterpret_cast), но они используются в особых случаях и поэтому здесь не описываются. Программистам рекомендуется применять их как более безопасные средства вместо прежнего механизма приведения типов.

    2. Проектная часть

    Данный параграф , несмотря на краткость, является очень важным – практически все профессиональное программирование в Java основано на использовании полиморфизма. В то же время эта тема является одной из наиболее сложных для понимания учащимися. Поэтому рекомендуется внимательно перечитать этот параграф несколько раз.

    Методы классов помечаются модификатором static не случайно – для них при компиляции программного кода действует статическое связывание . Это значит, что в контексте какого класса указано имя метода в исходном коде, на метод того класса в скомпилированном коде и ставится ссылка . То есть осуществляется связывание имени метода в месте вызова с исполняемым кодом этого метода. Иногда статическое связывание называют ранним связыванием , так как оно происходит на этапе компиляции программы. Статическое связывание в Java используется еще в одном случае – когда класс объявлен с модификатором final ("финальный", "окончательный").

    Методы объектов в Java являются динамическими, то есть для них действует динамическое связывание . Оно происходит на этапе выполнения программы непосредственно во время вызова метода, причем на этапе написания данного метода заранее неизвестно, из какого класса будет проведен вызов. Это определяется типом объекта, для которого работает данный код - какому классу принадлежит объект , из того класса вызывается метод. Такое связывание происходит гораздо позже того, как был скомпилирован код метода. Поэтому такой тип связывания часто называют поздним связыванием .

    Программный код, основанный на вызове динамических методов , обладает свойством полиморфизма – один и тот же код работает по -разному в зависимости от того, объект какого типа его вызывает, но делает одни и те же вещи на уровне абстракции, относящейся к исходному коду метода.

    Для пояснения этих не очень понятных при первом чтении слов рассмотрим пример из предыдущего параграфа – работу метода moveTo. Неопытным программистам кажется, что этот метод следует переопределять в каждом классе-наследнике. Это действительно можно сделать, и все будет правильно работать. Но такой код будет крайне избыточным – ведь реализация метода будет во всех классах-наследниках Figure совершенно одинаковой:

    public void moveTo(int x, int y){ hide(); this.x=x; this.y=y; show(); };

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

    Еще часто вызывает недоумение, зачем в абстрактном классе Figure писать реализацию данного метода. Ведь используемые в нем вызовы методов hide и show , на первый взгляд, должны быть вызовами абстрактных методов – то есть, кажется, вообще не могут работать!

    Но методы hide и show являются динамическими, а это, как мы уже знаем, означает, что связывание имени метода и его исполняемого кода производится на этапе выполнения программы. Поэтому то, что данные методы указаны в контексте класса Figure , вовсе не означает, что они будут вызываться из класса Figure ! Более того, можно гарантировать, что методы hide и show никогда не будут вызываться из этого класса. Пусть у нас имеются переменные dot1 типа Dot и circle1 типа Circle , и им назначены ссылки на объекты соответствующих типов. Рассмотрим, как поведут себя вызовы dot1.moveTo(x1,y1) и circle1.moveTo(x2,y2) .

    При вызове dot1.moveTo(x1,y1) происходит вызов из класса Figure метода moveTo . Действительно, этот метод в классе Dot не переопределен, а значит, он наследуется из Figure . В методе moveTo первый оператор – вызов динамического метода hide . Реализация этого метода берется из того класса, экземпляром которого является объект dot1 , вызывающий данный метод. То есть из класса Dot . Таким образом, скрывается точка. Затем идет изменение координат объекта, после чего вызывается динамический метод show . Реализация этого метода берется из того класса, экземпляром которого является объект dot1 , вызывающий данный метод. То есть из класса Dot . Таким образом, на новом месте показывается точка.

    Для вызова circle1.moveTo(x2,y2) все абсолютно аналогично – динамические методы hide и show вызываются из того класса, экземпляром которого является объект circle1 , то есть из класса Circle . Таким образом, скрывается на старом месте и показывается на новом именно окружность .

    То есть если объект является точкой, перемещается точка. А если объект является окружностью - перемещается окружность . Более того, если когда-нибудь кто-нибудь напишет, например, класс Ellipse , являющийся наследником Circle , и создаст объект Ellipse ellipse=new Ellipse(…) , то вызов ellipse.moveTo(…) приведет к перемещению на новое место эллипса. И происходить это будет в соответствии с тем, каким образом в классе Ellipse реализуют методы hide и show . Заметим, что работать будет давным-давно скомпилированный полиморфный код класса Figure . Полиморфизм обеспечивается тем, что ссылки на эти методы в код метода moveTo в момент компиляции не ставятся – они настраиваются на методы с такими именами из класса вызывающего объекта непосредственно в момент вызова метода moveTo .

    В объектно-ориентированных языках программирования различают две разновидности динамических методов – собственно динамические и виртуальные . По принципу работы они совершенно аналогичны и отличаются только особенностями реализации. Вызов виртуальных методов быстрее. Вызов динамических медленнее, но служебная таблица динамических методов ( DMT – Dynamic Methods Table ) занимает чуть меньше памяти, чем таблица виртуальных методов ( VMT – Virtual Methods Table ).

    Может показаться, что вызов динамических методов неэффективен с точки зрения затрат по времени из-за длительности поиска имен. На самом деле во время вызова поиска имен не делается, а используется гораздо более быстрый механизм, использующий упомянутую таблицу виртуальных (динамических) методов. Но мы на особенностях реализации этих таблиц останавливаться не будем, так как в Java нет различения этих видов методов.

    6.8. Базовый класс Object

    Класс Object является базовым для всех классов Java . Поэтому все его поля и методы наследуются и содержатся во всех классах. В классе Object содержатся следующие методы:

    • public Boolean equals(Object obj) – возвращает true в случае, когда равны значения объекта, из которого вызывается метод, и объекта, передаваемого через ссылку obj в списке параметров. Если объекты не равны, возвращается false . В классе Object равенство рассматривается как равенство ссылок и эквивалентно оператору сравнения "==" . Но в потомках этот метод может быть переопределен, и может сравнивать объекты по их содержимому. Например, так происходит для объектов оболочечных числовых классов. Это легко проверить с помощью такого кода:

      Double d1=1.0,d2=1.0; System.out.println("d1==d2 ="+(d1==d2)); System.out.println("d1.equals(d2) ="+(d1.equals(d2)));

      Первая строка вывода даст d1==d2 =false , а вторая d1. equals (d2) =true

    • public int hashCode() – выдает хэш-код объекта. Хэш-кодом называется условно уникальный числовой идентификатор, сопоставляемый какому-либо элементу. Из соображений безопасности выдавать адрес объекта прикладной программе нельзя. Поэтому в Java хэш-код заменяет адрес объекта в тех случаях, когда для каких-либо целей надо хранить таблицы адресов объектов.
    • protected Object clone() throws CloneNotSupportedException – метод занимается копированием объекта и возвращает ссылку на созданный клон (дубликат) объекта. В наследниках класса Object его обязательно надо переопределить, а также указать, что класс реализует интерфейс Clonable . Попытка вызова метода из объекта, не поддерживающего клонирования , вызывает возбуждение исключительной ситуации CloneNotSupportedException ("Клонирование не поддерживается"). Про интерфейсы и исключительные ситуации будет рассказано в дальнейшем.

      Различают два вида клонирования : мелкое (shallow ), когда в клон один к одному копируются значения полей оригинального объекта, и глубокое (deep ), при котором для полей ссылочного типа создаются новые объекты, клонирующие объекты, на которые ссылаются поля оригинала. При мелком клонировании и оригинал, и клон будут ссылаться на одни и те же объекты. Если объект имеет поля только примитивных типов , различия между мелким и глубоким клонированием нет. Реализацией клонирования занимается программист, разрабатывающий класс, автоматического механизма клонирования нет. И именно на этапе разработки класса следует решить, какой вариант клонирования выбирать. В подавляющем большинстве случаев требуется глубокое клонирование .

    • public final Class getClass() – возвращает ссылку на метаобъект типа класс. С его помощью можно получать информацию о классе, к которому принадлежит объект, и вызывать его методы класса и поля класса .
    • protected void finalize() throws Throwable – вызывается перед уничтожением объекта. Должен быть переопределен в тех потомках Object , в которых требуется совершать какие-либо вспомогательные действия перед уничтожением объекта (закрыть файл, вывести сообщение, отрисовать что-либо на экране, и т.п.). Подробнее об этом методе говорится в соответствующем параграфе.
    • public String toString() – возвращает строковое представление объекта (настолько адекватно, насколько это возможно). В классе Object этот метод реализует выдачу в строку полного имени объекта (с именем пакета), после которого следует символ "@" , а затем в шестнадцатеричном виде хэш-код объекта. В большинстве стандартных классов этот метод переопределен. Для числовых классов возвращается строковое представление числа, для строковых – содержимое строки, для символьного – сам символ (а не строковое представление его кода!). Например, следующий фрагмент кода

      Object obj=new Object(); System.out.println(" obj.toString() дает "+obj.toString()); Double d=new Double(1.0); System.out.println(" d.toString()дает "+d.toString()); Character c="A"; System.out.println("c.toString() дает "+c.toString());

      обеспечит вывод

      obj.toString() дает java.lang.Object@fa9cf d.toString()дает 1.0 c.toString()дает A

    Также имеются методы notify() , notifyAll() , и несколько перегруженных вариантов метода wait , предназначенные для работы с потоками (threads). О них говорится в разделе, посвященном потокам.

    6.9. Конструкторы. Зарезервированные слова super и this. Блоки инициализации

    Как уже говорилось, объекты в Java создаются с помощью зарезервированного слова new , после которого идет конструктор – специальная подпрограмма , занимающаяся созданием объекта и инициализацией полей создаваемого объекта. Для него не указывается тип возвращаемого значения, и он не является ни методом объекта (вызывается через имя класса когда объекта еще нет), ни методом класса (в конструкторе доступен объект и его поля через ссылку this ). На самом деле конструктор в сочетании с оператором new возвращает ссылку на создаваемый объект и может считаться особым видом методов, соединяющим в себе черты методов класса и методов объекта.

    Если в объекте при создании не нужна никакая дополнительная инициализация , можно использовать конструктор , который по умолчанию присутствует для каждого класса. Это имя класса , после которого ставятся пустые круглые скобки – без списка параметров. Такой конструктор при разработке класса задавать не надо, он присутствует автоматически.

    Если требуется инициализация , обычно применяют конструкторы со списком параметров. Примеры таких конструкторов рассматривались нами для классов Dot и Circle . Классы Dot и Circle были унаследованы от абстрактных классов , в которых не было конструкторов. Если же идет наследование от неабстрактного класса, то есть такого, в котором уже имеется конструктор (пусть даже и конструктор по умолчанию), возникает некоторая специфика. Первым оператором в конструкторе должен быть вызов конструктора из суперкласса . Но его делают не через имя этого класса, а с помощью зарезервированного слова super (от " superclass "), после которого идет необходимый для прародительского конструктора список параметров. Этот конструктор инициализирует поля данных, которые наследуются от суперкласса (в том числе и от всех более ранних прародителей). Например, напишем класс FilledCircle - наследник от Circle , экземпляр которого будет отрисовываться как цветной круг.

    package java_gui_example; import java.awt.*; public class FilledCircle extends Circle{ /** Creates a new instance of FilledCircle */ public FilledCircle(Graphics g,Color bgColor, int r,Color color) { super(g,bgColor,r); this.color=color; } public void show(){ Color oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size,size); graphics.setColor(oldC); graphics.setPaintMode(); } public void hide(){ Color oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size,size); graphics.setColor(oldC); graphics.setPaintMode(); }}

    Вообще, логика создания сложно устроенных объектов: родительская часть объекта создается и инициализируется первой, начиная от части, доставшейся от класса Object , и далее по иерархии, заканчивая частью, относящейся к самому классу. Именно поэтому обычно первым оператором конструктора является вызов прародительского конструктора super (список параметров ), так как обращение к неинициализированной части объекта, относящейся к ведению прародительского класса, может привести к непредсказуемым последствиям.

    В данном классе мы применяем более совершенный способ отрисовки и "скрывания" фигур по сравнению с предыдущими классами. Он основан на использовании режима рисования XOR ("исключающее или"). Установка этого режима производится методом setXORMode . При этом повторный вывод фигуры на то же место приводит к восстановлению первоначального изображения в области вывода. Переход в обычный режим рисования осуществляется методом setPaintMode .

    В конструкторах очень часто используют

    Связывание - подстановка в коды программы вызовов конкретных функций -методов класса . Имеет смысл только для производных классаов.

    Обычно компилятор имеет необходимую информацию для того, чтобы определить, какая функция имеется в виду. Например, если в программе встречается вызов obj.f(), компилятор однозначно выбирает функцию f()в зависимости от типа адресатаobj. Если в программе используются указатели на экземпляры класса:ptr->f(), выбор функции - метода класса определяется типом указателя.

    Если выбор функции выполняется на этапе компиляции, мы имеем дело со статическим связыванием .

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

    Если выбор функции выполняется на этапе выполнения программы, мы имеем дело с динамическим связыванием .

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

    Виртуальные функции

    По умолчанию для производных классов устанавливается статичесое связывание. Если для каких-либо методов класса нужно использовать динамическое связывание, такие методы должны быть объявлены виртуальными .

    Виртуальные функции:

      имеют в прототипе в базовом классе ключевое слово virtual;

      обязательно функции-члены класса:

      Во всех производных классах должны иметь такой же прототип (указание слова virtualв производных классах не обязательно).

    Если какие-либо методы в производных классах имеют то же имя, что и в базовом классе, но другой список параметров, мы имеем делео с перегруженными функциями.

    Пример: классы Точка и Окружность.

    virtual void print();

    class Circle: public Point{

    void print(); // можно virtual void print();

    void Point::print()

    cout << "Point (" << x << ", " << y << ")";

    void Circle::print()

    cout << "Circle with center in "; Point::print();

    cout << "and radius " << rad;

    Использование:

    Point p1(3,5), p2(1,1), *pPtr;

    Cicle c1(1), c2(p2, 1);

    pPtr = &p1; pPtr->print(); // получим: Point (3, 5)

    pPtr = &c2; pPtr->print(); // получим:

    Circle with center in Point (1, 1) and radius 1

    Пример использования динамического связывания: список

    Наиболее часто динамичесое связывание используется с контейнерными классами, содержащими указатель на базовый класс; в такие контейнерные классы можно включать информацию, относящуюся и к базовому, и к любым производным классам.

    Рассмотрим пример - список, содержащий и точки, и окружности.

    // конструктор

    Item():info(NULL), next(NULL){}

    Item(Point *p):info(p), next(NULL){}

    List():head(NULL){}

    void insert(Point *p){p->next = head; head = p;}

    void List::print()

    for(Item *cur = head; cur; cur = cur->next){

    cur->info->print();

    cout << endl;

    Использование класса:

    Point *p = new Point(1,2);

    mylist.insert(p);

    p = new Cicle(1,2,1);

    mylist.insert(p);

    Circle with center in Point (1, 2) and radius 1

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

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

    Чрезвычайно распространенным примером являются устройства, использующие системы GNU/Linux, используя Busybox . Я сделал это до крайности с помощью NetBSD путем создания загрузочного образа системы i386 (32-разрядной версии), который включает как ядро, так и его корневую файловую систему, которая содержит один статический связанный (через crunchgen) двоичный код с жесткими ссылками на все программы, которые содержат all (ну при последнем счете 274) (большая часть, за исключением инструментальной цепочки), и это меньше, чем 20 мега байтов (и, вероятно, очень удобно работает в системе с 64 МБ памяти (даже с корневой файловой системой без сжатия и полностью в ОЗУ), хотя я не смог найти такой маленький, чтобы проверить его).

    В предыдущих сообщениях упоминалось, что время запуска статических связанных двоичных файлов выполняется быстрее (и это может быть намного быстрее), но это только часть изображения, особенно когда весь объектный код связан в тот же файл, а тем более, когда операционная система поддерживает запрос подкачки кода непосредственно из исполняемого файла. В этом идеальном сценарии время запуска программ буквально незначительно, поскольку почти все страницы кода уже находятся в памяти и будут использоваться оболочкой (и и init любыми другими фоновыми процессами, которые могут быть запущены), даже если запрошенная программа никогда не запускалась с момента загрузки, поскольку, возможно, загружена только одна страница памяти для выполнения требований к времени выполнения программы.

    Однако это еще не вся история. Я также обычно создаю и использую установку операционной системы NetBSD для своих полных систем разработки путем статического связывания всех двоичных файлов. Несмотря на то, что для этого требуется огромное количество дискового пространства (~ 6,6 ГБ всего для x86_64 со всем, включая toolchain и X11 static-linked) (особенно если вы сохраняете полные таблицы символов отладки, доступные для всех программ еще на ~ 2,5 ГБ), результат все же работает быстрее в целом, и для некоторых задач даже используется меньше памяти, чем типичная динамически связанная система, предназначенная для обмена библиотечными кодовыми страницами. Диск дешевый (даже быстрый диск), а память для кэширования часто используемых файлов на диске также относительно дешева, но CPU-циклы действительно не являются, а оплата начального заработка ld.so для каждого процесса, который начинается каждый раз, когда он начинается, займет часы и часы циклов процессора от задач, требующих запуска многих процессов, особенно когда одни и те же программы используются снова и снова, например, компиляторы в системе разработки. Статические связанные программные программы могут сократить время создания многоадресной архитектуры для всей системы за часы. Мне еще предстоит построить toolchain в моем одиночном двоичном файле crunchgen "ed, но я подозреваю, что когда я это сделаю, будет больше времени на сборку, сохраненное из-за выигрыша для кэша процессора.

    (Late Static Binding, LSB) является бурно темой обсуждений последние три года в кругах разработчиков PHP (и наконец мы его получили в PHP 5.3). Но зачем оно нужно? В данной статье, как раз и будет рассматриваться, как позднее статическое связывание может значительно упростить ваш код.

    На встрече разработчиков PHP, которая проходила в Париже в ноябре 2005 года, тема позднего статического связывания официально обсуждалась основной командой разработчиков. Они согласились реализовать его, наряду со многими другими темами, которые стояли на повестке дня. Детали должны были быть согласованы в ходе открытых дискуссий.

    С тех пор как позднее статическое связывание было объявлено как грядущая фишка, прошло два года. И вот наконец LSB стало доступно для использования в PHP 5.3. Но это событие прошло незаметно для разработчиков использующих PHP, из заметок только страничка в мануале .

    Если кратко, новая функциональность позднего статического связывания, позволяет объектам все также наследовать методы у родительских классов, но помимо этого дает возможность унаследованным методам иметь доступ к статическим константам, методам и свойствам класса потомка, а не только родительского класса. Давайте рассмотрим пример:

    Class Beer { const NAME = "Beer!"; public function getName() { return self::NAME; } } class Ale extends Beer { const NAME = "Ale!"; } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getName() ."\n"; echo "Ale is: " . $aleDrink->getName() ."\n";

    Этот код выдаст такой результат:

    Beer is: Beer! Ale is: Beer!

    Класс Ale унаследовал метод getName() , но при этом self все еще указывает на класс в котором оно используется (в данном случае это класс Beer ). Это осталось и в PHP 5.3, но добавилось слово static . И снова рассмотрим пример:

    Class Beer { const NAME = "Beer!"; public function getName() { return self::NAME; } public function getStaticName() { return static::NAME; } } class Ale extends Beer { const NAME = "Ale!"; } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getName() ."\n"; echo "Ale is: " . $aleDrink->getName() ."\n"; echo "Beer is actually: " . $beerDrink->getStaticName() ."\n"; echo "Ale is actually: " . $aleDrink->getStaticName() ."\n";

    Новое ключевое слово static указывает, что необходимо использовать константу унаследованного класса, вместо константы которая была определена в классе где объявлен метод getStaticName() . Слово static было добавлено, чтобы реализовать новый функционал, а для обратной совместимости self работает также как и в предыдущих версиях PHP.

    Внутренне, основное отличие (и, собственно, причина почему связывание назвали поздним) между этими двумя способами доступа, в том, что PHP определят значение для self::NAME во время «компиляции» (когда симовлы PHP преобразуются в машинный код, который будет обрабатываться движком Zend), а для static::NAME значение будет определено в момент запуска (в тот момент, когда машинный код будет выполнятся в движке Zend).

    Это еще один инструмент для PHP-разработчиков. Во второй части рассмотрим как его можно использовать во благо.