Что касается R, может ли кто-нибудь объяснить мне, что касается наследования объектов, если у меня есть объект S4 X, который содержит Y, если Y имеет инициализатор, как вызывать этот инициализатор из инициализатора X, когда X построено.
Наследование в R
Ответ 1
Первый проход, недостаточно неплохой
Вот два класса
.A <- setClass("A", representation(a="integer"))
.B <- setClass("B", contains="A", representation(b="integer"))
Символ .A
- это функция генератора класса (по существу вызов new()
) и является относительно новым дополнением к пакету методов.
Здесь мы пишем инициализирующий A-метод, используя callNextMethod
, чтобы вызвать следующий метод (конструктор по умолчанию, initialize, ANY-method) для класса
setMethod("initialize", "A", function(.Object, ..., a=integer()) {
## do work of initialization
cat("A\n")
callNextMethod(.Object, ..., a=a)
})
Аргумент, соответствующий слоту a=a
, приходит после ...
, чтобы функция не назначала никакие неназванные аргументы a
; это важно, поскольку предполагается, что для инициализации базовых классов, а не слотов, допускаются неименованные аргументы (от ?initialize
); важность этого становится очевидной ниже. Аналогично для "B":
setMethod("initialize", "B", function(.Object, ..., b=integer()) {
cat("B\n")
callNextMethod(.Object, ..., b=b)
})
и в действии
> b <- .B(a=1:5, b=5:1)
B
A
> b
An object of class "B"
Slot "b":
[1] 5 4 3 2 1
Slot "a":
[1] 1 2 3 4 5
Собственно, это не совсем правильно, потому что по умолчанию initialize
является конструктором копирования
.C <- setClass("C", representation(c1="numeric", c2="numeric"))
c <- .C(c1=1:5, c2=5:1)
> initialize(c, c1=5:1)
An object of class "C"
Slot "c1":
[1] 5 4 3 2 1
Slot "c2":
[1] 5 4 3 2 1
и наш метод инициализации нарушил этот аспект контракта
> initialize(b, a=1:5) # BAD: no copy construction
B
A
An object of class "B"
Slot "b":
integer(0)
Slot "a":
[1] 1 2 3 4 5
Скопированная конструкция оказывается весьма удобной, поэтому мы не хотим ее сломать.
Сохранение конструкции копии
Для сохранения функций построения копии используются два решения. Первое исключает определение метода инициализации, но вместо этого создает обычную старую функцию в качестве конструктора
.A1 <- setClass("A1", representation(a="integer"))
.B1 <- setClass("B1", contains="A1", representation(b="integer"))
A1 <- function(a = integer(), ...) {
.A1(a=a, ...)
}
B1 <- function(a=integer(), b=integer(), ...) {
.B1(A1(a), b=b, ...)
}
Эти функции включают ...
в качестве аргументов, так что класс "B1" может быть расширен и его конструктор все еще используется. Это на самом деле довольно привлекательно; конструктор может иметь разумную подпись с документированными аргументами. initialize
может использоваться как конструктор копирования (помните, что инициализация, A1-метод или инициализация, B1-метод, поэтому вызов .A1()
вызывает метод инициализации по умолчанию, метод-конструктор). Функция (.B1(A1(a), b=b, ...)
говорит: "Вызовите генератор для класса B1, с неназванным аргументом, создающим его суперкласс, используя конструктор" A1 ", и именованный аргумент, соответствующий слоту b". Как упоминалось выше, из ?initialize
аргументы используются для инициализации суперкласса (es) (с множественными классами, когда структура класса включает в себя множественное наследование). Использование конструкторов означает, что классы A1 и B1 могут быть не осведомлены о структуре и реализации друг друга.
Второе решение, реже используемое в его полной славе, состоит в том, чтобы написать метод инициализации, который сохраняет структуру копирования по линиям
.A2 <- setClass("A2", representation(a1="integer", a2="integer"),
prototype=prototype(a1=1:5, a2=5:1))
setMethod("initialize", "A2",
function(.Object, ..., [email protected], [email protected])
{
callNextMethod(.Object, ..., a1=a1, a2=a2)
})
Аргумент [email protected]
использует текущее значение слота a1
.Object
как значение по умолчанию, соответствующее, когда метод используется в качестве конструктора копирования. Пример иллюстрирует использование prototype
для обеспечения начальных значений, отличных от векторов длины 0. В действии:
> a <- .A2(a2=1:3)
> a
An object of class "A1"
Slot "a1":
[1] 1 2 3 4 5
Slot "a2":
[1] 1 2 3
> initialize(a, a1=-(1:3)) # GOOD: copy constructor
An object of class "A1"
Slot "a1":
[1] -1 -2 -3
Slot "a2":
[1] 1 2 3
К сожалению этот подход не удается при попытке инициализировать производный класс из базового класса.
Другие соображения
Конечной точкой является структура самого метода инициализации. Иллюстрированный выше шаблон
## do class initialization steps, then...
callNextMethod(<...>)
поэтому callNextMethod()
находится в конце метода инициализации. Альтернативой является
.Object <- callNextMethod(<...>)
## do class initialization steps by modifying .Object, e.g.,...
[email protected] <- <...>
.Object
Причина, по которой предпочтение отдается первому подходу, заключается в том, что задействовано меньшее количество копий; инициализация по умолчанию, ANY-метод заполняет слоты с минимальным копированием, тогда как подход обновления слота копирует весь объект каждый раз, когда слот изменен; это может быть очень плохо, если объект содержит большие векторы.