Есть ли способ объявить общедоступные и частные методы для эталонных классов S4?

Предварительно: я знаю, что R — это функциональный язык, поэтому, пожалуйста, не кусайтесь ;-)

У меня был отличный опыт использования ООП-подхода для многих моих программ. Теперь мне интересно, есть ли способ провести различие между public и private методами при использовании Справочные классы S4 в R?

Пример

Определения классов

setRefClass("B",
    field=list(
        b.1="numeric",
        b.2="logical"
    ),
    methods=list(
        thisIsPublic=function(...) {
            thisIsPublic_ref(.self=.self, ...)
        },
        thisIsPrivate=function(...) {
            thisIsPrivate_ref(.self=.self, ...)
        }
    )
)

setRefClass("A",
    field=list(
        a.1="B"
    )
)

ПРИМЕЧАНИЕ

Обычно я не помещаю определение метода фактического в определение класса, а выделяю его в метод S4 (т. е. thisIsPublic_ref) по следующим причинам:

  1. Таким образом, определение класса остается четко организованным и его легче читать в случаях, когда определения отдельных методов становятся довольно большими.
  2. Он позволяет в любой момент переключиться на функциональное выполнение методов. Будьте x экземпляром определенного класса, вы можете вызывать foo_ref(.self=x) вместо x$foo().
  3. Это позволяет вам байт-компилировать методы через compiler::cmpfun(), что, я думаю, невозможно, если у вас есть «простые» методы ссылочного класса.

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

Определения методов

setGeneric(
    name="thisIsPublic_ref",
    signature=c(".self"),
    def=function(
        .self,
        ...
    ) {
    standardGeneric("thisIsPublic_ref")    
    }
)
setGeneric(
    name="thisIsPrivate_ref",
    signature=c(".self"),
    def=function(
        .self,
        ...
    ) {
    standardGeneric("thisIsPrivate_ref")    
    }
)

require(compiler)

setMethod(
    f="thisIsPublic_ref",
    signature=signature(.self="B"),
    definition=cmpfun(function(  
        .self,
        ...
    ){
    .self$b.1 * 1000
    })
)
setMethod(
    f="thisIsPrivate_ref",
    signature=signature(.self="B"),
    definition=cmpfun(function(  
        .self,
        ...
    ){
    .self$b.2
    })
)

Экземпляры

x.b <- new("B", b.1=10, b.2=TRUE)
x.a <- new("A", a.1=x.b, a.2="hello world")

Публичные и частные

Экземплярам класса A (то есть x.a) должно быть разрешено использовать public методы класса B:

> x.a$a.1$thisIsPublic()
[1] 10000

Экземплярам класса A (т. е. x.a) не разрешается использовать частные методы класса B. Поэтому я бы хотел, чтобы это не работало, т.е. приводило к ошибке:

> x.a$a.1$thisIsPrivate()
[1] TRUE

Любая идея, как можно указать это?

Единственное, что я пока придумал:

Добавляя аргумент sender к каждому методу, явно указывайте его для каждого вызова метода и проверяйте, не является ли class(.self) == class(sender). Но это кажется немного «явным».


person Rappster    schedule 17.06.2012    source источник
comment
x.a является экземпляром класса A, но x.a$a.1 является экземпляром класса B. Вы хотите запретить экземпляру класса B доступ к закрытым методам класса B? Вы, вероятно, собираетесь войти в целый мир боли, пытаясь помешать классу получить доступ к своим методам в зависимости от того, в какой структуре данных он может жить...   -  person Spacedman    schedule 26.08.2012
comment
Совершенно верно, и это не то, к чему я стремлюсь. Опять же, это тема, в которой я чувствую, что мне просто не хватает базовых знаний об ООП. Размещение экземпляров определенных классов в полях других классов (например, x.a$a.1 в качестве экземпляра класса B в x.a класса A) было просто моим способом реализации некоторой степени инкапсуляции. Но вы совершенно правы в том, что таким образом невозможно различить общедоступные и частные методы, поскольку в конце концов метод вызывается a.1, а не x.a. Я подумаю о хорошем обновлении моего примера, чтобы прояснить ситуацию.   -  person Rappster    schedule 28.08.2012


Ответы (2)


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

hello <- function() {
    print_ <- function() { 
         return ('hello world')
    }
    print_()
}

Да, это дерзко, возможно, не самый чистый способ, но он работает... Вызывайте с помощью 'hello()'.

person hd1    schedule 27.09.2012
comment
Я вовсе не думаю, что это дерзко. Это довольно стандартная практика в библиотеках javascript, и я не удивлюсь, увидев ее в других функционально-ориентированных языках сценариев. - person 16807; 14.06.2016

Короткий ответ - сделать пакет. системы объектов R и средства разделения кода (пространства имен) более обособлены, чем их эквиваленты в Java-подобных языках.

Когда вы создаете пакет, вы указываете, что экспортируется в файл с именем NAMESPACE, используя директивы export и exportMethods. Вы можете не экспортировать методы и другие объекты R, которые вы хотите сделать закрытыми для пакета (используя терминологию Java). См. пространства имен с S4. раздел классов и методов руководства по написанию расширений R

Создание пакета сложно в первый раз, но есть много помощи. См. документы для package.skeleton и руководство по Writing R Extensions, указанное выше.

Убедитесь, что эталонные классы — это действительно то, что вам нужно. Обычные классы S4 обычно более ориентированы на R, чего бы это ни стоило. Отличным источником информации о многих объектно-ориентированных конструкциях R (а также об упаковке) является вики по инструментам разработки Хэдли Уикхэм. .

person cbare    schedule 30.08.2012
comment
Я полностью согласен с этим, пакеты - это путь. Обратите внимание, однако, что отсутствие экспорта чего-либо из пакета не означает, что пользователь не может получить к нему доступ. Вы по-прежнему можете получить доступ к закрытым функциям и данным с помощью оператора «:::». - person Gabor Csardi; 31.08.2012