Объектно-ориентированный PHP

Объектно-ориентированное (сокращённо OO) — это идея и методология программирования, которая инкапсулирует данные в программе вместе с методами, работающими с этими данными, образуя «объекты», и выполняет функции программы посредством взаимодействия и передачи сообщений между объектами. Объектно-ориентированное программирование подчёркивает такие особенности, как инкапсуляция, наследование, полиморфизм и динамическое связывание, делая программу более расширяемой, поддерживаемой и повторно используемой.

В объектно-ориентированном программировании (англ.: Object-oriented programming, сокращённо OOP) объект — это целое, состоящее из информации и описания обработки этой информации, и он является абстракцией реального мира.

В реальном мире всё, с чем мы сталкиваемся, является объектом, например компьютеры, телевизоры, велосипеды и так далее.

Три основных характеристики объекта:

  • Поведение объекта: операции, которые объект может выполнять. Например, включение света или выключение света — это поведение.
  • Форма объекта: то, как объект реагирует на различные поведения, например цвет, размер и внешний вид.
  • Представление объекта: представление объекта эквивалентно идентификационной карте, которая конкретно различает различия между объектами с одинаковым поведением и состоянием.

Например, Animal — это абстрактный класс. Мы можем конкретизировать его как собаку и овцу. Собака и овца — это конкретные объекты. Они имеют атрибуты цвета и состояния поведения, такие как лай и бег.

Три основных характеристики объектно-ориентированного программирования:

  • Инкапсуляция: означает объединение свойств и методов объекта вместе, так что извне нельзя напрямую получить доступ к внутреннему состоянию объекта или изменить его. Путём использования модификаторов контроля доступа (public, private, protected) для ограничения доступа к свойствам и методам достигается инкапсуляция.

  • Наследование: означает возможность создать новый класс, который наследует свойства и методы родительского класса и может добавлять свои собственные свойства и методы. Благодаря наследованию можно избежать повторного написания похожего кода и достичь повторного использования кода.

  • Полиморфизм: означает возможность использовать переменную типа родительского класса для ссылки на объекты разных типов подклассов, тем самым обеспечивая единообразные операции над разными объектами. Полиморфизм делает код более гибким, с лучшей расширяемостью и сопровождаемостью. В PHP полиморфизм может быть достигнут путем реализации интерфейсов и использования абстрактных классов.


Содержание объектно-ориентированного программирования

  • Класс− Определяет абстрактные характеристики вещи. Определение класса включает форму данных и операции над данными.

  • Объект− Является экземпляром класса.

  • Переменные-члены− Переменные, определенные внутри класса. Значение переменной не видно извне, но к нему можно получить доступ через функции-члены. После создания экземпляра класса переменная становится свойством объекта.

  • Функции-члены− Определены внутри класса, их можно использовать для доступа к данным объекта.

  • Наследование− Наследование — это механизм, посредством которого подкласс автоматически наследует структуры данных и методы родительского класса; это связь между классами. При определении и реализации класса вы можете делать это на основе существующего класса, принимая содержимое, определенное существующим классом, как свое собственное, и добавляя некоторое новое содержимое.

  • Родительский класс− Если класс наследуется другими классами, такой класс можно назвать родительским классом, базовым классом или суперклассом.

  • Дочерний класс− Класс, который наследует другой класс, называется дочерним классом, также известным как производный класс.

  • Полиморфизм− Полиморфизм означает, что одна и та же функция или метод может действовать с несколькими типами объектов и получать разные результаты. Разные объекты, получающие одно и то же сообщение, могут выдавать разные результаты; это явление называется полиморфизмом.

  • Перегрузка− Проще говоря, это ситуация, когда функции или методы имеют одинаковое имя, но разные списки параметров. Такие функции или методы с одинаковым именем, но разными параметрами называются перегруженными функциями или методами.

  • Абстракция− Абстракция означает выделение объектов с согласованными структурами данных (атрибутами) и поведением (операциями) в классы. Класс и есть такая абстракция; он отражает важные свойства, относящиеся к приложению, игнорируя другое нерелевантное содержимое. Любая классификация классов субъективна, но она должна быть связана с конкретным приложением.

  • Инкапсуляция− Инкапсуляция означает связывание атрибутов и поведения объекта, существующего в реальном мире, вместе и помещение их в логическую единицу.

  • Конструктор− В основном используется для инициализации объекта при его создании, то есть для присвоения начальных значений переменным-членам объекта. Он всегда используется вместе с оператором new в инструкциях, создающих объекты.

  • Деструктор− Деструктор противоположен конструктору. Когда объект завершает свой жизненный цикл (например, когда функция, содержащая объект, завершает выполнение), система автоматически выполняет деструктор. Деструктор часто используется для выполнения работы по «очистке» (например, если при создании объекта с помощью new была выделена область памяти, её следует освободить с помощью delete в деструкторе перед выходом).

На рисунке ниже мы создали три объекта с использованием класса Car: Mercedes, Bmw и Audi.

$mercedes = new Car ();
$bmw = new Car ();
$audi = new Car ();


Определение класса в PHP

Обычный синтаксис определения класса в PHP выглядит следующим образом:

<?php
class phpClass {
  var $var1;
  var $var2 = "constant string";
  
  function myfunc ($arg1, $arg2) {
     [..]
  }
  [..]
}
?>

Анализ выглядит следующим образом:

  • Классы определяются с использованиемclassключевого слова class, за которым следует имя класса.

  • Переменные и методы можно определять внутри пары фигурных скобок ({}) после имени класса.

  • Переменные класса объявляются с помощьюvar, а переменные также можно инициализировать значениями.

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

Пример

<?php
class Site {
  /* 成员变量 */
  var $url;
  var $title;
  
  /* 成员函数 */
  function setUrl($par){
     $this->url = $par;
  }
  
  function getUrl(){
     echo $this->url . PHP_EOL;
  }
  
  function setTitle($par){
     $this->title = $par;
  }
  
  function getTitle(){
     echo $this->title . PHP_EOL;
  }
}
?>

Переменная$this$this представляет сам объект.

PHP_EOL— это символ новой строки.


Создание объектов в PHP

После создания класса мы можем использоватьnewоператор, чтобы создать экземпляр класса:

$example = new Site;
$taobao = new Site;
$google = new Site;

В приведённом выше коде мы создали три объекта. Каждый из этих трёх объектов независим. Далее давайте посмотрим, как получить доступ к методам-членам и переменным-членам.

Вызов методов-членов

После создания экземпляра объекта мы можем использовать объект для вызова методов-членов. Методы-члены объекта могут работать только с переменными-членами объекта:

// 调用成员函数,设置标题和URL
$example->setTitle( "Example" );
$taobao->setTitle( "淘宝" );
$google->setTitle( "Google 搜索" );

$example->setUrl( 'www.example.com' );
$taobao->setUrl( 'www.taobao.com' );
$google->setUrl( 'www.google.com' );

// 调用成员函数,获取标题和URL
$example->getTitle();
$taobao->getTitle();
$google->getTitle();

$example->getUrl();
$taobao->getUrl();
$google->getUrl();

Полный код выглядит следующим образом:

Пример

<?php
class Site {
  
/* Переменные-члены */
  
var $url;
  var
$title;
  
  
/* Функции-члены */
  
function setUrl($par){
    
$this->url = $par;
  }
  
  function
getUrl(){
     echo
$this->url . PHP_EOL;
  }
  
  function
setTitle($par){
    
$this->title = $par;
  }
  
  function
getTitle(){
     echo
$this->title . PHP_EOL;
  }
}

$example = new Site;
$taobao = new Site;
$google = new Site;

// Вызов функций-членов для установки заголовка и URL
$example->setTitle( Пример);
$taobao->setTitle( Taobao);
$google->setTitle( Поиск Google);

$example->setUrl( 'www.example.com' );
$taobao->setUrl( 'www.taobao.com' );
$google->setUrl( 'www.google.com' );

// Вызов функций-членов для получения заголовка и URL
$example->getTitle();
$taobao->getTitle();
$google->getTitle();

$example->getUrl();
$taobao->getUrl();
$google->getUrl();
?>

Запустить пример »

Выполнение приведенного выше кода дает следующий вывод:

Example
淘宝
Google 搜索
www.example.com
www.taobao.com
www.google.com

Конструктор PHP

Конструктор — это специальный метод. Он в основном используется для инициализации объекта при его создании, то есть для присвоения начальных значений переменным-членам объекта. В инструкции, создающей объект, вместе сnewоператором.

PHP 5 позволяет разработчикам определять метод в классе как конструктор. Синтаксис следующий:

void __construct ([ mixed $args [, $... ]] )

В приведённом выше примере мы можем инициализировать переменные $url и $title через конструктор:

function __construct( $par1, $par2 ) {
   $this->url = $par1;
   $this->title = $par2;
}

Теперь нам больше не нужно вызывать методы setTitle и setUrl:

Пример

$example = new Site('www.example.com', 'Example');
$taobao = new Site('www.taobao.com', 'Taobao');
$google = new Site('www.google.com', 'Google Search');

// Вызов функций-членов для получения заголовка и URL
$example->getTitle();
$taobao->getTitle();
$google->getTitle();

$example->getUrl();
$taobao->getUrl();
$google->getUrl();

Запустить пример »

Деструктор

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

В PHP 5 была введена концепция деструктора, аналогичная другим объектно-ориентированным языкам. Его синтаксис выглядит следующим образом:

void __destruct ( void )

Пример

<?php
class MyDestructableClass {
   function __construct() {
       print "构造函数\n";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "销毁 " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass();
?>

Выполнение приведенного выше кода дает следующий вывод:

构造函数
销毁 MyDestructableClass

Наследование

PHP использует ключевое словоextendsдля наследования класса. PHP не поддерживает множественное наследование, формат следующий:

class Child extends Parent {
   // 代码部分
}

Пример

В примере класс Child_Site наследует класс Site и расширяет его функциональность:

<?php 
// 子类扩展站点类别
class Child_Site extends Site {
   var $category;

    function setCate($par){
        $this->category = $par;
    }
  
    function getCate(){
        echo $this->category . PHP_EOL;
    }
}

Переопределение методов

Если методы, унаследованные от родительского класса, не могут удовлетворить потребности дочернего класса, их можно переопределить. Этот процесс называется переопределением методов (override).

В примере методы getUrl и getTitle переопределены:

function getUrl() {
   echo $this->url . PHP_EOL;
   return $this->url;
}
   
function getTitle(){
   echo $this->title . PHP_EOL;
   return $this->title;
}

Управление доступом

PHP реализует управление доступом к свойствам или методам, добавляя перед ними ключевые слова public, protected или private.

  • public:К открытым членам класса можно получить доступ откуда угодно.
  • protected:К защищённым членам класса можно получить доступ из самого класса, а также из его дочерних классов и родительского класса.
  • private:К приватным членам класса можно получить доступ только из класса, в котором они определены.

Управление доступом к свойствам

Свойства класса должны быть определены как public, protected или private. Если они определены с помощью var, они рассматриваются как public.

<?php
/**
 * Define MyClass
 */
class MyClass
{
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj = new MyClass();
echo $obj->public; // 这行能被正常执行
echo $obj->protected; // 这行会产生一个致命错误
echo $obj->private; // 这行也会产生一个致命错误
$obj->printHello(); // 输出 Public、Protected 和 Private


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // 可以对 public 和 protected 进行重定义,但 private 而不能
    protected $protected = 'Protected2';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj2 = new MyClass2();
echo $obj2->public; // 这行能被正常执行
echo $obj2->private; // 未定义 private
echo $obj2->protected; // 这行会产生一个致命错误
$obj2->printHello(); // 输出 Public、Protected2 和 Undefined

?>

Управление доступом к методам

Методы в классе могут быть определены как public, private или protected. Если ни одно из этих ключевых слов не задано, метод по умолчанию является public.

<?php
/**
 * Define MyClass
 */
class MyClass
{
    // 声明一个公有的构造函数
    public function __construct() { }

    // 声明一个公有的方法
    public function MyPublic() { }

    // 声明一个受保护的方法
    protected function MyProtected() { }

    // 声明一个私有的方法
    private function MyPrivate() { }

    // 此方法为公有
    function Foo()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate();
    }
}

$myclass = new MyClass;
$myclass->MyPublic(); // 这行能被正常执行
$myclass->MyProtected(); // 这行会产生一个致命错误
$myclass->MyPrivate(); // 这行会产生一个致命错误
$myclass->Foo(); // 公有,受保护,私有都可以执行


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // 此方法为公有
    function Foo2()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate(); // 这行会产生一个致命错误
    }
}

$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 这行能被正常执行
$myclass2->Foo2(); // 公有的和受保护的都可执行,但私有的不行

class Bar 
{
    public function test() {
        $this->testPrivate();
        $this->testPublic();
    }

    public function testPublic() {
        echo "Bar::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Bar::testPrivate\n";
    }
}

class Foo extends Bar 
{
    public function testPublic() {
        echo "Foo::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Foo::testPrivate\n";
    }
}

$myFoo = new foo();
$myFoo->test(); // Bar::testPrivate 
                // Foo::testPublic
?>

Интерфейсы

Используя интерфейсы, можно указать, какие методы должен реализовать класс, не определяя конкретное содержимое этих методов.

Интерфейс определяется с использованием ключевого словаinterface, так же как при определении стандартного класса, но все методы, определённые в нём, пусты.

Все методы, определённые в интерфейсе, должны быть public; это особенность интерфейсов.

Чтобы реализовать интерфейс, используйте операторimplements. Класс должен реализовать все методы, определённые в интерфейсе, иначе будет сообщено о фатальной ошибке. Класс может реализовать несколько интерфейсов, разделяя имена нескольких интерфейсов запятыми.

<?php

// 声明一个'iTemplate'接口
interface iTemplate
{
    public function setVariable($name, $var);
    public function getHtml($template);
}


// 实现接口
class Template implements iTemplate
{
    private $vars = array();
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
  
    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
        }
 
        return $template;
    }
}

Константы

В классе можно определять значения, которые остаются постоянными, как константы. При определении и использовании констант символ $ не нужен.

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

Начиная с PHP 5.3.0, переменную можно использовать для динамического вызова класса. Однако значением этой переменной не может быть ключевое слово (например, self, parent или static).

Пример

<?php
class MyClass
{
    const constant = '常量值';

    function showConstant() {
        echo  self::constant . PHP_EOL;
    }
}

echo MyClass::constant . PHP_EOL;

$classname = "MyClass";
echo $classname::constant . PHP_EOL; // 自 5.3.0 起

$class = new MyClass();
$class->showConstant();

echo $class::constant . PHP_EOL; // 自 PHP 5.3.0 起
?>

Абстрактные классы

Любой класс, имеющий хотя бы один метод, объявленный как abstract, сам должен быть объявлен как abstract.

Класс, определённый как abstract, не может быть инстанцирован.

Методы, определённые как abstract, только объявляют сигнатуру метода (параметры) и не могут определять его конкретную функциональную реализацию.

При наследовании от абстрактного класса дочерний класс должен определить все абстрактные методы родительского класса. Кроме того, уровень доступа этих методов должен быть таким же, как (или более широким, чем) в родительском классе. Например, если абстрактный метод объявлен как protected, то метод, реализованный в дочернем классе, должен быть объявлен как protected или public и не может быть определён как private.

<?php
abstract class AbstractClass
{
 // 强制要求子类定义这些方法
    abstract protected function getValue();
    abstract protected function prefixValue($prefix);

    // 普通方法(非抽象方法)
    public function printOut() {
        print $this->getValue() . PHP_EOL;
    }
}

class ConcreteClass1 extends AbstractClass
{
    protected function getValue() {
        return "ConcreteClass1";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass1";
    }
}

class ConcreteClass2 extends AbstractClass
{
    public function getValue() {
        return "ConcreteClass2";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass2";
    }
}

$class1 = new ConcreteClass1;
$class1->printOut();
echo $class1->prefixValue('FOO_') . PHP_EOL;

$class2 = new ConcreteClass2;
$class2->printOut();
echo $class2->prefixValue('FOO_') . PHP_EOL;
?>

Выполнение приведенного выше кода дает следующий вывод:

ConcreteClass1
FOO_ConcreteClass1
ConcreteClass2
FOO_ConcreteClass2

Кроме того, методы дочернего класса могут содержать необязательные параметры, которых нет в абстрактных методах родительского класса.

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

<?php
abstract class AbstractClass
{
    // 我们的抽象方法仅需要定义需要的参数
    abstract protected function prefixName($name);

}

class ConcreteClass extends AbstractClass
{

    // 我们的子类可以定义父类签名中不存在的可选参数
    public function prefixName($name, $separator = ".") {
        if ($name == "Pacman") {
            $prefix = "Mr";
        } elseif ($name == "Pacwoman") {
            $prefix = "Mrs";
        } else {
            $prefix = "";
        }
        return "{$prefix}{$separator} {$name}";
    }
}

$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";
?>

Результат вывода:

Mr. Pacman
Mrs. Pacwoman

Ключевое слово static

Объявление свойства или метода класса как static позволяет обращаться к ним напрямую без создания экземпляра класса.

К статическим свойствам нельзя получить доступ через экземпляр класса (но к статическим методам — можно).

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

К статическим свойствам нельзя получить доступ из объекта с помощью оператора ->.

Начиная с PHP 5.3.0, переменную можно использовать для динамического вызова класса. Однако значением этой переменной не могут быть ключевые слова self, parent или static.

<?php
class Foo {
  public static $my_static = 'foo';
  
  public function staticValue() {
     return self::$my_static;
  }
}

print Foo::$my_static . PHP_EOL;
$foo = new Foo();

print $foo->staticValue() . PHP_EOL;
?>    

Выполнение вышеуказанной программы дает следующий вывод:

foo
foo

Ключевое слово final

В PHP 5 добавлено ключевое слово final. Если метод в родительском классе объявлен как final, дочерний класс не может его переопределить. Если класс объявлен как final, от него нельзя наследоваться.

При выполнении следующего кода возникнет ошибка:

<?php
class BaseClass {
   public function test() {
       echo "BaseClass::test() called" . PHP_EOL;
   }
   
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called"  . PHP_EOL;
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called"  . PHP_EOL;
   }
}
// 报错信息 Fatal error: Cannot override final method BaseClass::moreTesting()
?>

Вызов конструктора родителя

PHP не вызывает автоматически конструктор родительского класса в конструкторе дочернего класса. Чтобы выполнить конструктор родительского класса, нужно вызвать его в конструкторе дочернего класса:parent::__construct() 。

<?php
class BaseClass {
   function __construct() {
       print "BaseClass 类中构造方法" . PHP_EOL;
   }
}
class SubClass extends BaseClass {
   function __construct() {
       parent::__construct();  // 子类构造方法不能自动调用父类的构造方法
       print "SubClass 类中构造方法" . PHP_EOL;
   }
}
class OtherSubClass extends BaseClass {
    // 继承 BaseClass 的构造方法
}

// 调用 BaseClass 构造方法
$obj = new BaseClass();

// 调用 BaseClass、SubClass 构造方法
$obj = new SubClass();

// 调用 BaseClass 构造方法
$obj = new OtherSubClass();
?>

Выполнение вышеуказанной программы дает следующий вывод:

BaseClass 类中构造方法
BaseClass 类中构造方法
SubClass 类中构造方法
BaseClass 类中构造方法
Другие расширения