Има ли начин да се декларират публични и частни методи за референтни класове на S4?

Предварително: съм наясно, че R е функционален език, така че, моля, не хапете ;-)

Имам страхотен опит с използването на ООП подход за много от моите програми. Сега се чудя дали има начин да се направи разлика между публични и частни методи, когато се използва Референтни класове на 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"
    )
)

ЗАБЕЛЕЖКА

Обикновено не поставям действителната дефиниция на метода в класа def, а я отделям към 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) трябва да имат право да използват публичните методи на клас 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 и ръководството за Писане на R разширения, свързано по-горе.

Уверете се, че референтните класове наистина са това, което искате. Редовните S4 класове обикновено са по-R-ish начин, каквото и да си струва. Чудесен източник на информация за многото OO конструкции на R (и също за опаковането) е в уикито за инструменти за разработчици на Hadley Wickham.

person cbare    schedule 30.08.2012
comment
Напълно съм съгласен с това, пакетите са правилният начин. Обърнете внимание обаче, че неекспортирането на нещо от пакет не означава, че потребителят няма достъп до него. Все още имате достъп до частни функции и данни с оператора „:::“. - person Gabor Csardi; 31.08.2012