Наследование класса: C# и .NET | Наследование

Содержание

Классы, наследование— Ruby Rush

В этом уроке мы рассмотрим непростую тему наследования классов. Мы научимся писать классы-детей, наследующие поведение от классов-родителей. А также, спроектируем программу «Блокнот», в которой помимо обычных заметок будут также задачки и ссылки.

План урока

  1. Наследование классов
  2. Готовим классы для нового блокнота

Концепция наследования

Чтобы понять, что такое наследование, давайте рассмотрим в качестве примера понятие Средство транспортировки:

Средством транспортировки может быть что угодно: машина, корабль, самолёт или даже газовая или нефтяная труба — средство транспортировки горючих материалов куда надо и кому надо.

У каждого средства транспортировки, вне зависимости от его типа, есть как минимум вот такие свойства и методы

СредствоТранспортировки

Свойства:
@пропускная_способность
@год_запуска_в_эксплуатацию

Методы:
начать_транспортировку
остановить_транспортировку

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

Корабль:
Все методы и свойства «СредстваТранспортировки» плюс:

Свойства:
@скорость_хода
@водоизмещение

Методы:
начать_движение
остановиться

Наконец, добираемся до чего-то совсем конкретного (конкретнее только указать кадастровый номер какого-нибудь судна): рассмотрим моторную лодку:

МоторнаяЛодка:
Все методы и свойства «Корабля» плюс:

Свойства:
@текущая_температура_двигателя
@расход_топлива

Методы:
завести_мотор
заглушить_мотор

Получается, что эти понятия как бы вложены друг в друга: МоторнаяЛодка < Корабль < СредствоТранспортировки.

Для описания таких структур в объектно-ориентированных языках программирования придумали понятие наследования.

Что такое наследование и зачем оно нужно?

Класс — это чертеж, по которому создается объект какой-то определенной группы.

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

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

При этом родительский класс, как правило, ещё абстрактнее, чем его дети: «транспорт» куда абстрактнее «моторной лодки».

Этот шаблон широко используется в современных фреймворках (например, в популярном фреймворке для создания веб-приложений Ruby on Rails). Именно поэтому так важно понять эту концепцию.

Проектируем новый дневник

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

Воспользуемся программой из 17-го урока (вы можете также взять её из материалов к этому уроку). Мы будем её развивать на протяжении этого и следующего урока.

Итак, у нас будет три типа записи: заметка, ссылка и задача.

Давайте для начала разберёмся со свойствами.

Для заметки нам понадобятся такие поля

@text # содержание заметки
@created_at # дата создания заметки

Для ссылки нам понадобятся такие поля

@url # адрес ссылки
@description # описание ссылки
@created_at # дата создания ссылки

А для тудушки — такие:

@text # тело задачи
@due_date # к какому числу задачу нужно сделать
@created_at # дата создания задачки

Как вы видите, у всех записей есть общее поле @created_at, также если присмотреться, у всех классов есть поле с текстом @text, только у ссылки оно называется @description. Большой беды не будет, если мы его переименуем.

Поля @created_at и @text мы вынесем в родительский класс Post. Дочерний класс Memo не будет иметь дополнительных полей, дочерний класс Task будет иметь дополнительное поле @due_date, а дочерний класс Link будет иметь дополнительное поле @url.

Родительский класс Post

Итак, выделим общие поля в родительский класс Post. Для этого в папке урока rubytut2/lesson6 создайте папку notepad и в ней создайте файл post.rb с описанием класса Post. Мы опишем в этом классе общую логику работы с записями:

# Базовый класс "Запись"
# Задает основные методы и свойства, присущие всем разновидностям Записи
class Post

  # Конструктор
  def initialize
    @created_at = Time.now # дата создания записи
    @text = nil # массив строк записи — пока пустой
  end

  # Этот метод вызывается в программе, когда нужно
  # считать ввод пользователя и записать его в нужные поля объекта
  def read_from_console
    # должен быть реализован классами-детьми,
    # которые знают как именно считывать свои данные из консоли
  end

  # Этот метод возвращает состояние объекта в виде массива строк, готовых к записи в файл
  def to_strings
    # должен быть реализован классами-детьми,
    # которые знают как именно хранить себя в файле
  end

  # Этот метод записывает текущее состояние объекта в файл
  def save
    # он будет только у родителя, его мы напишем позже
  end
end

# PS: Весь набор методов, объявленных в родительском классе называется интерфейсом класса
# Дети могут по–разному реализовывать методы, но они должны подчиняться общей идее
# и набору функций, которые заявлены в базовом (родительском классе)

Дочерний класс Memo

Напишем класс Memo, который унаследует основной функционал от класса Post.

# Класс "Заметка", разновидность базового класса "Запись"
class Memo < Post

  # отдельный конструктор здесь не нужен, т. к. он совпадает с родительским

  # Этот метод пока пустой, он будет спрашивать ввод содержимого Заметки
  # наподобие программы Дневник из "базового блока" курса
  def read_from_console
  end
end

Дочерний класс Link

Класс ссылки тоже будет наследоваться от класса Post:

# Класс Ссылка, разновидность базового класса "Запись" class Link < Post def initialize super # вызываем конструктор родителя # потом инициализируем специфичное для ссылки поле @url = '' end # Этот метод пока пустой, он будет спрашивать 2 строки — адрес ссылки и описание def read_from_console end # Массив из трех строк: адрес ссылки, описание и дата создания # Будет реализован в след. уроке def to_strings end end

Дочерний класс Task

Наконец, опишем в файле task.rb класс Task, который наследует себя от класса Post:

# Подключим встроенный в руби класс Date для работы с датами
require 'date'

# Класс Задача, разновидность базового класса "Запись"
class Task < Post
  def initialize
    super # вызываем конструктор родителя

    # потом инициализируем специфичное для Задачи поле - дедлайн
    @due_date = Time.now
  end

  # Этот метод пока пустой, он будет спрашивать 2 строки - описание задачи и дату дедлайна
  def read_from_console
  end

  # Массив из трех строк: дедлайн задачи, описание и дата создания
  # Будет реализован в след. уроке
  def to_strings
  end
end

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

Наследование (программирование) — это… Что такое Наследование (программирование)?

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

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

Типы наследования

Простое наследование

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class

). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class).

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

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

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

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

Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

Единый базовый класс

В ряде языков программирования все классы явно или неявно наследуются от некого базового класса. Smalltalk был одним из первых языков, в которых использовалась эта концепция. К таким языкам относятся Objective-C (NSObject), Perl (UNIVERSAL), Eiffel (ANY), Java (java.lang.Object), C# (System.Object), Delphi (TObject).

Наследование в языках программирования

Visual Basic

Наследование в Visual Basic:

Class A 'базовый класс
End Class
 
Class B : Inherits A    'наследование от A
End Class
 
Noninheritable Class C     'Класс, который нельзя наследовать (final в Java)
End Class
 
MustInherit Class Z 'Класс, который обязательно наследовать (абстрактный класс)
End Class

C++

Наследование в C++:

class A{    //базовый класс
};
 
class B : public A{    //public наследование
};
 
class C : protected A{    //protected наследование
};
 
class Z : private A{    //private наследование
};

В C++ существует три типа наследования: public, protected, private. Спецификаторы доступа членов базового класса меняются в потомках следующим образом:

ANSI ISO IEC 14882 2003

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

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

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

\ANSI ISO IEC 14882 2003

Одним из основных преимуществ public-наследования является то, что указатель на классы-наследники может быть неявно преобразован в указатель на базовый класс, то есть для примера выше можно написать:

Эта интересная особенность открывает возможность динамической идентификации типа (RTTI).

Delphi (Object Pascal)

Для использования механизма наследования в Delphi необходимо в объявлении класса справа от слова class указать класс предок:

Предок:

TAncestor = class
private
protected
public
  // Виртуальная процедура
  procedure VirtualProcedure; virtual; abstract; 
  procedure StaticProcedure;
end;

Наследник:

TDescendant = class(TAncestor)
private
protected
public
  // Перекрытие виртуальной процедуры
  procedure VirtualProcedure; override;
  procedure StaticProcedure;
end;

Абсолютно все классы в Delphi являются потомками класса TObject. Если класс-предок не указан, то подразумевается, что новый класс является прямым потомком класса TObject.

Множественное наследование в Delphi частично поддерживается за счёт использования классов-помощников (Сlass Helpers).

Python

Python поддерживает как одиночное, так и множественное наследование. При доступе к атрибуту порядок просмотра производных классов называется порядком разрешения метода (англ. method resolution order)[1].

class Ancestor1(object):   # Предок 1
    def m1(self): pass
class Ancestor2(object):   # Предок 2
    def m1(self): pass
class Descendant(Ancestor1, Ancestor2):   # Наследник
    def m2(self): pass
 
d = Descendant()           # инстанциация
print d.__class__.__mro__  # порядок разрешения метода:
(<class '__main__.Descendant'>, <class '__main__.Ancestor1'>, <class '__main__.Ancestor2'>, <type 'object'>)

С версии Python 2.2 в языке сосуществуют «классические» классы и «новые» классы. Последние являются наследниками object. «Классические» классы будут поддерживаться вплоть до версии 2.6, но удалены из языка в Python версии 3.0.

Множественное наследование применяется в Python, в частности, для введения в основной класс классов-примесей (англ. mix-in).

PHP

Для использования механизма наследования в PHP необходимо в объявлении класса после имени объявляемого класса-наследника указать слово extends и имя класса-предка:

class Descendant extends Ancestor {
}

В случае перекрытия классом-наследником свойств и методов предка, доступ к свойствам и методам предка можно получить с использованием ключевого слова parent:

class A {
  function example() {
    echo "Вызван метод A::example().<br />\n";
  }
}
 
class B extends A {
  function example() {
    echo "Вызван метод B::example().<br />\n";
    parent::example();
  }
}

Objective-C

@interface MyNumber : NSObject { 
   int num;
}
- (int) num;
- (void) setNum: (int) theNum;
@end
 
@implementation
- (id) init { 
   self = [super init];
   return self;
}
 
- (int) num {
   return num;
}
 
- (void) setNum: (int) theNum {
   num = theNum;
}
@end

Переопределенные методы не нужно объявлять в интерфейсе.

Java

Пример наследования от одного класса и двух интерфейсов:

        public class A { }
        public interface I1 { }
        public interface I2 { }
        public class B extends A implements I1, I2 { }

Директива final в объявлении класса делает наследование от него невозможным.

C#

Пример наследования от одного класса и двух интерфейсов:

        public class A { }
        public interface I1 { }
        public interface I2 { }
        public class B : A, I1, I2 { }

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

        public class A<T>
        { }
        public class B : A<int>
        { }
        public class B2<T> : A<T>
        { }

Допустимо также наследование вложенных классов от классов, их содержащих:

    class A
    {
        public class B : A { }
    }

Директива sealed в объявлении класса делает наследование от него невозможным.[2]

Ruby

class Parent
 
  def public_method
    "Public method"
  end
 
  private
 
    def private_method
      "Private method"
    end
 
end
 
class Children < Parent
 
  def public_method
    "Redefined public method"
  end
 
  def call_private_method
    "Ancestor's private method: " + private_method
  end
 
end

Класс Parent является предком для класса Children, у которого переопределен метод public_method.

children = Children.new
children.public_method #=> "Redefined public method"
children.call_private_method #=> "Ancestor's private method: Private method"

Приватные методы предка можно вызывать из наследников.

JavaScript

var Parent = function( data ) {
    this.data = data || false;
    this.public_method = function() { return 'Public Method'; }
}
 
var Child = function() {
    this.public_method = function() { return 'Redefined public method'; }
    this.getData = function() { return 'Data: ' + this.data; }
}
 
Child.prototype = new Parent('test');
var Test = new Child();
 
Test.getData(); // => "Data: test"
Test.public_method(); // => "Redefined public method"
Test.data; // => "test"

Класс Parent является предком для класса Children, у которого переопределен метод public_method. В JavaScript используется прототипное наследование.

Конструкторы и деструкторы

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

class First
{
public:
    First()  { cout << ">>First constructor" << endl; }
    ~First() { cout << ">>First destructor" << endl; }
};
 
class Second: public First
{
public:
    Second()  { cout << ">Second constructor" << endl; }
    ~Second() { cout << ">Second destructor" << endl; }
};
 
class Third: public Second
{
public:
    Third()  { cout << "Third constructor" << endl; }
    ~Third() { cout << "Third destructor" << endl; }
};
 
// выполнение кода
Third *th = new Third();
delete th;
 
// результат вывода
/*
>>First constructor
>Second constructor
Third constructor
 
Third destructor
>Second destructor
>>First destructor
*/

См. также

Примечания

Ссылки

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

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

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

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

Любой класс, который ничего не наследует из другого класса, называется базовым классом.

Заметка

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

Пример ниже определяет класс Vehicle. Этот базовый класс определяет хранимое свойство currentSpeed, с начальным значением 0.0 (выведенный тип Double). Значение свойства currentSpeed используется вычисляемым нередактируемым свойством description типа String, для создания описания транспортного средства (экземпляра Vehicle).

Так же класс Vehicle определяет метод makeNoise. Этот метод фактически ничего не делает для базового экземпляра класса Vehicle, но будет настраиваться подклассом класса Vehicle чуть позже:

class Vehicle {
  var currentSpeed = 0.0
  var description: String {
    return "движется на скорости \(currentSpeed) миль в час"
  }
  func makeNoise() {
    //ничего не делаем, так как не каждый транспорт шумит
  }
}

Вы создаете новый экземпляр класса Vehicle при помощи синтаксиса инициализатора, который написан как TypeName, за которым идут пустые круглые скобки:

let someVehicle = Vehicle()

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

print("Транспорт: \(someVehicle.description)")
//Транспорт: движется на скорости 0.0 миль в час

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

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

Для индикации того, что подкласс имеет суперкласс, просто напишите имя подкласса, затем имя суперкласса и разделите их двоеточием:

class SomeSubclass: SomeSuperclass {
  // определение подкласса проводится тут
}

Приведенный пример определяет подкласс Bicycle с суперклассом Vehicle:

class Bicycle: Vehicle {
  var hasBasket = false
}

Новый класс Bicycle автоматически собирает все характеристики Vehicle, например, такие свойства как currentSpeed и description и метод makeNoise().

В дополнение к характеристикам, которые он наследует, класс Bicycle определяет свое новое хранимое свойство hasBasket, со значением по умолчанию false (тип свойства выведен как Bool).

По умолчанию, любой новый экземпляр Bicycle, который вы создадите не будет иметь корзину (hasBasket = false). Вы можете установить hasBasket на значение true для конкретного экземпляра Bicycle, после того как он создан:

let bicycle = Bicycle()
bicycle.hasBasket = true

Вы так же можете изменить унаследованное свойство currentSpeed экземпляра Bicycle и запросить его свойство description:

bicycle.currentSpeed = 15.0
print("Велосипед: \(bicycle.description)")
//Велосипед: движется на скорости 15.0 миль в час

Подклассы сами могут создавать подклассы. В следующем примере класс Bicycle создает подкласс для двухместного велосипеда известного как “тандем”:

class Tandem: Bicycle {
  var currentNumberOfPassengers = 0
}

Класс Tandem наследует все свойства и методы Bicycle, который в свою очередь наследует все свойства и методы от Vehicle. Подкласс Tandem так же добавляет новое хранимое свойство currentNumberOfPassengers, которое по умолчанию равно 0.

Если вы создадите экземпляр Tandem, то вы можете работать с любым из его новых и унаследованных свойств. Свойство description, которое является свойством только для чтения, он наследует от Vehicle:

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Тандем: \(tandem.description)")
// Тандем: движется на скорости 22.0 миль в час

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

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

Ключевое слово override так же подсказывает компилятору Swift проверить, что вы переопределяете суперкласс класса (или один из его параметров), который содержит то определение, которое вы хотите переопределить. Эта проверка гарантирует, что ваше переопределение корректно.

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

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

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

  • Переопределенный метод someMethod может вызвать версию суперкласса метода someMethod, написав super.someMethod() внутри переопределения реализации метода.
  • Переопределённое свойство someProperty может получить доступ к свойству версии суперкласса someProperty как super.someProperty внутри переопределения реализации геттера или сеттера.
  • Переопределенный индекс для someIndex может получить доступ к версии суперкласса того же индекса как super[someIndex] изнутри переопределения реализации индекса.

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

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

Следующий пример определяет новый подкласс Train класса Vehicle, который переопределяет метод makeNoise(), который Train наследует от Vehicle:

class Train: Vehicle {
  override func makeNoise() {
    print("Чу-чу")
  }
}

Если вы создаете новый экземпляр класса Train и вызовите его метод makeNoise(), вы увидите, что версия метода подкласса Train вызывается вот так:

let train = Train()
train.makeNoise()
// Выведет "Чу-чу"

Переопределение свойств

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

Переопределения геттеров и сеттеров свойства

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

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

Заметка

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

Следующий пример определяет класс Car, который является подклассом Vehicle. Класс Car предоставляет новое свойство хранения gear, имеющее значение по умолчанию равное 1. Класс Car так же переопределяет свойство description, которое он унаследовал от Vehicle, для предоставления собственного описания, которое включает в себя текущую передачу:

class Car: Vehicle {
  var gear = 1
  override var description: String {
    return super.description + " на передаче \(gear)"
  }
}

Переопределение свойства description начинается с super.description, который возвращает свойство description класса Vehicle. Версия класса Car свойства description добавляет дополнительный текст в конец описания текущего свойства description.

Если вы создадите экземпляр класса Car и зададите свойства gear, currentSpeed, то вы увидите что его свойство description возвращает новое описание класса Car:

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Машина: \(car.description)")
// Выведет "Машина: движется на скорости 25.0 миль в час на передаче 3"

Переопределение наблюдателей свойства

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

Заметка

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

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

В следующем примере определим новый класс AutomaticCar, который является подклассом Car. Класс AutomaticCar представляет машину с автоматической коробкой передач, которая автоматически переключает передачи в зависимости от текущей скорости:

class AutomaticCar: Car {
  override var currentSpeed: Double {
    didSet {
      gear = Int(currentSpeed / 10.0) + 1
    }
  }
}

Куда бы вы не поставили свойство currentSpeed экземпляра класса AutomaticCar, наблюдатель didSet свойства устанавливает свойство экземпляра gear в подходящее значение передачи в зависимости от скорости. Если быть точным, то наблюдатель свойства выбирает передачу как значение равное currentSpeed поделенная на 10 и округленная вниз и выбираем ближайшее целое число + 1. Если скорость равна 10.0, то передача равна 2, если скорость 35.0, то передача 4:

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("Машина с автоматом: \(automatic.description)")
//Выведет "Машина с автоматом: движется на скорости 35.0 миль в час на передаче 4"

Вы можете предотвратить переопределение метода, свойства или индекса, обозначив его как конечный. Сделать это можно написав ключевое слово final перед ключевым словом метода, свойства или индекса (final var, final func, final class func, и final subscript ).

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

Вы можете отметить целый класс как конечный или финальный, написав слово final перед ключевым словом class (final class). Любая попытка изменить класс так же приведет к ошибке компиляции.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Наследование классов в Python.

Классы в языке Python поддерживают наследование классов, что позволяет создавать новые классы с расширенным и/или измененным функционалом базового класса. Новый класс, созданный на основе базового класса — называется производный класс (derived class) или просто подкласс.

Подкласс наследует атрибуты и методы из родительского класса. Он так же может переопределять (override) методы родительского класса. Если подкласс не определяет свой конструктор __init__, то он наследует конструктор родительского класса по умолчанию.

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

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    <statement-N>

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

class DerivedClassName(modname.BaseClassName):
    <statement-1>
    .
    <statement-N>

Выполнение определения производного класса DerivedClassName происходит так же, как и для базового класса BaseClassName. Когда объект класса создан, базовый класс BaseClassName запоминается. Это используется для разрешения ссылок на атрибуты. Если запрошенный атрибут не найден в классе DerivedClassName, поиск переходит к поиску в базовом классе BaseClassName. Это правило применяется рекурсивно, если сам базовый класс является производным от какого-либо другого класса.

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

Производные классы DerivedClassName могут переопределять методы своих базовых классов BaseClassName. Поскольку методы не имеют особых привилегий при вызове других методов того же объекта, метод базового класса, который вызывает другой метод, определенный в том же базовом классе, может в конечном итоге вызвать метод производного класса, который переопределяет его. Для программистов C++ — все методы в Python фактически являются виртуальными.

Переопределяющий метод в производном классе может фактически расширить, а не просто заменить метод базового класса с тем же именем. Существует простой способ вызвать метод базового класса напрямую: просто вызовите BaseClassName.methodname(self, arguments). Это иногда полезно и для «клиентов». Обратите внимание, что это работает только в том случае, если базовый класс доступен как имя базового класса BaseClassName в глобальной области видимости.

Python имеет две встроенные функции, которые работают с наследованием:

  • Используйте isinstance() для проверки типа экземпляра класса: isinstance(obj, int) будет истинным True только в том случае, если obj.__class__ равен int или класс является производным от класса int.

  • Используйте issubclass() для проверки наследования классов: issubclass(bool, int) является истинным, так как bool является подклассом int(). Однако issubclass(float, int) является ложным False, так как float не является подклассом int.

Примеры использования переопределения методов.
class One:
    def __init__(self, name):
        self.name = name

    def talk(self):
        return f'Меня зовут {self.name}'

    def say(self):
        return f'Привет {self.name}'


class Two(One):
    # декорируем метод
    def say(self):
        x = One.say(self)
        return f'{x} !!!'


class Three(One):
    # переопределяем метод
    def say(self, word):
        return f'{word} {self.name}...'

one = One('Андрей')
two = Two('Юра')
three = Three('Аня')

Результат:

print(f'class {One.__name__}:')
print(one.talk())
print(one.say())
# class One:
# Меня зовут Андрей
# Привет Андрей

print(f'class {Two.__name__}:')
print(two.talk())
print(two.say())
print('Two is subclass One:', issubclass(Two, One))
# class Two:
# Меня зовут Юра
# Привет Юра !!!
# Two is subclass One: True

print(f'class {Three.__name__}:')
print(three.talk())
print(three.say('Пока'))
print('Three is subclass One:', issubclass(Three, One))
# class Three:
# Меня зовут Аня
# Пока Аня...
# Three is subclass One: True

print('Three is subclass Two:', issubclass(Three, Two))
# Three is subclass Two: False

Курс Java Core — Лекция: Наследование. Преимущество наследования

— Привет, Амиго! Сейчас будет одна тема, которой, я думаю, ты будешь частенько пользоваться. Это – наследование.

Программирование, для несведущих, неотличимо от магии. Поэтому начну с такой интересной аналогии…

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

В программировании такой процесс называется «наследование». Предположим тебе нужно написать очень сложный класс. Писать с нуля долго, потом еще долго все тестировать и искать ошибки. Зачем идти самым сложным путем? Лучше поискать – а нет ли уже такого класса?

Предположим, ты нашел класс, который своими методами реализует 80% нужной тебе функциональности. Ты можешь просто скопировать его код в свой класс. Но у такого решения есть несколько минусов:

1) Найденный класс уже может быть скомпилирован в байт-код, а доступа к его исходному коду у тебя нет.

2) Исходный код класса есть, но ты работаешь в компании, которую могут засудить на пару миллиардов за использование даже 6 строчек чужого кода. А потом она засудит тебя.

3) Ненужное дублирование большого объема кода. Кроме того, если автор чужого класса найдет в нем ошибку и исправит ее, у тебя эта ошибка останется.

Есть решение потоньше, и без необходимости получать легальный доступ к коду оригинального класса. В Java ты можешь просто объявить тот класс родителем твоего класса. Это будет эквивалентно тому, что ты добавил код того класса в код своего. В твоем классе появятся все данные и все методы класса-родителя. Например, можно делать так: наследуемся от «лошади», добавляем «крылья» — получаем «пегаса»

— Очень интересно, продолжай.

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

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

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

— Король, Ферзь, Слон, Конь, Ладья и Пешка.

— Отлично. Ничего не упустил.

— А какие бы данные ты предложил хранить в этих классах?

— Координаты x и y, а также ее ценность (worth). Ведь некоторые фигуры ценнее других.

— А в чем отличия этих классов?

— Отличия в том, как они ходят, фигуры. В поведении.

— Да. Вот как можно было бы описать их в виде классов

class King
{
int x;
int y;
int worth;
void kingMove()
{
//код, решающий,
//как пойдет король
}
}
class Queen
{
int x;
int y;
int worth;
void queenMove()
{
//код, решающий,
//как пойдет ферзь
}
}
class Rook
{
int x;
int y;
int worth;
void rookMove()
{
//код, решающий,
//как пойдет ладья
}
}
class Knight
{
int x;
int y;
int worth;
void knightMove()
{
//код, решающий,
//как пойдет конь
}
}
class Bishop
{
int x;
int y;
int worth;
void bishopMove()
{
//код, решающий,
//как пойдет слон
}
}
class Pawn
{
int x;
int y;
int worth;
void pawnMove()
{
//код, решающий, 
//как пойдет пешка
}
}

— Да, именно так я бы и написал.

— А вот, как можно было бы сократить код с помощью наследования. Мы могли бы вынести одинаковые методы и данные в общий класс. Назовем его ChessItem. Объекты класса ChessItem не имеет смысла создавать, так как ему не соответствует ни одна шахматная фигура, но от него было бы много пользы:

class King extends ChessItem
{
void kingMove()
{
//код, решающий,
//как пойдет король
}
}
class Queen extends ChessItem
{
void queenMove()
{
//код, решающий,
//как пойдет ферзь
}
}
class Rook extends ChessItem
{
void rookMove()
{
//код, решающий,
//как пойдет ладья
}
}
 class ChessItem
{
int x;
int y;
int worth;
}
 
class Knight extends ChessItem
{
void knightMove()
{
//код, решающий,
//как пойдет конь
}
}
class Bishop extends ChessItem
{
void bishopMove()
{
//код, решающий,
//как пойдет слон
}
}
class Pawn extends ChessItem
{
void pawnMove()
{
//код, решающий,
//как пойдет пешка
}
}

— Как интересно.

— Именно! Особенно много преимуществ мы получаем, когда в проекте тысячи различных объектов и сотни классов. Тогда правильно подобранными классами можно не только существенно упростить логику, но и сократить код в десятки раз.

— А что нужно чтобы унаследовать какой-то класс?

— Для этого после объявления нашего класса нужно указать ключевое слово extends и написать имя родительского класса. Унаследоваться можно только от одного класса.

На картинке мы видим «корову», унаследованную от «свиньи». «Свинья» унаследована от «курицы», «курица» от «яйца». Только один родитель! Такое наследование не всегда логично. Но если есть только свинья, а очень нужна корова, программист зачастую не может устоять перед желанием сделать «корову» из «свиньи».

— А если мне хочется унаследоваться от двух классов. Можно же что-то сделать?!

— Почти ничего. Множественного наследования классов в Java нет: класс может иметь только одного класса-родителя. Но есть множественное наследование интерфейсов. Это немного снижает остроту проблемы.

— Ясно. А что такое интерфейс?

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

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

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

При создании новый класс является наследников членов и методов ранее определенного базового класса. Создаваемый путем наследования класс является производным (derived class), который в свою очередь может выступать в качестве базового класса (based class) для создаваемых классов. Если имена методов производного и базового классов совпадают, то методы производного класса перегружают методы базового класса.

При использовании наследования члены и методы кроме свойств public и private могут иметь свойство protected. Для одиночного класса описатели protected и private равносильны. Разница между protected и private проявляется при наследовании. Закрытые члены и методы, объявленные в базовом классе как protected, в производном могут использоваться как открытые (public). Защищенные (protected) члены и методы являются промежуточным вариантом между public и private.

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

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

class name_derived_class:
type_inheritance base_class
{
//закрытые методы и члены класса

public:
//открытые методы и члены класса

protected:
//защищенные методы и члены класса

};

Здесь name_derived_class — имя создаваемого производного класса, type_inheritance — способ наследования, возможны следующие способы наследования: public, private и protected; base_class — имя базового типа. Следует различать тип доступа к элементам в базовом классе и тип наследования.

Типы наследования и типы доступа

Способ доступаСпецификатор в базовом классеДоступ в производном классе
privateprivate
protected
public
нет
private
private
protectedprivate
protected
public
нет
protected
protected
publicprivate
protected
public
нет
protected
public

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

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

class new_class;

Это описание аналогично описанию прототипов функции.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

#include «stdafx.h»
#include <iostream>
#include <math.h>
#define PI 3.14159;
using namespace std;
//базовый класс figure
class figure
{
public:
//n-количество сторон фигуры для окружности n=1
int n;
//p-массив длин сторон фигуры, для окружности в p хранится радиус
float *p;
//конструктор
figure();
//метод вычисления периметра фигуры
float perimetr();
//метод вычисления площади фигуры
virtual float square();
//метод вывода информации о фигуре:
//ее название, периметр, площадь и т.д.
virtual void show_parametri();
};
//конструктор класса фигуры
figure::figure()
{
cout<<» Это абстрактный конструктор «<<endl;
}
//метод вычисления периметра, он будет
//перегружаться только в классе _circle
float figure::perimetr()
{
int i;
float psum;
for (psum=0, i=0; i<n; psum+=p[i], i++)
return psum;
}
//метод вычисления площади, пока он абстрактный, в
//каждом классе будет перегружаться реальным методом
float figure::square()
{
cout<<» Квадратная фигура не абстрактна «<<endl;
return 0;
}
//метод вывода информации о фигуре будет
//перегружаться в каждом производном классе
void figure::show_parametri()
{
cout<<» Абстрактная фигура «;
}
//производный класс _circle (окружность) основанный
//на классе figure
class _circle:public figure
{
public:
//конструктор
_circle();
//перегружаемые методы perimetr(), square(), show_parametri()
float perimetr();
virtual float square();
virtual void show_parametri();
};
//производный класс RecTangle (прямоугольник),
//основанный на классе figure
class RecTangle:public figure
{
public:
//конструктор
RecTangle();
//перегружаемые методы square(), show_parametri()
virtual float square();
virtual void show_parametri();
};
//главная функция
void main()
{
setlocale(LC_ALL,«Rus»);
_circle RR;
RR.show_parametri();
RecTangle PP;
PP.show_parametri();
system(«pause»);
}
//конструктор класса _circle
_circle::_circle()
{
cout<<» Параметры окружности «<<endl;
//в качестве сторон окружности выступает
//единственный параметр радиус
n=1;
p=new float[n];
cout<<» Введите радиус «<<endl;
cin>>p[0];
}
//метод вычисления периметра окружности
float _circle::perimetr()
{
return 2*PI*p[0];
}
//метод вычисления площади окружности
float _circle::square()
{
return PI*p[0]*p[0];
}
//метод вывода параметров окружности
void _circle::show_parametri()
{
//вывод сообщения о том, что это окружность
cout<<» Это окружность «<<endl;
//вывод радиуса окружности
cout<<» Радиус = «<<p[0]<<endl;
//вывод периметра окружности
cout<<» Периметр = «<<perimetr()<<endl;
//вывод площади окружности
cout<<» Площадь = «<<square()<<endl;
}
//конструктор класса RecTangle
RecTangle::RecTangle()
{
cout<<» Параметры прямоугольника «<<endl;
//количество сторон = 4
n=4;
p=new float[n];
//ввод длин сторон прямоугольника
cout<<» Введите длину сторон «;
cin>>p[0]>>p[1];
p[2]=p[0];
p[3]=p[1];
}
//метод вычисления площади прямоугольника
float RecTangle::square()
{
return p[0]*p[1];
}
//метод вывода параметров прямоугольника
void RecTangle::show_parametri()
{
//вывод сообщения о том, что это прямоугольник
cout<<» Это прямоугольник «<<endl;
//вывод длины сторон прямоугольника
cout<<» a= «<<p[0]<<» b= «<<p[1]<<endl;
//вывод периметра прямоугольника. Обратите внимание,
//что в классе RecTangle вызывается метод perimetr()
//базового класса (figure)
cout<<» Периметр = «<<perimetr()<<endl;
//вывод площади прямоугольника
cout<<» Площадь = «<<square()<<endl;
}


Похожие записи:

Pythonicway — Наследование в Python

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

При создании нового объекта (класса), как в программировании, так и в реальной жизни, мы можем использовать 2 подхода:

  1. Создать экземпляр с нуля.
  2. Использовать уже существующий объект и обработать напильником довести до нужного нам состояния.

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

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

class Tree(object):
     def __init__(self, kind, height):
         self.kind = kind
         self.age = 0
         self.height = height

    def info(self):
         """ Метод вывода информации о дереве """
         print ("{} years old {}. {} meters high.".format(self.age, self.kind, self.height))    

   def grow(self):
        """ Метод роста """
        self.age += 1
        self.height += 0.5

Для проверки создадим экземпляр класса дерево и немного поиграем с ним.

tree_1 = Tree("oak", 0.5)
tree_1.info()
tree_1.grow()
tree_1.info()

Теперь представим, что наша задача усложняется: нам необходимо добавить класс фруктового дерева, у которого будут те же атрибуты и методы, но в дополнение появится метод сбора урожая фруктов. Конечно, мы можем создать с нуля класс FruitTree и переписать наново весь код, который будет практически идентичен тому что мы уже написали, но это будет неэффективно, кроме того, нарушается принцип DRY (Don’t Repeat Yourself).  Вместо этого мы можем воспользоваться наследованием, то есть создать класс FruitTree и указать ему в качестве родителя класс Tree. Таким образом, FruitTree получит доступ ко всем атрибутам и методам, которыми обладает класс Tree.

Как указать родительский класс в Python.

Родительский класс помещается в скобки после имени класса.

class FruitTree(Tree):

 Таким образом, наш код для класса фруктового дерева будет следующим:

class FruitTree(Tree):
    def __init__(self, kind, height):
        # Необходимо вызвать метод инициализации родителя.
        # В Python 3.x это делается при помощи функции super()
        super().__init__(kind, height)

    def give_fruits(self):
        print ("Collected 20kg of {}s".format(self.kind))

 Создадим экземпляр класса FruitTree:

tree_2 = FruitTree("apple", 0.7)
# у нас есть доступ к методам родителя
tree_2.info()
tree_2.grow()
# Мы можем использовать свой метод
tree_2.give_fruits()
# А для родительского экземпляра метод give_fruits() недоступен
tree_1.give_fruits() # Вызовет ошибку  

 

Зачем использовать наследование.

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

В нашей следующей статье мы рассмотрим множественное наследование в Python.

Наследование Python (с примерами)

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

Наследование — мощная функция объектно-ориентированного программирования.

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


Синтаксис наследования Python

класс BaseClass:
  Кузов базового класса
класс DerivedClass (BaseClass):
  Тело производного класса 

Производный класс наследует функции от базового класса, и к нему могут быть добавлены новые функции.Это приводит к повторному использованию кода.


Пример наследования в Python

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

Многоугольник — это замкнутая фигура с 3 или более сторонами. Скажем, у нас есть класс Polygon , который определяется следующим образом.

  класс Polygon:
    def __init __ (self, no_of_sides):
        self.n = no_of_sides
        self.sides = [0 для i в диапазоне (no_of_sides)]

    def inputSides (self):
        себя.Стороны = [float (input ("Enter side" + str (i + 1) + ":")) для i в диапазоне (self.n)]

    def dispSides (сам):
        для i в диапазоне (self.n):
            print ("Сторона", i + 1, "есть", self.sides [i])  

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

Метод inputSides () принимает величину каждой стороны, а dispSides () отображает эти длины сторон.

Треугольник — это многоугольник с 3 сторонами.Итак, мы можем создать класс под названием Triangle , который наследуется от Polygon . Это делает все атрибуты класса Polygon доступными для класса Triangle .

Нам не нужно определять их снова (возможность повторного использования кода). Треугольник можно определить следующим образом.

  класс Треугольник (Многоугольник):
    def __init __ (сам):
        Многоугольник .__ init __ (self, 3)

    def findArea (сам):
        a, b, c = self.sides
        # вычисляем полупериметр
        s = (a + b + c) / 2
        площадь = (s * (s-a) * (s-b) * (s-c)) ** 0.5
        print ('Площадь треугольника% 0.2f'% area)  

Однако класс Triangle имеет новый метод findArea () для поиска и печати площади треугольника. Вот пример выполнения.

  >>> t = Треугольник ()

>>> t.inputSides ()
Введите сторону 1: 3
Введите сторону 2: 5
Введите сторону 3: 4

>>> t.dispSides ()
Сторона 1 - 3,0
Сторона 2 - 5,0
Сторона 3 - 4,0

>>> t.findArea ()
Площадь треугольника 6,00  
.

Мы видим, что, хотя мы не определили методы вроде inputSides () или dispSides () для класса Triangle отдельно, мы смогли их использовать.

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


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

В приведенном выше примере обратите внимание, что метод __init __ () был определен в обоих классах, Triangle и Polygon . Когда это происходит, метод в производном классе переопределяет метод в базовом классе. То есть __init __ () в Triangle получает предпочтение перед __init__ в Polygon .

Обычно при переопределении базового метода мы склонны расширять определение, а не просто заменять его. То же самое делается путем вызова метода в базовом классе из метода в производном классе (вызов Polygon .__ init __ () из __init __ () в Triangle ).

Лучше использовать встроенную функцию super () . Итак, super () .__ init __ (3) эквивалентно Polygon .__ init __ (self, 3) и является предпочтительным.Чтобы узнать больше о функции super () в Python, посетите функцию Python super ().

Две встроенные функции isinstance () и issubclass () используются для проверки наследования.

Функция isinstance () возвращает True , если объект является экземпляром класса или других классов, производных от него. Каждый класс в Python наследуется от объекта базового класса .

  >>> isinstance (t, Треугольник)
Истинный

>>> isinstance (t, Многоугольник)
Истинный

>>> isinstance (t, int)
Ложь

>>> isinstance (t, объект)
Правда  

Аналогично, issubclass () используется для проверки наследования классов.

  >>> issubclass (Многоугольник, Треугольник)
Ложь

>>> issubclass (Треугольник, Многоугольник)
Истинный

>>> issubclass (булево, интервал)
Правда  
Учебник по программированию на Ruby

- наследование классов и модули

В предыдущей главе мы кратко говорили о наследовании. Наследование - это когда класс наследует поведение от другого класса. Класс, наследующий поведение, называется подклассом, а класс, от которого он наследуется, называется суперклассом.

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

Здесь мы извлекаем метод speak в суперкласс Animal и используем наследование, чтобы сделать это поведение доступным для классов GoodDog и Cat .

  класс Животное
  деф говорить
    "Привет!"
  конец
конец

класс GoodDog  Здравствуйте!
ставит paws.speak # => Здравствуйте!
  

Мы используем символ <, чтобы обозначить, что класс GoodDog наследуется от класса Animal . Это означает, что все методы класса Animal доступны для использования классу GoodDog . Мы также создали новый класс под названием Cat , который также наследуется от Animal . Мы удалили метод speak из класса GoodDog , чтобы использовать метод speak из Animal .

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

Но что, если мы хотим использовать исходный метод speak только из класса GoodDog . Давайте добавим его обратно и посмотрим, что произойдет.

  класс Животное
  деф говорить
    "Привет!"
  конец
конец

класс GoodDog  Спарки говорит arf!
ставит paws.speak # => Здравствуйте!
  

В классе GoodDog мы переопределяем метод speak в классе Animal , потому что Ruby сначала проверяет класс объекта на наличие метода, прежде чем он будет искать в суперклассе. Это означает, что когда мы написали код sparky.speak , он сначала посмотрел на класс sparky , то есть GoodDog .Он нашел там метод , говорите и использовал его. Когда мы написали код paws.speak , Ruby сначала посмотрел на класс paws , то есть Cat . Он не нашел там метода говорить , поэтому продолжил поиск в суперклассе Cat , Animal . В Animal был найден метод говорящего и использован его. Мы поговорим об этом пути поиска метода более подробно чуть позже.

Наследование может быть отличным способом устранения дублирования в вашей кодовой базе.В сообществе Ruby вы часто встретите аббревиатуру «DRY». Это расшифровывается как «Не повторяйся». Это означает, что если вы обнаружите, что повторяете одну и ту же логику снова и снова в своих программах, есть способы извлечь эту логику в одно место для повторного использования.

Ruby предоставляет нам ключевое слово super для вызова методов ранее в пути поиска методов. Когда вы вызываете super из метода, он выполняет поиск метода с таким же именем в пути поиска метода, а затем вызывает его.Давайте посмотрим на быстрый пример того, как это работает:

  класс Животное
  деф говорить
    "Привет!"
  конец
конец

класс GoodDog  "Привет! из класса GoodDog"
  

В приведенном выше примере мы создали простой класс Animal с методом экземпляра speak . Затем мы создали GoodDog , который является подклассом Animal , также с методом экземпляра speak , чтобы переопределить унаследованную версию.Однако в методе подкласса speak мы используем super для вызова метода speak из суперкласса Animal , а затем расширяем функциональность, добавляя текст к возвращаемому значению.

Другой более распространенный способ использования super - это инициализировать . Давайте посмотрим на иллюстрацию этого:

  класс Животное
  attr_accessor: имя

  def инициализировать (имя)
    @name = имя
  конец
конец

класс GoodDog  # 
  

Интересная концепция, которую мы хотим объяснить, - это использование super в классе GoodDog . В этом примере мы используем super без аргументов. Однако метод инициализации , в котором используется super , принимает аргумент и добавляет новый поворот в способ вызова super . Здесь, в дополнение к поведению по умолчанию, super автоматически пересылает аргументы, которые были переданы методу, из которого вызывается super ( инициализирует метод в классе GoodDog ).На этом этапе super передаст аргумент color в initialize , определенном в подклассе, для суперкласса Animal и вызовет его. Это объясняет наличие @ name = "brown" при создании экземпляра bruno . Наконец, подкласс initialize продолжает устанавливать переменную экземпляра @color .

При вызове с определенными аргументами, например. super (a, b) , указанные аргументы будут отправлены вверх по цепочке поиска метода.Давайте посмотрим на быстрый пример:

  класс BadDog <Животное
  def initialize (возраст, имя)
    супер (имя)
    @age = возраст
  конец
конец

BadDog.new (2, "медведь") # => # 
  

Это похоже на наш предыдущий пример, с той разницей, что super принимает аргумент, следовательно, переданный аргумент отправляется суперклассу. Следовательно, в этом примере, когда создается класс BadDog , переданный аргумент name («медведь») передается суперклассу и устанавливается в переменную экземпляра @name .

И последний поворот. Если вы вызываете super () точно так, как показано - с круглыми скобками - он вызывает метод в суперклассе вообще без аргументов. Если в вашем суперклассе есть метод, который не принимает аргументов, это самый безопасный, а иногда и единственный способ его вызова:

  класс Животное
  def инициализировать
  конец
конец

класс Медведь <Животное
  def инициализировать (цвет)
    супер()
    @color = цвет
  конец
конец

bear = Bear.new ("black") # => # 
  

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

Еще один способ ОСУШИТЬ код в Ruby - использовать модули . Мы уже немного видели, как использовать модули, но здесь мы приведем еще несколько примеров.

Извлечение общих методов в суперкласс, как мы делали в предыдущем разделе, - отличный способ моделирования понятий, которые являются естественно иерархическими. Мы привели в пример животных. У нас есть общий суперкласс под названием Animal , который может сохранять все базовое поведение всех животных. Затем мы можем немного расширить модель и, возможно, получить подкласс Mammal от Animal .Мы можем представить всю иерархию классов, как на рисунке ниже.

На приведенной выше диаграмме показано, как выглядит наследование на основе чистого класса. Помните, что цель этого - поместить правильное поведение (то есть методы) в правильный класс, чтобы нам не нужно было повторять код в нескольких классах. Мы можем представить, что все объекты Fish связаны с животными, которые живут в воде, поэтому, возможно, метод swim должен быть в классе Fish .Мы также можем представить, что у всех объектов Mammal будет теплая кровь, поэтому мы можем создать метод под названием warm_blooded? в классе Mammal и вернуть true . Следовательно, объекты Cat и Dog будут иметь доступ к warm_blooded? , который автоматически наследуется от Mammal классами Cat и Dog , но у них не будет доступа к методам в классе Fish .

Этот тип иерархического моделирования до некоторой степени работает, но всегда есть исключения. Например, мы поместили метод swim в класс Fish , но некоторые млекопитающие также могут плавать. Мы не хотим перемещать метод swim в Animal , потому что не все животные плавают, и мы не хотим создавать еще один метод swim в Dog , потому что это нарушает принцип DRY. Для подобных проблем мы хотели бы сгруппировать их в модуль, а затем смешать с этого модуля с классами, которым требуется такое поведение.Вот пример:

  модуль для плавания
  def плавать
    "Я плыву!"
  конец
конец

класс Animal; конец

class Fish <Животное
  включить смешивание Swimmable # в модуль Swimmable
конец

class Mammal <Животное
конец

class Cat <Млекопитающее
конец

класс Собака <Млекопитающее
  включить Swimmable # смешивание в Swimmable модуль
конец
  

А теперь объекты Fish и Dog могут плавать, но объекты других классов не смогут:

  Спарки = Собака.новый
neemo = Fish.new
paws = Cat.new

sparky.swim # => Я плаваю!
neemo.swim # => Я плаваю!
paws.swim # => NoMethodError: неопределенный метод `swim 'для # 
  

Использование модулей для группировки общих поведений позволяет нам создавать более мощный, гибкий и СУХОЙ дизайн.

Примечание. Обычным соглашением об именах для Ruby является использование суффикса «способный» к любому глаголу, описывающему поведение, моделируемое модулем.Вы можете увидеть это соглашение с нашим модулем Swimmable . Точно так же мы могли бы назвать модуль, который описывает «ходьбу», как Walkable . Не все модули названы таким образом, однако это довольно распространенное явление.

Теперь вы знаете два основных способа, которыми Ruby реализует наследование. Наследование классов - это традиционный способ думать о наследовании: один тип наследует поведение другого типа. Результатом является новый тип, который специализируется на типе суперкласса.Другая форма иногда называется наследованием интерфейса : здесь вступают в игру модули миксинов. Класс не наследуется от другого типа, а вместо этого наследует интерфейс, предоставляемый модулем mixin. В этом случае тип результата не является специализированным типом по отношению к модулю.

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

  • Вы можете создать подкласс (наследование класса) только от одного класса.Вы можете смешивать столько модулей (наследование интерфейса), сколько захотите.
  • Если существует связь «is-a», обычно правильным выбором является наследование класса. Если существует связь «есть», обычно лучше использовать наследование интерфейса. Например, собака - это «животное, и она« умеет плавать ».
  • Вы не можете создать экземпляры модулей (т.е. из модуля нельзя создать объект). Модули используются только для размещения имен и группирования общих методов вместе.

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

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

  модуль пешеходный
  деф прогулка
    "Я иду."
  конец
конец

модуль Swimmable
  def плавать
    "Я плыву."
  конец
конец

модуль Альпинистский
  определенно подняться
    «Я лазаю».
  конец
конец

класс Animal
  включить пешеходный

  деф говорить
    "Я животное, и я говорю!"
  конец
конец
  

У нас есть три модуля и один класс.Мы смешали один модуль с классом Animal . Путь поиска метода - это путь, по которому Ruby ищет метод. Мы можем увидеть этот путь с помощью метода класса ancestors .

  помещает "--- Поиск метода животных ---"
помещает Animal.ancestors
  

Результат выглядит так:

  --- Поиск метода на животных ---
Животное
Пешеходный
Объект
Ядро
BasicObject
  

Это означает, что когда мы вызываем метод любого объекта Animal , сначала Ruby просматривает класс Animal , затем модуль Walkable , затем класс Object , затем модуль Kernel и, наконец, BasicObject класс.

  fido = Animal.new
fido.speak # => Я животное, и я говорю!
  

Руби нашла метод speak в классе Animal и больше не стала искать.

  fido.walk # => Я иду.
  

Ruby сначала искал метод экземпляра walk в Animal и, не найдя его там, продолжил поиск в следующем месте в соответствии с нашим списком, которым является модуль Walkable .Он увидел там метод walk , выполнил его и перестал смотреть дальше.

  fido.swim
  # => NoMethodError: неопределенный метод `swim 'для # 
  

Ruby просмотрел все классы и модули в списке и не нашел метод swim , поэтому он выдал ошибку.

Давайте добавим еще один класс в приведенный выше код. Этот класс наследуется от класса Animal и смешивается с модулями Swimmable и Climbable .

  класс GoodDog <Животное
  включить Swimmable
  включать альпинистский
конец

помещает "--- поиск метода GoodDog ---"
ставит GoodDog.ancestors
  

И вот результат:

  --- Поиск метода GoodDog ---
Хорошая собака
Альпинистский
Плавать
Животное
Пешеходный
Объект
Ядро
BasicObject
  

В вышеприведенном выводе есть несколько интересных моментов. Во-первых, это говорит нам о важности порядка, в котором мы включаем модули. На самом деле Ruby просматривает последний модуль, который мы включили , первый .Это означает, что в редких случаях, когда модули, которые мы смешиваем, содержат метод с тем же именем, первым будет обращаться к последнему включенному модулю. Вторая интересная вещь заключается в том, что модуль, включенный в суперкласс, попал в путь поиска метода. Это означает, что все объекты GoodDog будут иметь доступ не только к методам Animal , но и к методам, определенным в модуле Walkable , а также ко всем другим модулям, смешанным с любым из его суперклассов.

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

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

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

  модуль Млекопитающее
  класс Dog
    def говорить (звук)
      p "# {звук}"
    конец
  конец

  класс Cat
    def say_name (имя)
      p "# {имя}"
    конец
  конец
конец
  

Мы вызываем классы в модуле, добавляя имя класса к имени модуля с двумя двоеточиями ( :: )

  приятель = Млекопитающее :: Собака.новый
kitty = Млекопитающее :: Cat.new
buddy.speak ('Arf!') # => "Арф!"
kitty.say_name ('котенок') # => "котенок"
  

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

  модуль Млекопитающее
  ...

  def self.some_out_of_place_method (число)
    число ** 2
  конец
конец
  

Такое определение методов в модуле означает, что мы можем вызывать их прямо из модуля:

  значение = Mammal.some_out_of_place_method (4)
  

Мы также можем вызвать такие методы, выполнив:

  значение = Млекопитающее :: some_out_of_place_method (4)
  

, хотя первый вариант является предпочтительным.

Последнее, о чем мы хотим рассказать, это то, что на самом деле довольно просто, но необходимо; Контроль доступа к методам.Контроль доступа - это концепция, которая существует во многих языках программирования, включая Ruby. Обычно это реализуется с помощью модификаторов доступа . Назначение модификаторов доступа - разрешить или ограничить доступ к определенной вещи. В Ruby мы озабочены ограничением доступа к методам, определенным в классе. Таким образом, в контексте Ruby вы обычно увидите эту концепцию, называемую Method Access Control .

Способ реализации управления доступом к методам в Ruby заключается в использовании модификаторов доступа public , private и protected .Сейчас все методы в нашем классе GoodDog являются общедоступными. Открытый метод - это метод, доступный любому, кто знает имя класса или имя объекта. Эти методы легко доступны для использования остальной частью программы и составляют интерфейс класса (именно так другие классы и объекты будут взаимодействовать с этим классом и его объектами).

Иногда у вас есть методы, которые выполняют работу в классе, но не должны быть доступны для остальной части программы.Эти методы можно определить как private . Как мы определяем частные методы? Мы используем вызов метода private в нашей программе, и все, что ниже него, является частным (если после него не вызывается другой метод, например, protected , чтобы отменить его).

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

  класс GoodDog
  DOG_YEARS = 7

  attr_accessor: name,: age

  def инициализировать (n, a)
    self.name = n
    self.age = a
  конец

  частный

  def human_years
    возраст * DOG_YEARS
  конец
конец

sparky = GoodDog.new ("Спарки", 4)
sparky.human_years
  

Получаем сообщение об ошибке:

  NoMethodError: вызов частного метода human_years
  # 
  

Мы сделали метод human_years закрытым, поместив его в метод private .Тогда для чего это нужно, если мы не можем это назвать? частные методы доступны только из других методов в классе. Например, с учетом приведенного выше кода допускается следующее:

  # предполагаем, что определение метода ниже находится над "частным" методом

def public_disclosure
  "# {self.name} по человеческим годам равно # {human_years}"
конец
  

Обратите внимание, что в этом случае мы можем , а не , использовать self.human_years , потому что метод human_years является частным.Помните, что self.human_years эквивалентно sparky.human_years , что недопустимо для частных методов. Следовательно, нам нужно просто использовать человеко-лет . Таким образом, частные методы вообще недоступны вне определения класса и доступны только изнутри класса при вызове без self .

Начиная с Ruby 2.7, теперь разрешено вызывать частные методы с буквальным self в качестве вызывающего. Обратите внимание, что , а не , означает, что мы можем вызвать частный метод с любым другим объектом, даже не с одним и тем же типом.Мы можем вызвать только частный метод с текущим объектом.

Общедоступные и частные методы являются наиболее распространенными, но в некоторых менее распространенных ситуациях нам понадобится промежуточный подход. Мы можем использовать метод protected для создания защищенных методов . Самый простой способ понять защищенные методы - следовать этим двум правилам:

  • изнутри класса, защищенных методов доступны так же, как общедоступных методов .
  • извне класса, защищенных методов действуют так же, как частных методов .

Давайте посмотрим на несколько примеров:

  класс Животное
  def a_public_method
    «Это сработает?» + Self.a_protected_method
  конец

  защищенный

  def a_protected_method
    "Да, я защищен!"
  конец
конец
  

Изучите приведенный выше код, так как он немного сложен. Мы создадим объект Animal и протестируем его.

  fido = Animal.new
fido.a_public_method # => Это сработает? Да, я защищен!
  

Приведенная выше строка кода показывает нам, что мы можем вызвать метод , защищенный изнутри класса, даже с добавленным self .А что насчет вне класса?

  fido.a_protected_method
  # => NoMethodError: вызываемый защищенный метод `a_protected_method '
    # <Животное: 0x007fb174157110>
  

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

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

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

  класс Родительский
  def say_hi
    p "Привет от родителей."
  конец
конец

Родитель.суперкласс # => Объект
  

Это означает, что методы, определенные в классе Object , доступны в всех классах .

Кроме того, напомним, что с помощью магии наследования подкласс может переопределить метод суперкласса.

  class Child <Родитель
  def say_hi
    p "Привет от ребенка."
  конец
конец

child = Child.new
child.say_hi # => «Привет от ребенка».
  

Это означает, что если вы случайно переопределите метод, изначально определенный в классе Object , это может иметь далеко идущие последствия для вашего кода.Например, send - это метод экземпляра, который все классы наследуют от объекта Object . Если вы определили новый метод экземпляра send в своем классе, все объекты вашего класса будут вызывать ваш собственный метод send вместо метода в классе Object , который, вероятно, они хотят вызвать. Объект send служит способом вызова метода, передавая ему символ или строку, представляющую метод, который вы хотите вызвать. Следующая пара аргументов будет представлять аргументы метода, если таковые имеются.Давайте посмотрим, как обычно работает send, используя наш Child class:

  сын = Child.new
son.send: say_hi # => «Привет от ребенка».
  

Давайте посмотрим, что произойдет, когда мы определим метод send в нашем классе Child , а затем попытаемся вызвать метод send объекта Object :

  класс Детский
  def say_hi
    p "Привет от ребенка."
  конец

  def отправить
    p "отправить от ребенка ..."
  конец
конец

lad = Ребенок.новый
парень. отправить: say_hi
  

Обычно мы ожидаем, что на выходе этого вызова будет «Привет от ребенка». , но при запуске кода мы получаем совершенно другой результат:

  ArgumentError: неправильное количество аргументов (1 вместо 0)
from (pry): 12: в `отправить '
  

В нашем примере мы передаем send с одним аргументом, хотя наш переопределенный метод send не принимает никаких аргументов. Давайте посмотрим на другой пример, исследуя объект instance_of? метод.Этот удобный метод возвращает true, , если объект является экземпляром данного класса, и false, в противном случае. Посмотрим на это в действии:

  c = Child.new
c.instance_of? Ребенок # => истина
c.instance_of? Родитель # => ложь
  

Теперь давайте переопределим instance_of? внутри Детский :

  класс Детский
  # другие методы пропущены

  def instance_of?
    p "Я подделка"
  конец
конец

наследник = Ребенок.новый
heir.instance_of? Ребенок
  

И снова мы увидим нечто совершенно иное, хотя мы намеревались использовать объект instance_of? метод:

  ArgumentError: неправильное количество аргументов (1 вместо 0)
from (pry): 22: в `instance_of? '
  

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

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

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

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

Руководство по ООП Python - Настоящий Python

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

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

Когда вы пишете код Python с использованием классов, вы используете наследование, даже если вы не знаете, что используете его. Давайте посмотрим, что это значит.

Объект Суперкласс

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

>>>
  >>> класс MyClass:
...     проходить
...
  

Вы объявили класс MyClass , который мало что делает, но он иллюстрирует самые основные концепции наследования. Теперь, когда у вас объявлен класс, вы можете использовать функцию dir () для вывода списка его членов:

>>>
  >>> c = MyClass ()
>>> dir (c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
  

dir () возвращает список всех членов в указанном объекте.Вы не заявили ни одного участника в MyClass , так откуда берется список? Вы можете узнать с помощью интерактивного переводчика:

>>>
  >>> o = объект ()
>>> dir (o)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
  

Как видите, два списка почти идентичны.В MyClass есть несколько дополнительных членов, например __dict__ и __weakref__ , но каждый отдельный член класса объекта также присутствует в MyClass .

Это связано с тем, что каждый класс, который вы создаете в Python, неявно является производным от объекта . Вы могли бы быть более явным и написать class MyClass (object): , но это избыточно и ненужно.

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

Исключения являются исключением

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

Вы можете увидеть проблему с помощью интерактивного интерпретатора Python:

>>>
  >>> класс MyError:
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: исключения должны быть производными от BaseException
  

Вы создали новый класс, чтобы указать тип ошибки.Затем вы попытались использовать его, чтобы вызвать исключение. Возникает исключение, но в выходных данных указано, что исключение имеет тип TypeError , а не MyError , и что все исключения должны быть производными от BaseException .

BaseException - это базовый класс, предоставляемый для всех типов ошибок. Чтобы создать новый тип ошибки, вы должны унаследовать свой класс от BaseException или одного из его производных классов. По соглашению в Python пользовательские типы ошибок выводятся из исключения Exception , которое, в свою очередь, является производным от BaseException .

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

>>>
  >>> класс MyError (Исключение):
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
__main __. MyError
  

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

Создание иерархии классов

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

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

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

Вы начинаете с реализации класса PayrollSystem , который обрабатывает расчет заработной платы:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')
  

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

Теперь вы реализуете базовый класс Employee , который обрабатывает общий интерфейс для каждого типа сотрудника:

  # В часах в год

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя
  

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

Система HR требует, чтобы каждый обработанный Сотрудник предоставлял интерфейс .calculate_payroll () , который возвращает еженедельную зарплату для сотрудника. Реализация этого интерфейса различается в зависимости от типа Сотрудник .

Например, у административных работников фиксированная зарплата, поэтому каждую неделю они получают одинаковую сумму:

  # В час.ру

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary
  

Вы создаете производный класс SalaryEmployee , который наследует Employee . Класс инициализируется идентификатором id и именем , требуемым базовым классом, и вы используете super () для инициализации членов базового класса.Вы можете прочитать все о super () в Supercharge Your Classes With Python super ().

SalaryEmployee также требуется параметр инициализации weekly_salary , который представляет сумму, которую сотрудник зарабатывает в неделю.

Класс предоставляет требуемый метод .calculate_payroll () , используемый системой HR. Реализация просто возвращает сумму, хранящуюся в weekly_salary .

В компании также работают рабочие, получающие почасовую оплату, поэтому вы добавляете HourlyEmployee в систему управления персоналом:

  # В час.ру

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate
  

Класс HourlyEmployee инициализируется идентификатором id и именем , как и базовый класс, плюс hours_worked и hour_rate , необходимых для расчета заработной платы.Метод .calculate_payroll () реализуется путем возврата количества отработанных часов, умноженного на почасовую ставку.

Наконец, в компании работают торговые партнеры, которым выплачивается фиксированная заработная плата плюс комиссия, основанная на их продажах, поэтому вы создаете класс CommissionEmployee :

  # В часах в год

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        себя.комиссия = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы выводите CommissionEmployee из SalaryEmployee , потому что оба класса должны учитывать weekly_salary . В то же время CommissionEmployee инициализируется значением комиссии , которое основано на продажах для сотрудника.

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

Поскольку CommissionEmployee происходит от SalaryEmployee , у вас есть доступ напрямую к свойству weekly_salary , и вы могли бы реализовать .calculate_payroll () , используя значение этого свойства.

Проблема с прямым доступом к свойству заключается в том, что если реализация SalaryEmployee.calculate_payroll () изменится, вам также придется изменить реализацию CommissionEmployee.calculate_payroll () .Лучше полагаться на уже реализованный метод в базовом классе и расширять функциональность по мере необходимости.

Вы создали свою иерархию первого класса для системы. UML-диаграмма классов выглядит так:

На схеме показана иерархия наследования классов. Производные классы реализуют интерфейс IPayrollCalculator , который требуется для системы PayrollSystem . Реализация PayrollSystem.calculate_payroll () требует, чтобы переданные объекты employee содержали id , name и calculate_payroll () .

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

Приложение создает своих сотрудников и передает их в систему расчета заработной платы для обработки расчета заработной платы:

  # В program.py

импортные часы

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = hr.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = час.Система начисления заработной платы()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

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

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

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

Обратите внимание, что базовый класс Employee не определяет метод .calculate_payroll () . Это означает, что если вы создадите простой объект Employee и передадите его в PayrollSystem , то получите ошибку. Вы можете попробовать это в интерактивном интерпретаторе Python:

>>>
  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, «Недействительный»)
>>> payroll_system = hr.PayrollSystem ()
>>> payroll_system.calculate_payroll ([сотрудник])

Заработная плата для: 1 - недействительна
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
  Файл "/hr.py", строка 39, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
AttributeError: объект «Сотрудник» не имеет атрибута «calculate_payroll»
  

Хотя вы можете создать экземпляр объекта Employee , этот объект не может использоваться системой PayrollSystem .Почему? Потому что не может .calculate_payroll () для Сотрудника . Чтобы соответствовать требованиям PayrollSystem , вам нужно преобразовать класс Employee , который в настоящее время является конкретным классом, в абстрактный класс. Таким образом, ни один сотрудник не будет просто Сотрудником , но будет реализован .calculate_payroll () .

Абстрактные базовые классы в Python

Класс Employee в приведенном выше примере называется абстрактным базовым классом.Абстрактные базовые классы существуют для наследования, но никогда не создаются. Python предоставляет модуль abc для определения абстрактных базовых классов.

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

Модуль abc в стандартной библиотеке Python предоставляет функциональные возможности для предотвращения создания объектов из абстрактных базовых классов.

Вы можете изменить реализацию класса Employee , чтобы исключить возможность его создания:

  # В часах в год

from abc import ABC, abstractmethod

класс Сотрудник (ABC):
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

    @abstractmethod
    def calculate_payroll (самостоятельно):
        проходить
  

Вы наследуете Employee от ABC , что делает его абстрактным базовым классом. Затем вы украшаете .Calcul_payroll () с декоратором @abstractmethod .

У этого изменения есть два хороших побочных эффекта:

  1. Вы сообщаете пользователям модуля, что объекты типа Employee создавать нельзя.
  2. Вы говорите другим разработчикам, работающим над модулем hr , что если они являются производными от Employee , то они должны переопределить абстрактный метод .calculate_payroll () .

Вы можете видеть, что объекты типа Сотрудник не могут быть созданы с помощью интерактивного интерпретатора:

>>>
  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, 'аннотация')

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: невозможно создать экземпляр абстрактного класса Employee с абстрактными методами
Calcul_payroll
  

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

Наследование реализации и наследование интерфейса

Когда вы производите один класс от другого, производный класс наследует оба:

  1. Интерфейс базового класса: Производный класс наследует все методы, свойства и атрибуты базового класса.

  2. Реализация базового класса: Производный класс наследует код, реализующий интерфейс класса.

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

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

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

Чтобы проиллюстрировать это, теперь вы добавите класс DisgruntledEmployee в приведенный выше пример, который не является производным от Employee :

.
  # В disgruntled.py

класс DisgruntledEmployee:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

    def calculate_payroll (самостоятельно):
        возврат 1000000
  

Класс DisgruntledEmployee не является производным от Employee , но предоставляет тот же интерфейс, который требуется для PayrollSystem . PayrollSystem.calculate_payroll () требуется список объектов, реализующих следующий интерфейс:

  • Свойство или атрибут id , который возвращает идентификатор сотрудника
  • A name свойство или атрибут, представляющий имя сотрудника
  • А .Calcul_payroll () , который не принимает никаких параметров и возвращает сумму заработной платы для обработки

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

Вы можете изменить программу, чтобы использовать класс DisgruntledEmployee :

  # В program.py

импортные часы
недовольный импорт

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = час.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
disgruntled_employee = disgruntled.DisgruntledEmployee (20000, 'Анонимный')
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee,
    disgruntled_employee
])
  

Программа создает объект DisgruntledEmployee и добавляет его в список, обрабатываемый системой PayrollSystem .Теперь вы можете запустить программу и увидеть ее результат:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Заработная плата для: 20000 - Аноним
- Сумма чека: 1000000
  

Как видите, PayrollSystem все еще может обрабатывать новый объект, потому что он соответствует желаемому интерфейсу.

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

  • Используйте наследование для повторного использования реализации: Производные классы должны использовать большую часть своей реализации базового класса. Они также должны моделировать отношения как . Класс Customer также может иметь идентификатор id и имя , но Customer не является Employee , поэтому не следует использовать наследование.

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

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

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {сотрудник.Calcul_payroll ()} ')
            Распечатать('')

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы удалили импорт модуля abc , поскольку класс Employee не должен быть абстрактным.Вы также удалили из него абстрактный метод calculate_payroll () , поскольку он не предоставляет никакой реализации.

По сути, вы наследуете реализацию атрибутов id и name класса Employee в производных классах. Поскольку .calculate_payroll () - это просто интерфейс для метода PayrollSystem.calculate_payroll () , вам не нужно реализовывать его в базовом классе Employee .

Обратите внимание, как класс CommissionEmployee является производным от SalaryEmployee . Это означает, что CommissionEmployee наследует реализацию и интерфейс SalaryEmployee . Вы можете увидеть, как метод CommissionEmployee.calculate_payroll () использует реализацию базового класса, поскольку он полагается на результат super (). Calculate_payroll () для реализации своей собственной версии.

Проблема взрыва класса

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

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

Система ProductivitySystem отслеживает производительность в зависимости от ролей сотрудников. Существуют разные роли сотрудников:

  • Менеджеры: Они ходят и кричат ​​на людей, говорящих им, что делать.Они наемные работники и зарабатывают больше денег.
  • Секретари: Они делают всю бумажную работу для менеджеров и следят за тем, чтобы все счета были выставлены и оплачены вовремя. Они тоже наемные работники, но зарабатывают меньше денег.
  • Сотрудников отдела продаж: Они много звонят по телефону, чтобы продать товары. У них есть зарплата, но они также получают комиссионные с продаж.
  • Заводских рабочих: Производят продукцию для предприятия. Оплата им почасовая.

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

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

  # В employee.py

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        супер().__init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        супер().__init __ (id, name, weekly_salary)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

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

  # В program.py

импортные часы
импортные сотрудники

salary_employee = сотрудники.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = сотрудники.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = employee.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

Вы запускаете программу и проверяете, что она по-прежнему работает:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

Когда все готово, вы начинаете добавлять новые классы:

  # У сотрудников.ру

Менеджер класса (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} кричит и кричит {hours} часов.')

классный секретарь (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит {hours} часов на оформление офисных документов.')

class SalesPerson (CommissionEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит на телефон {hours} часов.')

класс FactoryWorker (HourlyEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} производит гаджеты в течение {часов} часов.')
  

Сначала вы добавляете класс Manager , производный от SalaryEmployee . Класс предоставляет метод work () , который будет использоваться системой повышения производительности. Метод требует часов работы сотрудника.

Затем вы добавляете Secret , SalesPerson и FactoryWorker , а затем реализуете интерфейс work () , чтобы их можно было использовать в системе повышения производительности.

Теперь вы можете добавить ProductivitySytem class:

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            employee.work (часы)
        Распечатать('')
  

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

  # В program.py

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

менеджер = сотрудники.Менеджер (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = employee.FactoryWorker (2, 'Джейн Доу', 40, 15)
сотрудники = [
    управляющий делами,
    секретарь,
    sales_guy,
    рабочий,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (сотрудников, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (сотрудники)
  

Программа формирует список сотрудников разного типа.Список сотрудников отправляется в систему продуктивности для отслеживания их работы в течение 40 часов. Затем тот же список сотрудников отправляется в систему расчета заработной платы для расчета их заработной платы.

Вы можете запустить программу, чтобы увидеть результат:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600
  

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

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

На следующей диаграмме показана новая иерархия классов:

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

Наследование нескольких классов

Python - один из немногих современных языков программирования, поддерживающих множественное наследование.Множественное наследование - это возможность одновременно наследовать класс из нескольких базовых классов.

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

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

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

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

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

  1. Унаследовать от Секретарь : Вы можете унаследовать от Секретарь , чтобы унаследовать .work () для роли, а затем переопределите метод .calculate_payroll () , чтобы реализовать его как HourlyEmployee .

  2. Производный от HourlyEmployee : Вы можете наследовать от HourlyEmployee , чтобы унаследовать метод .calculate_payroll () , а затем переопределить метод .work () , чтобы реализовать его как секретарь .

Затем вы помните, что Python поддерживает множественное наследование, поэтому вы решаете унаследовать от секретарь и HourlyEmployee :

  # У сотрудников.ру

class TemporarySecretary (Секретарь, Почасовой сотрудник):
    проходить
  

Python позволяет наследовать от двух разных классов, указав их в скобках в объявлении класса.

Теперь вы измените свою программу, добавив нового временного секретаря:

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

manager = employee.Manager (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = сотрудники.FactoryWorker (4, 'Джейн Доу', 40, 15)
временный_секретари = сотрудники.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
company_employees = [
    управляющий делами,
    секретарь,
    sales_guy,
    рабочий,
    временный_секретарь,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (company_employees, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (company_employees)
  

Вы запускаете программу для проверки:

  $ программа на питоне.ру

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
TypeError: __init __ () принимает 4 позиционных аргумента, но было дано 5
  

Вы получаете исключение TypeError , в котором говорится, что 4 позиционных аргументов там, где ожидалось, но было дано 5 .

Это связано с тем, что вы получили TemporarySecretary сначала из Секретарь , а затем из HourlyEmployee , поэтому переводчик пытается использовать Секретарь.__init __ () для инициализации объекта.

Ладно, перевернем:

  класс Временный секретарь (почасовой сотрудник, секретарь):
    проходить
  

Теперь запустите программу еще раз и посмотрите, что произойдет:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

Теперь кажется, что вам не хватает параметра weekly_salary , который необходим для инициализации Секретарь , но этот параметр не имеет смысла в контексте TemporarySecretary , потому что это HourlyEmployee .

Может быть, реализация TemporarySecretary .__ init __ () поможет:

  # В employee.py

class TemporarySecretary (Почасовой сотрудник, секретарь):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
  

Попробуйте:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  временный_секретарий = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "Сотрудник".py ", строка 54, в __init__
  super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

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

Когда осуществляется доступ к методу или атрибуту класса, Python использует класс MRO, чтобы найти его. MRO также используется super () , чтобы определить, какой метод или атрибут вызывать.Вы можете узнать больше о super () в Supercharge Your Classes With Python super ().

Вы можете оценить MRO класса TemporarySecretary с помощью интерактивного интерпретатора:

>>>
  >>> из сотрудников ввозят временного секретаря
>>> Временный секретарь .__ mro__

(<класс 'employee.TemporarySecretary'>,
 <класс 'employee.HourlyEmployee'>,
 <класс 'сотрудники. Секретарь'>,
 <класс 'employee.SalaryEmployee'>,
 <класс сотрудников.Сотрудник '>,
 <класс 'объект'>
)
  

MRO показывает порядок, в котором Python будет искать соответствующий атрибут или метод. В этом примере это происходит, когда мы создаем объект TemporarySecretary :

  1. Вызывается метод TemporarySecretary .__ init __ (self, id, name, hours_worked, hour_rate) .

  2. Вызов super () .__ init __ (id, name, hours_worked, hour_rate) соответствует HourlyEmployee.__init __ (self, id, name, hour_worked, hour_rate) .

  3. HourlyEmployee вызывает super () .__ init __ (id, name) , который MRO будет соответствовать секретарю .__ init __ () , который унаследован от SalaryEmployee .__ init __ (self, id, name, weekly_salary ) .

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

Вы можете обойти MRO, изменив порядок наследования и напрямую позвонив HourlyEmployee.__init __ () следующим образом:

  класс Временный секретарь (секретарь, почасовый сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)
  

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

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
Отслеживание (последний вызов последний):
  Файл ".\ program.py ", строка 20, в 
    payroll_system.calculate_payroll (сотрудники)
  Файл "hr.py", строка 7, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
  Файл employee.py, строка 12, в файле calculate_payroll
    return self.weekly_salary
AttributeError: объект TemporarySecretary не имеет атрибута weekly_salary
  

Проблема в том, что, поскольку вы изменили порядок наследования, MRO находит метод .calculate_payroll () для SalariedEmployee перед методом в HourlyEmployee .Вам нужно переопределить .calculate_payroll () в TemporarySecretary и вызвать из него правильную реализацию:

  класс Временный секретарь (секретарь, почасовый сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)

    def calculate_payroll (самостоятельно):
        return HourlyEmployee.calculate_payroll (self)
  

Метод calculate_payroll () напрямую вызывает HourlyEmployee.Calcul_payroll () , чтобы убедиться, что вы получите правильный результат. Вы можете снова запустить программу, чтобы убедиться, что она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

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

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

На следующей диаграмме показана проблема ромба в иерархии классов:

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

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

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

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

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

  1. Система продуктивности , отслеживающая продуктивность сотрудников.

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

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

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            result = employee.work (часы)
            print (f '{имя сотрудника}: {результат}')
        Распечатать('')

класс ManagerRole:
    def работа (самостоятельно, часы):
        ответь f'scream и кричит в течение {hours} часов ».

класс Секретарь
    def работа (самостоятельно, часы):
        return f'expends {hours} часов на оформление офисных документов.'

класс SalesRole:
    def работа (самостоятельно, часы):
        return f 'тратит {hours} часов на телефон.'

класс FactoryRole:
    def работа (самостоятельно, часы):
        вернуть гаджеты на {hours} часов ».
  

Модуль производительности реализует класс ProductivitySystem , а также связанные роли, которые он поддерживает. Классы реализуют требуемый системе интерфейс work () , но они не являются производными от Employee .

То же самое можно сделать с модулем часов :

  # В час.ру

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')

класс SalaryPolicy:
    def __init __ (self, weekly_salary):
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

класс HourlyPolicy:
    def __init __ (self, hours_worked, hour_rate):
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

класс CommissionPolicy (SalaryPolicy):
    def __init __ (self, weekly_salary, Commission):
        super () .__ init __ (недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Модуль hr реализует систему PayrollSystem , которая рассчитывает заработную плату для сотрудников.Он также реализует классы политики для расчета заработной платы. Как видите, классы политик больше не являются производными от Employee .

Теперь вы можете добавить необходимые классы в модуль сотрудника :

  # В employee.py

из часов импорта (
    SalaryPolicy,
    CommissionPolicy,
    Почасовая политика
)
из импорта производительности (
    ManagerRole,
    Секретарь роль,
    SalesRole,
    FactoryRole
)

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

Менеджер класса (Сотрудник, ManagerRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

класс Секретарь (Employee, SecretRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

class SalesPerson (Сотрудник, SalesRole, CommissionPolicy):
    def __init __ (self, id, name, weekly_salary, Commission):
        CommissionPolicy.__init __ (самостоятельно, недельная_ зарплата, комиссия)
        super () .__ init __ (идентификатор, имя)

класс FactoryWorker (Сотрудник, FactoryRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)

класс TemporarySecretary (Сотрудник, SecretRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)
  

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

Обратите внимание, что вам по-прежнему необходимо явно инициализировать политики заработной платы в конструкторах. Вы, наверное, видели, что инициализации Manager и Secret идентичны. Кроме того, одинаковы инициализации FactoryWorker и TemporarySecretary .

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

Вот диаграмма UML для нового дизайна:

На схеме показаны отношения для определения Секретарь и TemporarySecretary с использованием множественного наследования, но избегая проблемы ромба.

Вы можете запустить программу и посмотреть, как она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс: кричит и кричит 40 часов.Джон Смит: тратит 40 часов на оформление офисных документов.
Кевин Бэкон: 40 часов разговаривает по телефону.
Джейн Доу: производит гаджеты 40 часов.
Робин Уильямс: тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

Вы видели, как наследование и множественное наследование работают в Python.Теперь вы можете изучить тему композиции.

Inheritance v. Composition - документация Python 401 2.1

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

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

В объектно-ориентированном программировании (ООП) наследование - это способ повторного использования кода существующих объектов. Это хорошо, когда вы хотите создать подтип из существующего объекта.Объекты определяются классами, классы могут наследовать атрибуты и поведение от уже существующих классов. Результирующие классы известны как производные классы или подклассы.

Подкласс «наследует» все атрибуты (методы и т. Д.) Родительского класса. Мы можем создать новые атрибуты или методы, чтобы добавить к поведению родителя. Мы можем изменить («переопределить») некоторые или все атрибуты или методы, чтобы изменить поведение. Мы также можем расширить поведение родителя, используя оригинальные методы и добавив немного больше.

Мы указываем, что новый класс должен наследовать от существующего класса, помещая имя существующего класса в список базовых классов. Класс (а), названный в списке базовых классов, должен находиться в текущем пространстве имен при оценке оператора класса. Для совместимости с Python 2 и 3 любые новые классы, которые мы создаем, всегда будут наследовать от как минимум объект . Этот базовый класс находится в верхней части модели данных Python и находится в пространстве имен __builtin__ .

Это модель псевдокода для простейшего подкласса в Python:

 class Subclass (Суперкласс):
    проходить
 

Подкласс теперь ведет себя точно так же, как Суперкласс

Примечание

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

Переопределяющие атрибуты

Одна из основных целей подкласса - изменить поведение родительского класса некоторым полезным способом.Мы называем это переопределением унаследованным поведением. Переопределить атрибуты родительского класса в Python так же просто, как создать новый атрибут с тем же именем:

 класс Круг (объект):
    color = "красный"

класс NewCircle (Круг):
    цвет = "синий"

nc = NewCircle
печать (nc.color)
синий
 

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

Методы переопределения

Переопределение методов работает точно так же (помните, что метод - это атрибут в python).

 класс Круг (объект):
...
    def grow (self, factor = 2):
        "" "увеличивает диаметр круга в" "" раз
        self.diameter = self.diameter * коэффициент
...

класс NewCircle (Круг):
...
    def grow (self, factor = 2):
        "" "увеличивает площадь в раз ..." ""
        self.diameter = self.diameter * math.sqrt (2)
 

Экземпляры нового класса circle будут иметь новое поведение для метода grow . Экземпляры существующего класса сохранят прежнее поведение.

При переопределении поведения для подкласса помните, что при хорошем объектно-ориентированном программировании подкласс должен быть в основном похож на своих родителей. Если у вас есть система, которая использует родительский класс, вы должны иметь возможность использовать подкласс во всех тех же местах и ​​всеми одинаковыми способами. Это известно как «принцип замены Лискова». Авторы Think Python сформулировали это так:

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

Если вы соблюдаете это правило, вы обнаружите, что любая функция, предназначенная для работы
с экземпляром суперкласса, например Deck, также будет работать с
экземпляры подклассов, таких как Hand или PokerHand. Если вы нарушите это
Правило, ваш код рухнет, как (извините) карточный домик.

- [ThinkPython 18.10]
 

Методы расширения

Очень распространено желание или необходимость переопределить __init__ .В конце концов, мы пытаемся изменить работу родительского класса. Однако мы часто также хотим делать некоторые или все вещи, которые родительский класс делает с __init__ . Мы действительно хотим, чтобы расширил функциональность родительского класса __init__ . Подумайте: «делайте все, что делают мои родители, плюс еще кое-что».

 класс Круг (объект):
    color = "красный"
    def __init __ (self, диаметр):
        self.diameter = диаметр
...
класс CircleR (Круг):
    def __init __ (self, radius):
        диаметр = радиус * 2
        Круг.__init __ (собственный, диаметр)
 

То же самое можно сделать с любыми методами родительского класса. В методе __init__ нет ничего особенного (кроме того, что он вызывается автоматически).

 класс Круг (объект):
...
    def get_area (self, диаметр):
        return math.pi * (диаметр / 2,0) ** 2


класс CircleR2 (Круг):
...
    def get_area (сам):
        return Circle.get_area (self, self.radius * 2)
 

Порядок разрешения атрибутов

Мы обсудили, как Python ищет атрибуты экземпляра класса.Он начинается в пространстве имен экземпляра, а затем просматривает пространство имен класса. Что происходит, когда ваш класс является подклассом? Если имя не найдено в пространстве имен нашего экземпляра или в классе, поиск продолжается в родительском классе и так далее.

  • Это атрибут экземпляра?
  • Это атрибут класса?
  • Это атрибут суперкласса?
  • Это атрибут суперкласса?
  • ...

Процесс поиска атрибутов класса в иерархии наследования кажется относительно простым.Но Python также поддерживает множественное наследование (два или более имен в списке базовых классов). Что тогда происходит?

В Python 2.3 для прояснения этого вопроса был добавлен новый алгоритм. Самую ясную документацию по нему можно найти в примечаниях к выпуску 2.3. и в сообщении в блоге History of Python.

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

 В [37]: класс A (объект): x = 'A'
   ....:
В [38]: класс B (объект): x = 'B'
   ....:
В [39]: class C (A): pass
   ....:
В [40]: класс D (C, B): пройден
   ....:
В [41]: D.mro ()
Out [41]: [__main __. D, __main __. C, __main __. A, __main __. B, объект]
В [42]: D.x
Из [42]: 'A'
В [43]: class E (B, C): pass
   ....:
В [44]: E.mro ()
Out [44]: [__main __. E, __main __. B, __main __. C, __main __. A, object]
В [45]: E.x
Из [45]: 'B'
 

Аббревиатура MRO означает Порядок разрешения метода . Ясно, однако, что это относится к всем атрибутам класса , а не только к методам.

Последнее замечание, касающееся использования объекта в списке базовых классов для класса. В Python 2 таким образом мы отличаем класса нового стиля от классов старого стиля. У классов старого стиля был другой способ работы с разрешением атрибутов. Он плохо работал при использовании с множественным наследованием. Классы нового стиля справились с этим лучше, особенно после Python 2.3. Но уроки старого стиля все еще существовали.

В Python 3 нет таких вещей, как классы старого стиля. Все классы наследуются от объекта независимо от того, указаны они или нет. Мы предоставляем базовый класс объекта для обеспечения совместимости между Python 2 и Python 3.

Когда переходить к подклассу

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

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

Состав

Давайте представим, что у нас есть класс, который должен накапливать произвольное количество объектов. Список может это сделать, поэтому мы должны создать подклассы, верно?

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

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

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

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

 В [46]: класс Accumulator (объект):
   ....: def __init __ (сам):
   ....:         себя._container = []
   ....: def накопить (self, obj):
   ....: self._container.append (объект)
   ....: def stuff (self):
   ....: return self._container [:]
   ....:
 

Теперь мы можем создать экземпляр нашего класса Accumulator и начать накапливать данные:

 В [47]: junk_drawer = Аккумулятор ()
В [48]: junk_drawer.accumulate ('шпатель')
В [49]: junk_drawer.accumulate («пробковый винт»)
В [50]: junk_drawer.accumulate ('старая резинка')
 

И время от времени мы даже можем спросить, что находится в ящике для мусора (хотя, как и в любом хорошем ящике для мусора, на самом деле вы ничего не можете достать):

 В [51]: junk_drawer.вещи()
Out [51]: ['шпатель', 'пробковый винт', 'старая резинка']

В [52]: junk_drawer.stuff (). Pop ()
Out [52]: 'старая резинка'

В [53]: junk_drawer.stuff ()
Out [53]: [«шпатель», «пробковый винт», «старая резинка»]
 

Типовая отправка

Последнее слово к уроку о классах. Иногда мы можем увидеть такой код:

.
, если isinstance (другой, SomeClass):
    Do_something_with_other
еще:
    Do_something_else
 

Как правило, лучше использовать «утиную типизацию» (полиморфизм).В конце концов, если другой имеет правильные методы или атрибуты, то зачем нам все равно, является ли экземпляром экземпляра SomeClass ? Но когда это необходимо, вы можете использовать isinstance или его двоюродный брат issubclass :

.
 В [54]: isinstance (junk_drawer, Аккумулятор)
Out [54]: Верно

В [55]: isinstance (junk_drawer, объект)
Out [55]: Верно

В [56]: issubclass (Аккумулятор, объект)
Out [56]: Верно

В [57]: issubclass (объект, аккумулятор)
Out [57]: ложь
 

Заключение

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

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

Учебник по Python

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

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

Введение и определения

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

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

Класс, от которого наследуется класс, называется родительским или суперклассом. Класс, который наследуется от суперкласса, называется подклассом, также называемым наследником или дочерним классом. Суперклассы также иногда называют предками. Между классами существует иерархическая связь.Это похоже на отношения или категоризации, которые мы знаем из реальной жизни. Подумайте, например, об автомобилях. Велосипеды, автомобили, автобусы и грузовики - это транспортные средства. Пикапы, фургоны, спортивные автомобили, кабриолеты и универсалы - все это автомобили, и, будучи автомобилями, они также являются транспортными средствами. Мы могли бы реализовать класс транспортного средства на Python, который мог бы иметь такие методы, как ускорение и торможение. Автомобили, автобусы, грузовики и велосипеды могут быть реализованы как подклассы, которые унаследуют эти методы от транспортного средства.

Синтаксис наследования в Python

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

  класс DerivedClassName (BaseClassName):
    перевал  

6 Классы и объекты

6 Классы и объекты

Классы и объекты в Racket Guide представлены классы и объекты.

Класс определяет

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

Система классов позволяет программе определять новый класс ( производный класс) в терминах существующего класса ( суперкласс) с использованием наследования, переопределения и дополнения:

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

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

  • расширение: некоторые методы, объявленные в суперклассе, могут просто расширяется в производном классе. Метод суперкласса специально делегирует дополняющий метод в производном классе.

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

  • объявляет (или наследует) общедоступный метод для каждой переменной в интерфейс;

  • является производным от класса, требуемого интерфейсом, если таковой имеется; и

  • конкретно заявляет о своем намерении реализовать интерфейс.

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

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

Классы, объекты и интерфейсы - это все значения. Однако класс или интерфейс не является объектом (т. е. не существует «метаклассов» или «Мета-интерфейсы»).

Наследование классов - информатика

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

Ваше Уведомление о нарушении прав может быть отправлено стороне, предоставившей доступ к контенту, или третьим лицам, таким как в виде ChillingEffects.org.

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

Чтобы отправить уведомление, выполните следующие действия:

Вы должны включить следующее:

Физическая или электронная подпись правообладателя или лица, уполномоченного действовать от их имени; Идентификация авторских прав, которые, как утверждается, были нарушены; Описание характера и точного местонахождения контента, который, по вашему мнению, нарушает ваши авторские права, в \ достаточно подробностей, чтобы позволить репетиторам университетских школ найти и точно идентифицировать этот контент; например нам требуется а ссылка на конкретный вопрос (а не только на название вопроса), который содержит содержание и описание к какой конкретной части вопроса - изображению, ссылке, тексту и т. д. - относится ваша жалоба; Ваше имя, адрес, номер телефона и адрес электронной почты; и Ваше заявление: (а) вы добросовестно полагаете, что использование контента, который, по вашему мнению, нарушает ваши авторские права не разрешены законом, владельцем авторских прав или его агентом; (б) что все информация, содержащаяся в вашем Уведомлении о нарушении, является точной, и (c) под страхом наказания за лжесвидетельство, что вы либо владелец авторских прав, либо лицо, уполномоченное действовать от их имени.

Отправьте жалобу нашему уполномоченному агенту по адресу:

Чарльз Кон Varsity Tutors LLC
101 S. Hanley Rd, Suite 300
St. Louis, MO 63105

Или заполните форму ниже:

.
Оставить комментарий

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

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