Урок №161. множественное наследование

Object.create

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

На самом деле, есть третий тип, предоставляемый ES5Метод создаст новый объект, первый параметр получит объект, который будет объектом-прототипом вновь созданного объекта, а вторым необязательным параметром является дескриптор атрибута (обычно не используется, по умолчанию). пожалуйста, проверьтеObject.create()。

Давайте смоделируем простую версию:

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

Если вы хотите создать объект, который не наследует никаких свойств, вы можете использовать。

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

Множественное наследование в Java

Множественное наследование — возможность создания единого класса с несколькими суперклассами.

В отличие от некоторых других популярных объектно-ориентированных языках программирования, таких как C ++, Java не предоставляет поддержку множественного наследования в классах. Java не поддерживает множественное наследование классов, потому что это может привести к проблеме ромба (ромбовидное наследование) и вместо того, чтобы предоставлять сложный путь разрешения этой проблемы, придумали способ лучше.

Проблема ромба

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

Давайте создадим абстрактный суперклас SuperClass с методом doSomething(), а также два класса ClassA, ClassB

SuperClass.java

Java

package ua.com.prologistic.inheritance;

public abstract class SuperClass {

public abstract void doSomething();
}

1
2
3
4
5
6

packageua.com.prologistic.inheritance;

publicabstractclassSuperClass{

publicabstractvoiddoSomething();

}

ClassA.java

Java

package ua.com.prologistic.inheritance;

public class ClassA extends SuperClass{

@Override
public void doSomething(){
System.out.println(«doSomething реализуется в классе A»);
}

//Собственный метод класса ClassA
public void methodA(){

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

packageua.com.prologistic.inheritance;

publicclassClassAextendsSuperClass{

@Override

publicvoiddoSomething(){

System.out.println(«doSomething реализуется в классе A»);

}

//Собственный метод класса ClassA

publicvoidmethodA(){

}

}

ClassB.java

Java

package ua.com.prologistic.inheritance;

public class ClassB extends SuperClass{

@Override
public void doSomething(){
System.out.println(«doSomething реализуется классом B»);
}

//Свой метод класса ClassB
public void methodB(){

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

packageua.com.prologistic.inheritance;

publicclassClassBextendsSuperClass{

@Override

publicvoiddoSomething(){

System.out.println(«doSomething реализуется классом B»);

}

//Свой метод класса ClassB

publicvoidmethodB(){

}

}

А теперь давайте создадим класс ClassC, который наследует классы ClassA и ClassB

Java

package ua.com.prologistic.inheritance;

public class ClassC extends ClassA, ClassB{

public void test(){
//вызываем метод суперкласса
doSomething();
}

}

1
2
3
4
5
6
7
8
9
10

packageua.com.prologistic.inheritance;

publicclassClassC extendsClassA,ClassB{

publicvoidtest(){

//вызываем метод суперкласса

doSomething();

}

}

Обратите внимание, что метод вызывает метод суперкласса 

Это приводит к неопределенности: компилятор не знает, какой метод суперкласса выполнить из-за ромбовидной формы (выше на диаграмме классов). Это называют проблемой ромба — и это основная причина почему Java не поддерживает множественное наследование классов.

Объявление

Объявляется с использованием ключевого слова extends:

public class Vehicle {
    protected String licensePlate = null;

    public void setLicensePlate(String license) {
        this.licensePlate = license;
    }
}
public class Car extends Vehicle {
    int numberOfSeats = 0;

    public String getNumberOfSeats() {
        return this.numberOfSeats;
    }
}

Класс Car в этом примере расширяет класс Vehicle, то есть Car наследуется от Vehicle. Поскольку Car расширяет Vehicle, защищенное поле licensePlate из Vehicle наследуется Car. Когда licensePlate наследуется, оно становится доступным внутри экземпляра Car.

В поле licensePlate на самом деле не ссылаются из класса Car в приведенном выше коде, но можно, если мы захотим:

public class Car extends Vehicle {
    int numberOfSeats = 0;

    public String getNumberOfSeats() {
        return this.numberOfSeats;
    }

    public String getLicensePlate() {
        return this.licensePlate;
    }
}

Ссылка происходит внутри метода getLicensePlate(). Во многих случаях имело бы смысл поместить этот метод в класс Vehicle, где находится поле licensePlate.

Работа с абстрактными классами

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

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

На заметку: как только вы создали хотя бы один абстрактный метод в классе, вы должны объявить этот класс как абстрактный.

Когда от абстрактного класса наследуется обычный класс, он должен реализовать все абстрактные методы класса-родителя. В противном случае, PHP сгенерирует ошибку. Так, абстрактный класс создает “правила поведения” для своих дочерних классов.

На заметку: вы можете добавлять в абстрактный класс и не абстрактные методы. Они будут обыкновенным образом наследоваться дочерними классами.

abstract class Person {
 
  private $firstName = "";
  private $lastName = "";
 
  public function setName( $firstName, $lastName ) {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
  }
 
  public function getName() {
   return "$this->firstName $this->lastName";
  }
   
  abstract public function showWelcomeMessage();
}

Как видите, мы создали абстрактный класс, добавив в его описание ключевое слово abstract. В этом классе есть несколько свойств, общих для всех людей, — $frstName и $lastName — а также методы для инициализации и чтения значений этих полей.

В классе также есть абстрактный метод showWelcomeMessage(). Этот метод выводит приветствие, когда пользователь входит на сайт. Опять же, мы добавляем ключевое слово abstract в описание данного метода, чтобы сделать его абстрактным. Так как он абстрактный, в нем нет ни строчки кода, это просто его объявление. Тем не менее, любой дочерний класс обязан добавить и описать метод showWelcomeMessage().

Теперь давайте создадим пару классов от абстрактного класса Person:

  1. класс Member для участников форума;
  2. класс Shopper для покупателей онлайн-магазина.
class Member extends Person {
 
  public function showWelcomeMessage() {
    echo "Hi " . $this->getName() . ", welcome to the forums!<br>";
  }
 
  public function newTopic( $subject ) {
    echo "Creating new topic: $subject<br>";
  }
}
 
class Shopper extends Person {
 
  public function showWelcomeMessage() {
    echo "Hi " . $this->getName() . ", welcome to our online store!<br>";
  }
 
  public function addToCart( $item ) {
    echo "Adding $item to cart<br>";
  }
}

Как видите, каждый из них описывает метод showWelcomeMessage() из абстрактного супер-класса. Они имплементированы по-разному: в классе Member отображается сообщение «welcome to the forums», а в классе Shopper — «welcome to our online store», но это нормально. Главное то, что они оба описали данный метод.

Если бы один из них, например, Shopper, не описал метод, PHP выдал бы ошибку:

Class Shopper contains 1 abstract method and must therefore be declared abstract
or implement the remaining methods (Person::showWelcomeMessage)

Наряду с имплементацией абстрактного метода, в каждом классе есть свои обычные методы. В Member есть метод newTopic() для создания новой темы в форуме, а в Shopper — метод addToCart() для добавления товаров в корзину.

Теперь мы можем создавать участников форума и покупателей на нашем сайте. Мы можем вызывать методы newTopic() и addToCart() от этих объектов, а также getName() и setName(), так как они наследуются от супер-класса Person.

Более того, зная, что классы Member и Shopper наследуются от Person, мы можем спокойно вызывать метод showWelcomeMessage() для обоих классов, так как он точно реализован и в том и в другом. Мы в этом уверены, так как знаем, что он был объявлен как абстрактный метод в классе Person.

Вот пример:

$aMember = new Member();
$aMember->setName( "John", "Smith" );
$aMember->showWelcomeMessage();
$aMember->newTopic( "Teddy bears are great" );
 
$aShopper = new Shopper();
$aShopper->setName( "Mary", "Jones" );
$aShopper->showWelcomeMessage();
$aShopper->addToCart( "Ornate Table Lamp" );

На странице отобразится:

Hi John Smith, welcome to the forums!
Creating new topic: Teddy bears are great
Hi Mary Jones, welcome to our online store!
Adding Ornate Table Lamp to cart

Когда вы используете наследование в JavaScript?

В частности, после этой последней статьи вы можете подумать: «У-у-у, это сложно». Ну, ты прав. Прототипы и наследование представляют собой некоторые из самых сложных аспектов JavaScript, но многие возможности и гибкость JavaScript вытекают из его структуры объектов и наследования и стоит понять, как это работает.

В некотором смысле вы используете наследование все время. Всякий раз, когда вы используете различные функции веб-API или методы/свойства, определённые во встроенном объекте браузера, который вы вызываете в своих строках, массивах и т.д., вы неявно используете наследование.

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

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

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

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

Как наследовать от другого класса?

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

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

Синтаксис

<?php
class ParentClass {

}
class ChildClass extends ParentClass {

}
?>

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

Абстрактные методы и классы

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

Monster.java

Java

abstract class Monster {
}

1
2

abstractclassMonster{

}

Абстрактным методом называется метод, объявленный с ключевым словом
abstract  и не имеющий тела метода.

Java

abstract void myAbstractMethod(int myParam1, double myParam2);

1 abstractvoidmyAbstractMethod(intmyParam1,doublemyParam2);

Если в классе есть абстрактные методы, то он ДОЛЖЕН быть объявлен абстрактным.

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

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

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

Выбрать между абстрактным классом и интерфейсом бывает довольно сложно. Старайтесь руководствоваться правилами, описанными ниже.

Используйте абстрактные классы, если:

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

Используйте интерфейсы в следующих ситуациях:

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

Интерфейсы
Comparable  и
Cloneable , например, реализует очень большое количество совершенно разных классов.
Вы хотите указать поведение определённого типа, но вам абсолютно не важно, кто будет реализовывать это поведение.
Вам нужно множественное наследование типов.. Для примера абстрактного класса представьте ситуацию, что вам нужно реализовать несколько различных видов монстров:
Goblin ,
Hobgoblin ,
Orc ,
Gremlin  и
Genie

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

Для примера абстрактного класса представьте ситуацию, что вам нужно реализовать несколько различных видов монстров:
Goblin ,
Hobgoblin ,
Orc ,
Gremlin  и
Genie. Каждый из эти монстров имеет свои различные особенности, которые будут реализовываться в соответствующем классе, но все эти монстры будут уметь ходить и иметь координаты в пространстве, и у каждого из них будет уровень здоровья. В этом случае можно заложить умение ходить, координаты и уровень здоровья в базовом классе
Monster, который сделать абстрактным, и в котором объявить абстрактные методы для управления повадками и прочими вещами, реализации которых будут в соответствующих дочерних классах.

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 перечисления».
Предыдущая статья — «Java 8 интерфейсы».

Полный пример наследования

Для окончательной организации удобного javascript-наследования на классе, пригодится функция копирования свойств из объекта в другой :

// копирует все свойства из src в dst,
// включая те, что в цепочке прототипов src до Object
function mixin(dst, src){
	// tobj - вспомогательный объект для фильтрации свойств,
	// которые есть у объекта Object и его прототипа
	var tobj = {}
	for(var x in src){
		// копируем в dst свойства src, кроме тех, которые унаследованы от Object
		if((typeof tobj == "undefined") || (tobj != src)){
			dst = src;
		}
	}
	// В IE пользовательский метод toString отсутствует в for..in
	if(document.all && !document.isOpera){
		var p = src.toString;
		if(typeof p == "function" && p != dst.toString && p != tobj.toString &&
		 p != "\nfunction toString() {\n    \n}\n"){
			dst.toString = src.toString;
		}
	}
}

В полном примере мы создадим класс c методом и его насленика , который умеет летать: . Функции и принимают время ходьбы/полета и соответственно увеличивают свойство — расстояние до животного:

// ---- родительский класс ----

function Animal(name, walkSpeed) {
	this.name = name
	this.walkSpeed = walkSpeed
}

// добавляем методы объекта
mixin(Animal.prototype, {

	// пример переменной
	distance: 0,

	// пример метода
	walk: function(time) {
		this.distance = this.distance + time*this.walkSpeed
	},

	toString: function() {
		return this.name+" на расстоянии "+this.distance
	}
})

// ---- класс наследник ----

function Bird(name, walkSpeed, flySpeed) {
	// вызов родительского конструктора
	Bird.superclass.constructor.call(this, name, walkSpeed)

	this.flySpeed = flySpeed
}
extend(Bird, Animal)

mixin(Bird.prototype, {
	fly: function(time) {
		this.distance = this.distance + time*this.flySpeed
	}
})

Пример создания объекта-наследника:

bird = new Bird("Птыц", 1, 10)

bird.walk(3)

alert(bird) // => Птыц на расстоянии 3

bird.fly(2)

alert(bird) // => Птыц на расстоянии 23

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

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

function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype
	Child.prototype = new F()
	Child.prototype.constructor = Child
	Child.superclass = Parent.prototype
}

// ---- родительский класс ----

function Animal(name, walkSpeed) {

	// объявить приватную переменную
	var speed = walkSpeed

	// объявить открытую переменную
	this.distance = 0

	// добавить метод, использующий private speed
	this.walk = function(time) {
		this.distance = this.distance + time*speed
	}

	// добавить метод, использующий private name
	this.toString = function() {
		return name+" на расстоянии "+this.distance
	}
}


// ---- класс наследник ----

function Bird(name, walkSpeed, flySpeed) {
	// вызов родительского конструктора
	Bird.superclass.constructor.call(this, name, walkSpeed)

	this.fly = function(time) {
		this.distance = this.distance + time*flySpeed
	}
}
extend(Bird, Animal)


bird = new Bird("Птыц", 1, 10)

bird.walk(3)

alert(bird) // => Птыц на расстоянии 3

bird.fly(2)

alert(bird) // => Птыц на расстоянии 23

Приватными являются все свойства, которые доступны только из внутренних методов объекта через механизм замыкания (см. статью о функциях javascript).

Это свойства, явно объявленные через var, плюс аргументы конструктора.

При таком способе объявления — все свойства и методы записываются не в прототип объекта, а в сам объект.

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

Обычно же эти расходы можно во внимание не принимать

Если Вы использовали ООП в других языках программирования, то наверняка знаете, что чаще делаются не private свойства, а protected, т.е такие, к которым могут получить доступ наследники. Javascript не предоставляет синтаксиса для создания protected свойств, поэтому их просто помечают подчеркиванием в начале.

Например,

function Animal(name) {

	var privateVariable = 0

	this._protectedName = name

	this._protectedMethod = function(..) {
		... alert(privateVariable)..
	}

	this.publicMethod = function() { ... }
}

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

Ограничение доступа к таким «защищенным» свойствам не жесткое и остается на совести программиста.

Пример инструкции

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

Car car = new Car();

boolean isCar = car instanceof Car;

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

Инструкция instanceof также может использоваться для определения того, является ли объект экземпляром суперкласса своего класса. Вот пример, который проверяет, является ли объект Car экземпляром Vehicle:

Car car = new Car();

boolean isVehicle = car instanceof Vehicle;

Предполагая, что класс Car расширяет (наследует от) класс Vehicle, переменная isVehicle будет содержать значение true после выполнения этого кода. Объект Car также является объектом Vehicle, поскольку Car является подклассом Vehicle.

Как видите, инструкция instanceof может использоваться для изучения иерархии наследования. Тип переменной, используемый с ней, не влияет на ее результат. Посмотрите на этот пример:

Car car = new Car();

Vehicle vehicle = car;

boolean isCar = vehicle instanceof Car;

Несмотря на то, что переменная транспортного средства имеет тип Vehicle, объект, на который она в конечном итоге указывает в этом примере, является объектом Car. Поэтому экземпляр транспортного средства автомобиля будет оценен как истинный.

Вот тот же пример, но с использованием объекта Truck вместо объекта Car:

Truck truck = new Truck();

Vehicle vehicle = truck;

boolean isCar = vehicle instanceof Car;

После выполнения этого кода isCar будет содержать значение false. Объект Truck не является объектом Car.

Множественное наследование в интерфейсах

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

InterfaceA.java

Java

package ua.com.prologistic.inheritance;

public interface InterfaceA {

public void doSomething();
}

1
2
3
4
5
6

packageua.com.prologistic.inheritance;

publicinterfaceInterfaceA{

publicvoiddoSomething();

}

InterfaceB.java

Java

package ua.com.prologistic.inheritance;

public interface InterfaceB {

public void doSomething();
}

1
2
3
4
5
6

packageua.com.prologistic.inheritance;

publicinterfaceInterfaceB{

publicvoiddoSomething();

}

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

InterfaceC.java

Java

package ua.com.prologistic.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

//один и тот же метод объявлен в интерфейсах InterfaceA и InterfaceB
public void doSomething();

}

1
2
3
4
5
6
7
8

packageua.com.prologistic.inheritance;

publicinterfaceInterfaceC extendsInterfaceA,InterfaceB{

//один и тот же метод объявлен в интерфейсах InterfaceA и InterfaceB

publicvoiddoSomething();

}

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

Теперь давайте посмотрим на код ниже:

InterfacesImpl.java

Java

package ua.com.prologistic.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

@Override
public void doSomething() {
System.out.println(«doSomething реализуется в конкретном классе»);
}

public static void main(String[] args) {
InterfaceA objA = new InterfacesImpl();
InterfaceB objB = new InterfacesImpl();
InterfaceC objC = new InterfacesImpl();

//вызов методов с конкретной реализацией
objA.doSomething();
objB.doSomething();
objC.doSomething();
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

packageua.com.prologistic.inheritance;

publicclassInterfacesImpl implementsInterfaceA,InterfaceB,InterfaceC{

@Override

publicvoiddoSomething(){

System.out.println(«doSomething реализуется в конкретном классе»);

}

publicstaticvoidmain(Stringargs){

InterfaceA objA=newInterfacesImpl();

InterfaceB objB=newInterfacesImpl();

InterfaceC objC=newInterfacesImpl();

//вызов методов с конкретной реализацией

objA.doSomething();

objB.doSomething();

objC.doSomething();

}

}

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: