Могу ли я расширить класс, используя более 1 класса в PHP?
Если у меня есть несколько классов с функциями, которые мне нужны, но я хочу хранить их отдельно для организации, могу ли я расширить класс, чтобы иметь оба?
т.е. class a extends b extends c
редактировать: я знаю, как расширять классы по одному, но я ищу способ мгновенного расширения класса, используя несколько базовых классов - AFAIK, вы не можете сделать это в PHP, но должны быть способы обойти это, не прибегая к class c extends b
, class b extends a
19 ответов
Отвечая на ваши изменения:
Если вы действительно хотите подделать множественное наследование, вы можете использовать магическую функцию __call().
Это уродливо, хотя работает с точки зрения пользователя класса А:
class B {
public function method_from_b($s) {
echo $s;
}
}
class C {
public function method_from_c($s) {
echo $s;
}
}
class A extends B
{
private $c;
public function __construct()
{
$this->c = new C;
}
// fake "extends C" using magic function
public function __call($method, $args)
{
$this->c->$method($args[0]);
}
}
$a = new A;
$a->method_from_b("abc");
$a->method_from_c("def");
Отпечатки "abcdef"
Вы не можете иметь класс, который расширяет два базовых класса. Вы не могли бы иметь.
// this is NOT allowed (for all you google speeders)
Matron extends Nurse, HumanEntity
Вы могли бы, однако, иметь иерархию следующим образом...
Matron extends Nurse
Consultant extends Doctor
Nurse extends HumanEntity
Doctor extends HumanEntity
HumanEntity extends DatabaseTable
DatabaseTable extends AbstractTable
и так далее.
Вы можете использовать черты, которые, будем надеяться, будут доступны в PHP 5.4.
Черты - это механизм повторного использования кода в языках с одним наследованием, таких как PHP. Черта предназначена для уменьшения некоторых ограничений одиночного наследования, позволяя разработчику свободно использовать наборы методов в нескольких независимых классах, находящихся в разных иерархиях классов. Семантика комбинации признаков и классов определяется таким образом, что уменьшает сложность и позволяет избежать типичных проблем, связанных с множественным наследованием и миксинами.
Они признаны за их потенциал в поддержке лучшей композиции и повторного использования, следовательно, их интеграция в более новые версии языков, таких как Perl 6, Squeak, Scala, Slate и Fortress. Черты также были портированы на Java и C#.
Дополнительная информация: https://wiki.php.net/rfc/traits
Принятый в настоящее время ответ @Franck будет работать, но на самом деле это не множественное наследование, а дочерний экземпляр класса, определенный вне области, также есть __call()
стенография - рассмотрите возможность использования только $this->childInstance->method(args)
везде, где вам нужен метод класса ExternalClass в "расширенном" классе.
Точный ответ
Нет, вы не можете, соответственно, не очень, как руководство extends
Ключевое слово говорит:
Расширенный класс всегда зависит от одного базового класса, то есть множественное наследование не поддерживается.
Реальный ответ
Однако, как правильно подсказал @adam, это НЕ запрещает вам использовать множественное иерархическое наследование.
Вы МОЖЕТЕ продлить один класс, с другим и другой с другим и так далее...
Вот довольно простой пример:
class firstInheritance{}
class secondInheritance extends firstInheritance{}
class someFinalClass extends secondInheritance{}
//...and so on...
Важная заметка
Как вы могли заметить, множественное (2+) интегрирование по иерархии можно выполнять только в том случае, если у вас есть контроль над всеми классами, включенными в процесс - это означает, что вы не можете применять это решение, например, со встроенными классами или с классами, которые вы используете. просто не можете редактировать - если вы хотите это сделать, у вас остается решение @Franck - дочерние экземпляры.
... И, наконец, пример с некоторым выводом:
class A{
function a_hi(){
echo "I am a of A".PHP_EOL."<br>".PHP_EOL;
}
}
class B extends A{
function b_hi(){
echo "I am b of B".PHP_EOL."<br>".PHP_EOL;
}
}
class C extends B{
function c_hi(){
echo "I am c of C".PHP_EOL."<br>".PHP_EOL;
}
}
$myTestInstance = new C();
$myTestInstance->a_hi();
$myTestInstance->b_hi();
$myTestInstance->c_hi();
Какие выводы
I am a of A
I am b of B
I am c of C
Классы не должны быть просто коллекциями методов. Предполагается, что класс представляет абстрактную концепцию с состоянием (полями) и поведением (методами), которое изменяет состояние. Использование наследования просто для получения желаемого поведения звучит как плохой дизайн ОО, и именно поэтому многие языки запрещают множественное наследование: чтобы предотвратить "наследование спагетти", то есть расширение 3 классов, потому что у каждого есть метод, который вам нужен, и заканчивается класс, который наследует 100 методов и 20 полей, но использует только 5 из них.
Используйте черты в качестве базовых классов. Затем используйте их в родительском классе. Расширьте это.
trait business{
function sell(){
}
function buy(){
}
function collectMoney(){
}
}
trait human{
function think(){
}
function speak(){
}
}
class BusinessPerson{
use business;
use human;
// If you have more traits bring more
}
class BusinessWoman extends BusinessPerson{
function getPregnant(){
}
}
$bw = new BusinessWoman();
$bw ->speak();
$bw->getPregnant();
Увидеть сейчас бизнес-леди логически унаследовал бизнес и человека обоих;
Я думаю, что в ближайшее время планируется добавить дополнения.
Но до тех пор, иди с принятым ответом. Вы можете немного абстрагироваться, чтобы создать "расширяемый" класс:
class Extendable{
private $extender=array();
public function addExtender(Extender $obj){
$this->extenders[] = $obj;
$obj->setExtendee($this);
}
public function __call($name, $params){
foreach($this->extenders as $extender){
//do reflection to see if extender has this method with this argument count
if (method_exists($extender, $name)){
return call_user_func_array(array($extender, $name), $params);
}
}
}
}
$foo = new Extendable();
$foo->addExtender(new OtherClass());
$foo->other_class_method();
Обратите внимание, что в этой модели "OtherClass" узнает о $foo. OtherClass должен иметь открытую функцию setExtendee, чтобы установить это отношение. Затем, если его методы вызываются из $foo, он может получить внутренний доступ к $ foo. Однако он не получит доступ к каким-либо закрытым / защищенным методам / переменным, как настоящий расширенный класс.
<?php
// what if we want to extend more then one class?
Abstract class ExtensionBridge
{
// array containing all the extended classes
private $_exts = array();
public $_this;
function __construct(){$_this = $this;}
public function addExt($object)
{
$this->_exts[]=$object;
}
public function __get($varname)
{
foreach($this->_exts as $ext)
{
if(property_exists($ext,$varname))
return $ext->$varname;
}
}
public function __call($method,$args)
{
foreach($this->_exts as $ext)
{
if(method_exists($ext,$method))
return call_user_method_array($method,$ext,$args);
}
throw new Exception("This Method {$method} doesn't exists");
}
}
class Ext1{
private $name="";
private $id="";
public function setID($id){$this->id = $id;}
public function setName($name){$this->name = $name;}
public function getID(){return $this->id;}
public function getName(){return $this->name;}
}
class Ext2{
private $address="";
private $country="";
public function setAddress($address){$this->address = $address;}
public function setCountry($country){$this->country = $country;}
public function getAddress(){return $this->address;}
public function getCountry(){return $this->country;}
}
class Extender extends ExtensionBridge
{
function __construct()
{
parent::addExt(new Ext1());
parent::addExt(new Ext2());
}
public function __toString()
{
return $this->getName().', from: '.$this->getCountry();
}
}
$o = new Extender();
$o->setName("Mahdi");
$o->setCountry("Al-Ahwaz");
echo $o;
?>
Я прочитал несколько статей, не одобряющих наследование в проектах (в отличие от библиотек / фреймворков), и поощряющих программировать интерфейсы agaisnt, но не против реализации.
Они также защищают ОО по составу: если вам нужны функции в классах А и В, сделайте так, чтобы у c были члены / поля этого типа:
class C
{
private $a, $b;
public function __construct($x, $y)
{
$this->a = new A(42, $x);
$this->b = new B($y);
}
protected function DoSomething()
{
$this->a->Act();
$this->b->Do();
}
}
Множественное наследование работает на уровне интерфейса. Я сделал тест на php 5.6.1.
Вот рабочий код:
<?php
interface Animal
{
public function sayHello();
}
interface HairyThing
{
public function plush();
}
interface Dog extends Animal, HairyThing
{
public function bark();
}
class Puppy implements Dog
{
public function bark()
{
echo "ouaf";
}
public function sayHello()
{
echo "hello";
}
public function plush()
{
echo "plush";
}
}
echo PHP_VERSION; // 5.6.1
$o = new Puppy();
$o->bark();
$o->plush();
$o->sayHello(); // displays: 5.6.16ouafplushhello
Я не думал, что это было возможно, но я наткнулся на исходный код SwiftMailer, в классе Swift_Transport_IoBuffer, который имеет следующее определение:
interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream
Я еще не играл с ним, но подумал, что было бы интересно поделиться.
Одна из проблем PHP как языка программирования заключается в том, что вы можете иметь только одно наследование. Это означает, что класс может наследоваться только от одного другого класса.
Тем не менее, в большинстве случаев было бы полезно наследовать от нескольких классов. Например, может быть желательно наследовать методы от пары разных классов, чтобы предотвратить дублирование кода.
Эта проблема может привести к классу, у которого есть длинная семейная история наследования, которая часто не имеет смысла.
В PHP 5.4 была добавлена новая функция языка, известная как Черты. Черта похожа на Mixin в том, что она позволяет вам смешивать классы черт в существующий класс. Это означает, что вы можете уменьшить дублирование кода и получить преимущества, избегая проблем множественного наследования.
Я только что решил мою проблему "множественного наследования" с:
class Session {
public $username;
}
class MyServiceResponsetype {
protected $only_avaliable_in_response;
}
class SessionResponse extends MyServiceResponsetype {
/** has shared $only_avaliable_in_response */
public $session;
public function __construct(Session $session) {
$this->session = $session;
}
}
Таким образом, у меня есть возможность манипулировать сессией внутри SessionResponse, который расширяет MyServiceResponsetype, по-прежнему способный обрабатывать Session сам по себе.
Если вы хотите проверить, является ли функция общедоступной, см. Эту тему: /questions/30538585/kak-proverit-yavlyaetsya-li-funktsiya-obschedostupnoj-ili-zaschischennoj-v-php/30538595#30538595
И используйте метод call_user_func_array(...) для множества аргументов или нет.
Как это:
class B {
public function method_from_b($s) {
echo $s;
}
}
class C {
public function method_from_c($l, $l1, $l2) {
echo $l.$l1.$l2;
}
}
class A extends B {
private $c;
public function __construct() {
$this->c = new C;
}
public function __call($method, $args) {
if (method_exists($this->c, $method)) {
$reflection = new ReflectionMethod($this->c, $method);
if (!$reflection->isPublic()) {
throw new RuntimeException("Call to not public method ".get_class($this)."::$method()");
}
return call_user_func_array(array($this->c, $method), $args);
} else {
throw new RuntimeException("Call to undefined method ".get_class($this)."::$method()");
}
}
}
$a = new A;
$a->method_from_b("abc");
$a->method_from_c("d", "e", "f");
Вы можете сделать это, используя черты в PHP, анонсированные начиная с PHP 5.4
Вот краткое руководство для вас, http://culttt.com/2014/06/25/php-traits/
Не зная точно, чего вы пытаетесь достичь, я бы посоветовал изучить возможность изменения дизайна вашего приложения, чтобы использовать композицию, а не наследование в этом случае.
Всегда хорошая идея сделать родительский класс с функциями... т.е. добавить всю эту функциональность в родительский.
И "переместить" все классы, которые используют это иерархически вниз. Мне нужно - переписать функции, которые являются конкретными.
PHP не допускает множественного наследования, но вы можете сделать это с помощью реализации нескольких интерфейсов. Если реализация "тяжелая", предоставьте скелетную реализацию для каждого интерфейса в отдельном классе. Затем вы можете делегировать весь интерфейсный класс этим реализациям скелета с помощью содержания объекта.
PHP пока не поддерживает множественное наследование классов, однако поддерживает множественное наследование интерфейсов.
См. http://www.hudzilla.org/php/6_17_0.php для некоторых примеров.
Это не настоящий ответ, у нас есть повторяющийся класс
... но работает:)
class A {
//do some things
}
class B {
//do some things
}
class copy_B копирует весь класс B
class copy_B extends A {
//do some things (copy class B)
}
class A_B extends copy_B{}
сейчас же
class C_A extends A{}
class C_B extends B{}
class C_A_b extends A_B{} //extends A & B
Класс А расширяет B {}
класс B расширяет C {}
Тогда А расширил и В и С
Интерфейсы действительно бессмысленны, чтобы сформировать у меня логический смысл, так как они фактически заставляют меня повторно реализовывать один и тот же метод снова и снова, он просто действует как напоминание или комментарий TODO. Не сказать, что это плохо, но логика состоит в том, чтобы избежать алмазной проблемы, о которой, я думаю, Python, вероятно, найдет лучшее решение для этого. Вам не нужно переписывать тело функции снова и снова. Вы можете просто выбрать родительский вызывающий объект для выполнения.
Например, если у вас есть класс Herbivore и класс Carnivore, а также класс Omnivore, то все они имеют функцию eat, которая принимает пищу param, в этом случае нам придется заново реализовать eat в Omnivore, а не просто вызывать eat of omnivore - либо Herbivore eat, либо Carnivore eat.,
class Herbivore:
def eat(self, food):
print("Food is supposed to be vegan")
class Carnivore:
def eat(self, food):
print("Food is supposed to be meaty")
class Omnivore(Carnivore, Herbivore):
def eat(self, food):
#Test the food type and call function for either
#Carnivore.eat(food)
#Herbivore.eat(food)
#There is no actual re-implementation of the action
print("Food has to either bee meaty or vegan")