Зачем нужны интерфейсы php
Перейти к содержимому

Зачем нужны интерфейсы php

  • автор:

PHP: Что такое Интерфейс

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

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

Основы интерфейсов в PHP

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

Мы можем описать это следующим образом:

interface Vocalizer 

public function vocalize(string $message): string;
>

То о чём мы говорили выше: полученную строку $message , vocalize() вернёт то, что слышно, как строку.

Теперь интерфейс ничего не делает сам по себе. Он действует как тип PHP. Значит вы можете указать его в качестве аргумента или даже вернуть что-то в этом типе из функции или метода.

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

class Bird implements Vocalizer 

public function vocalize(string $message): string

return sprintf('%s', $message);
>
>

Допустим у нас есть следующая функция:

function prepareMessage(string $message, Vocalizer $vocalizer): string 

return $vocalizer->vocalize($message);
>

Вышеупомянутая функция может быть вызвана с любым $vocalizer реализующим Vocalizer :

$chickadee = new Bird(); 
echo prepareMessage('a song', $chickadee); // "a song"

Наследование и подстановка

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

В качестве примера предположим, что у нас есть класс Bird не реализующий Vocalizer , но у нас есть практически эквивалентная функциональность через метод tweet() :

class Bird 

public function tweet(string $message): string

return sprintf('%s', $message);
>
>

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

Во-первых, мы могли бы обновить класс, чтобы напрямую реализовать интерфейс Vocalizer :

class Bird implements Vocalizer 

public function vocalize(string $message): string

return $this->tweet($message);
>

public function tweet(string $message): string

return sprintf('%s', $message);
>
>

В качестве альтернативы мы могли бы создать расширение Bird , реализующее Vocalizer :

class VocalizingBird extends Bird implements Vocalizer 

public function vocalize(string $message): string

return $this->tweet($message);
>
>

Поскольку он является расширением Bird , он соответствует типу Bird для подсказок типа; поскольку он так же реализует Vocalizer , он также соответствует этому типу. Вы можете выполнить вышеуказанное с помощью реализации анонимного класса:

$bird = new class extends Bird implements Vocalizer  
public function vocalize(string $message): string

return $this->tweet($message);
>
>;

Это приводит нас к ключевому обоснованию использования интерфейсов: возможности замены одного типа другим.

Пять принципов объектно-ориентированного программирования

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

  • Single-responsibility principle — Принцип единой ответственности
  • Open-closed principle — Принцип Открытости/Закрытости
  • Liskov substitution principle — Принцип подстановки Барбары Лисков
  • Interface segregation principle — Принцип разделения интерфейса
  • Dependency inversion principle — Принцип инверсии зависимости

Принцип инверсии зависимости

На что я хочу обратить внимание, так это на принцип инверсии зависимости.

Принцип инверсии зависимости гласит, что мы должны зависеть от абстракций, а не от реализации. Что это значит?

Если нас беспокоит только озвучивание, нам не нужно беспокоиться о том, есть ли у нас Bird , Human или Animal . Мы должны беспокоиться о том, есть ли у нас что-то способное издавать звуки.

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

Распространённая ошибка при объектно-ориентированном программировании

Одной из распространённых ошибок при начале объектно-ориентированного программирования является создание строгого дерева наследования: базовый класс, затем подтипы этого базового класса, затем реализация этих подтипов и так далее.

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

Что можно определить в интерфейсе

Теперь, когда у нас есть общее представление, что такое интерфейс, зачем его использовать и как его реализовать, Что мы можем определить в интерфейсе?

Интерфейсы в PHP ограничены:

  • Публичными методами.
  • Публичными константами.

В предыдущей статье о PHP классах мы отметили, что видимость — более сложная тема. Это всё ещё так, но мы можем осветить некоторые основы. В двух словах, видимость помогает детализировать, что и где может использовать функциональность. К чему-то, что имеет публичную видимость, можно получить доступ как из методов класса, так и из экземпляров. Что подразумевается под последним? В следующем:

$chickadee = new Bird();

$chickadee — это экземпляр.

Отступление: Константы класса

В статье о классах мы не рассматривали константы классов. Константа похожа на обычную PHP константу в том смысле, что она детализирует значение, которое остаётся постоянным. Это значение нельзя переназначить позже. Для определения константы класса используется ключевое слово const в объявлении класса и, необязательно, оператор видимости (как у свойств и методов, видимость по умолчанию public ):

class Bird 

public const TAXONOMY_CLASS = 'aves';
>

Ссылаясь на константу используют имя класса и имя константы разделённые двоеточием : :

$taxonomy = Bird::TAXONOMY_CLASS;

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

По соглашению имена констант обычно пишут ЗАГЛАВНЫМИ БУКВАМИ с использованием подчёркивания _ в качестве разделителя слов.

Константы определённые в интерфейсе наследуются всеми реализациями.

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

Мы уже видели определение метода ранее, когда определяли метод vocalize() в интерфейсе Vocalizer :

public function vocalize(string $message): string;

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

Возможность реализации множественного наследования

Одной из возможностей, предлагаемых некоторыми языками, является множественное наследование. Эта возможность позволяет объекту расширять несколько других типов и, таким образом, выполнять их все. Например, класс Horse может расширять как класс Animal , так и класс Vehicle .

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

Пример реализации нескольких интерфейсов

Например, пакет laminas/laminas-cache определяет множество интерфейсов, описывающих возможности адаптера хранилища, включая FlushableInterface , OptimizableInterface , ClearByPrefixInterface , TaggableInterface , и т.д. Отдельные адаптеры — это всё адаптеры хранения, но они могут указывать, что у них есть другие возможности, реализуя эти интерфейсы.

Для реализации нескольких интерфейсов, вы предоставляете список интерфейсов, разделённых запятыми, после ключевого слова implements при объявлении вашего класса:

class MyCustomStorageAdapter extends AbstractAdapter implements 
ClearByStorageInterface,
FlushableInterface,
OptimizableInterface,
TaggableInterface

// . . .
>

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

Именование интерфейсов PHP

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

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

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

Вы и ваша команда должны решить, какой подход наиболее подходящий для вас!

Заключение

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

Зачем нужны интерфейсы php

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

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

Все методы, определённые в интерфейсах, должны быть общедоступными, что следует из самой природы интерфейса.

На практике интерфейсы используются в двух взаимодополняющих случаях:

  • Чтобы позволить разработчикам создавать объекты разных классов, которые могут использоваться взаимозаменяемо, поскольку они реализуют один и тот же интерфейс или интерфейсы. Типичный пример — несколько служб доступа к базе данных, несколько платёжных шлюзов или разных стратегий кеширования. Различные реализации могут быть заменены без каких-либо изменений в коде, который их использует.
  • Чтобы разрешить функции или методу принимать и оперировать параметром, который соответствует интерфейсу, не заботясь о том, что ещё может делать объект или как он реализован. Эти интерфейсы часто называют Iterable , Cacheable , Renderable и так далее, чтобы описать их поведение.

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

Замечание:

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

implements

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

Внимание

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

Замечание:

Интерфейсы могут быть унаследованы друг от друга, так же, как и классы, с помощью оператора extends.

Замечание:

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

Константы

Интерфейсы могут содержать константы. Константы интерфейсов работают точно так же, как и константы классов. До PHP 8.1.0 они не могли быть переопределены классом или интерфейсом, который их наследует.

Примеры

Пример #1 Пример интерфейса

// Объявим интерфейс ‘Template’
interface Template
public function setVariable ( $name , $var );
public function getHtml ( $template );
>

// Реализация интерфейса
// Это будет работать
class WorkingTemplate implements Template
private $vars = [];

public function setVariable ( $name , $var )
$this -> vars [ $name ] = $var ;
>

public function getHtml ( $template )
foreach( $this -> vars as $name => $value ) $template = str_replace ( » , $value , $template );
>

// Это не будет работать
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (Template::getHtml)
// (Фатальная ошибка: Класс BadTemplate содержит 1 абстрактный метод
// и поэтому должен быть объявлен абстрактным (Template::getHtml))
class BadTemplate implements Template
private $vars = [];

public function setVariable ( $name , $var )
$this -> vars [ $name ] = $var ;
>
>
?>

Пример #2 Наследование интерфейсов

interface B extends A
public function baz ( Baz $baz );
>

// Это сработает
class C implements B
public function foo ()
>

public function baz ( Baz $baz )
>
>

// Это не сработает и выдаст фатальную ошибку
class D implements B
public function foo ()
>

Пример #3 Совместимость с несколькими интерфейсами

class Foo <>
class Bar extends Foo <>

interface A public function myfunc ( Foo $arg ): Foo ;
>

interface B public function myfunc ( Bar $arg ): Bar ;
>

class MyClass implements A , B
public function myfunc ( Foo $arg ): Bar
return new Bar ();
>
>
?>

Пример #4 Множественное наследование интерфейсов

interface B
public function bar ();
>

interface C extends A , B
public function baz ();
>

class D implements C
public function foo ()
>

public function bar ()
>

Пример #5 Интерфейсы с константами

interface A
const B = ‘Константа интерфейса’ ;
>

// Выведет: Константа интерфейса
echo A :: B ;

class B implements A
const B = ‘Константа класса’ ;
>

// Выведет: Константа класса
// До PHP 8.1.0 этот код не будет работать,
// потому что было нельзя переопределять константы.
echo B :: B ;
?>

Пример #6 Интерфейсы с абстрактными классами

interface A
public function foo ( string $s ): string ;

public function bar ( int $i ): int ;
>

// Абстрактный класс может реализовывать только часть интерфейса.
// Классы, расширяющие абстрактный класс, должны реализовать все остальные.
abstract class B implements A
public function foo ( string $s ): string
return $s . PHP_EOL ;
>
>

class C extends B
public function bar ( int $i ): int
return $i * 2 ;
>
>
?>

Пример #7 Одновременное расширение и внедрение

// Порядок ключевых слов здесь важен. «extends» должно быть первым.
class Two extends One implements Usable , Updatable
/* . */
>
?>

Интерфейс, совместно с объявлениями типов, предоставляет отличный способ проверки того, что определённый объект содержит определённый набор методов. Смотрите также оператор instanceof и объявление типов.

Что обеспечивает interface и для чего он нужен Перевод

Создание и внедрение интерфейсов в наш код — это важно. Это помогает с подменой компонентов, облегчает тестирование, отделяет «что» от «как».

Но просто влепить интерфейс к классу и забыть — этого недостаточно.

Мы также должны подумать о том, на что мы «натянем» этот интерфейс.

Пример

Допустим, мы создаем систему очередей для чтения RSS канала. И нам нужно ставить в очередь URL-адреса. В зависимости от наших потребностей, в качестве механизма очередей мы можем использовать что-то вроде в RabbitMq или базы данных.

Мы ещё не решили точно, но в любом случае мы начнем с интерфейса для этой воображаемой очереди:

declare(strict_types=1); namespace Example\Infrastructure\Queue; use Example\Domain\Rss\FeedUrl; interface FeedUrlQueue

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

Через некоторое время мы решили, что для начала мы будем использовать базу данных в качестве механизма очередей. Соответственно, мы создаем реализацию FeedUrlQueue интерфейса:

declare(strict_types=1); namespace Example\Infrastructure\Storage\Database; use Example\Domain\Rss\FeedUrl; class FeedUrlTable extends AbstractTable implements FeedUrlQueue < public function add(FeedUrl $feedUrl) < $qb = $this->getQueryBuilder(); $query = $qb->insert('feed_urls') ->values( [ 'url' => '?', ] ) ->setParameter(0, (string) $feedUrl); $query->execute(); > >

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

А действительно ли она хорошо сделана?

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

Что-то тут нечисто

Для меня здесь есть три вещи, которые выбиваются, говоря мне, что что-то не так с этим кодом.

Во-первых, класс, который представляет собой Table , также является FeedUrlQueue . Тут действительно не должно быть двух вещей одновременно. Он либо должен быть очередью, либо таблицей и, безусловно, не тем и другим одновременно.

Во-вторых, класс, чья единственная ответственность должна быть сохранением URL-а в базе, – независимо от того, откуда этот URL приходит, – теперь ограничен сохранением URL-а RSS канала, который приходит из очереди. Ок, возможно (а может нет) это то ограничение, которое мы и хотим применить.

И в-третьих, он также несет ответственность за решение как преобразовать доменный объект FeedUrl в строку, которая можно сохранить в базе. Есть ли у него магический метод __toString , с помощью которого мы можем преобразовать его в строку? Или, может, это устаревший код с одним из тех методов toString() , которые нужно вызвать самим? Мы не узнаем, не взглянув.

Убиваем трех зайцев разом

Лучше было бы реализовать что-то вроде DatabaseFeedUrlQueue , который реализует FeedUrlQueue и использует FeedUrlTable :

declare(strict_types=1); namespace Example\Infrastructure\Queue; use Example\Domain\Rss\FeedUrl; class DatabaseFeedUrlQueue implements FeedUrlQueue < protected $table; public function __construct(FeedUrlTable $table) < $this->table = $table; > public function add(FeedUrl $feedUrl) < $payload = [ 'url' =>(string) $feedUrl ]; $this->table->save($payload); > >

а FeedUrlTable станет чем-то вроде этого:

declare(strict_types=1); namespace Example\Infrastructure\Storage\Database; class FeedUrlTable extends AbstractTable < public function save(array $payload) < $qb = $this->getQueryBuilder(); $query = $qb->insert('feed_urls') ->values( [ 'url' => '?', ] ) ->setParameter(0, $payload['url']); $query->execute(); > >

С помощью такого рефакторинга кода, как этот, мы в значительной степени исправили все три проблемы сразу:

  • DatabaseFeedUrlQueue — это FeedUrlQueue , а FeedUrlTable теперь не является двумя сущностями одновременно;
  • есть четкое разделение обязанностей: DatabaseFeedUrlQueue отвечает за создание хранимых данных, а FeedUrlTable обязан сохранять их;
  • слой хранения ничего не знает о наших доменных объектах и как их использовать.

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

Для чего нужны интерфейсы в PHP.

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

  • в базу данных;
  • в файл;
  • в сессию и др.

Для начала, давайте рассмотрим пример без использования интерфейса.

При создании приложения согласно ООП, нужно отделять логику от функционала, разбивая задачи на отдельные классы.
Допустим есть 3 класса:
— Date (общий класс, получающий данные и использующий методы вспомогательного класса(сервиса) для их сохранения);
— saveDb (класс сохраняющий данные в базу данных);
— saveFile (класс сохраняющий данные в файл)

class Date< //Db – класс сохраняющий данные в БД, $base – экземпляр этого класса public function date(saveDb $base)< $message = array(); //получение данных из формы $date = $this->validate($message); //валидация $base->insertInto(); //вызов методов класса Db для сохранения > public function validate($date) < //проверка данных return $date; >>

в данном примере, для сохранения данных, методу date() класса Date, в качестве аргумента, нужно передать экземпляр конкретного класса — saveDb или saveFile.

Недостаток данного подхода в том, что класс проверяющий данные и класс сохраняющий данные сильно связаны между собой. В примере, мы указываем, что аргумент метода date() — это экземпляр класса работающего с базой данных. Далее используются нужные методы данного класса (saveDb ) вроде insertInto().
А что, если понадобится сохранить данные не в БД, а в файл. В этом случае нужно будет менять весь метод date(), ведь у другого класса могут быть совсем другие методы, а не метод insertInto() и весь принцип построения другого класса может сильно отличаться. Может быть, там даже будет предусмотрена своя валидация и метод validate() класса Date окажется лишним.

Вот в таких случаях и используются интерфейсы. Они помогают в создании шаблонов для классов, которые будут использоваться в приложении и иметь общие методы делающие одни и те же задачи, но своим способом. Интерфейсы позволяют быстрее разобраться в коде и легче его поддерживать. Так же, они помогают создавать слабую связанность между классами, что можно продемонстрировать на примере:

interface Save < public function insert($date); >class saveDb implements Save < protected function connectDb()<>public function insert($date)< echo $date.' сохранено в БД 
'; > > class saveFile implements Save < protected function openFile()<>public function insert($date)< echo $date.' сохранено в файл
'; > protected function closeFile()<> > class saveSession implements Save < public function insert($date)< echo $date.' сохранено в сессию
'; > > //////////////////////////////////// class Date < public $date; public function __construct($date) < $this->date = $date; > // Save – интерфейс для сохранения данных, $obj – экземпляр одного из классов реализующих данный интерфейс. public function save(Save $obj)< $date = $this->validate($this->date); //валидация $obj->insert($date); //вызов методов класса Db для сохранения > public function validate($date) < // тут проверка данных return $date; >> $date = new Date('Контент'); $db = new saveDb(); $date->save($db); //Контент сохранено в БД $file = new saveFile(); $date->save($file); //Контент сохранено в файл $ses = new saveSession(); $date->save($ses); //Контент сохранено в сессию

В моем упрощенном примере видно, что интерфейс обязует реализующие его классы иметь метод insert(), который (допустим) непосредственно занимаются вставкой данных. А кроме него, каждый класс может иметь другие необходимые для его работы методы. Для класса saveDb, это, например, метод connectDb(), создающий соединение с базой данных. Для тестирования, я сделал вывод сообщений при срабатывании метода insert().

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

В данном примере, вместо зависимости от конкретного класса (saveDb) передается зависимость от интерфейса Save:

public function save(Save $obj)

Код класса Date не зависит от конкретного реализатора, а только от интерфейса. Стоит отметить, что данный пример построен на базе паттерна «Dependency injection», что переводится как «внедрение зависимости».
Интерфейс обеспечивает наличие указанных в нем методов во всех классах которые его реализуют. Поэтому можно быть уверенным, что вызов метода insert():

$obj->insert($date);

приведет к сохранению данных. При этом место хранения зависит от реализации данного метода в каждом из классов. Так, метод insert() класса saveDb сохраняет в базу данных, метод insert() класса saveFile сохраняет в файл и тд. Выполните код данного примера и увидите, что класс Date отработал со всеми классами интерфейса Save и при этом, код самого класса Date никак не пришлось менять.

Автор: Сергей Дата публикации: 03.06.2017

  • Пример простого REST API на PHP.
  • Использование YouTube Data API для своего WEB-сервиса. Выборка нужных видеозаписей, получение информации.
  • Определение страны и города посетителя по его IP. Расширение GeoIP.
  • Установка, настройка и базовое использование фреймворка для тестирования «Codeception».
  • Основы PHPUnit — 1 часть.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *