Introdução a orientação a objetos no R (OO-S3)

Links úteis:

Introdução

Observe as diferenças nos resultados das funções plot e summary abaixo:

a) plot:

plot(rivers)

plot of chunk plot

plot(iris[, -5],
     col=c('red', 'green', 'blue')[unclass(iris$Species)],
     pch=19)

plot of chunk plot

plot(Titanic)

plot of chunk plot

b) summary:

summary(rivers)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   135.0   310.0   425.0   591.2   680.0  3710.0
summary(iris)
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100   setosa    :50  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300   versicolor:50  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300   virginica :50  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199                  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800                  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500
summary(Titanic)
## Number of cases in table: 2201 
## Number of factors: 4 
## Test for independence of all factors:
##  Chisq = 1637.4, df = 25, p-value = 0
##  Chi-squared approximation may be incorrect

Os resultados são bem diferentes, não?

Se não consegue entender o motivo dessas diferenças, estude o que está abaixo. Isso deverá ajudar a entender alguns dos detalhes “técnicos” mais importantes do R (e de profunda influência em seu uso).

O uso eficiente (acima do básico) do R pressupõe conhecimentos (básicos) de Orientação a Objetos (OO)!

OO é um paradigma da Ciência da Computação (não uma exclusividade do R) para a resolução de problemas computacionais. Na realidade, quase todas as linguagens de programação modernas (o R é uma delas) adotaram OO. Ex: C++, C#, Object Pascal, Python, Ruby, etc.

OO é fundamentado na análise, projeto e programação de sistemas de software baseado na composição e interação entre as diversas unidades de software chamadas de objetos.

Na OO, um problema real complexo qualquer é abstraido (ou seja, simplificado) em suas partes mais elementares e importantes na resolução de um problema. Abstrair, significa uma simplificação da realidade complexa em seus elementos mínimos relevantes.

Surgem então os conceitos de CLASSE, INSTÂNCIA DA CLASSE e OBJETO (além de HERANÇA, ENCAPSULAMENTO e POLIMORFISMO DE MÉTODO).

Na realidade o R é um grande conjunto de objetos. Tudo no R é objeto! Adicionalmente, esses objetos estão organizados numa estrutura hierárquica de classes.

Mas o que são classes? As classes, são os projetos, as definições formais dessas partes simples (objetos) que irão fazer parte da resolução de um problema computacional!

Cada classe define, para cada objeto, minimamente:

Na OO, resolver um problema computacional qualquer significa essencialmente fazer uma modelagem de classes.

Uma classe não existe, ela não é nada mais que uma definição formal de algo que virá a existir. A classe passa a existir quando for instanciada, em outras palavras, quando uma instância da classe for efetivamente criada (o objeto real da abstração).

Uma vez criadas essas instâncias (objetos) elas irão atuar na resolução do problema para o qual foram pensadas (isso é feito na modelagem de classes).

class(rivers)
## [1] "numeric"
class(iris)
## [1] "data.frame"
class(Titanic)
## [1] "table"

A resposta simples para as diferenças encontradas nas funções genéricas plot e summary no início desse tópico é: OS OBJETOS PERTENCEM A CLASSES DISTINTAS. Portanto, possuem PROPRIEDADES E MÉTODOS DISTINTOS.

Vejamos um problema real que consiste em armazenar dados (de diferentes naturezas) de n indivíduos observados em p variáveis em escalas distintas.

A estrutura de dados (objeto) adequada para isso é um data.frame:

n <- 10
p <- 5

set.seed(13)
dad <- data.frame(Y1=floor(runif(n, 1, 10)),         # Renda (s. mínimo)
                  Y2=rnorm(n, 1.7, .2),              # Altura
                  Y3=rnorm(n, 60, .3),               # Peso
                  Y4=rnorm(n, 30, .4),               # Idade
                  Y5=sample(c('M', 'F'), n, rep=T))  # Sexo

rownames(dad) <- paste('id',
                       1:nrow(dad),
                       sep='-')
dad
##       Y1       Y2       Y3       Y4 Y5
## id-1   7 1.783105 59.94182 30.05974  M
## id-2   3 1.945901 60.41893 29.41627  M
## id-3   4 1.747336 60.03020 29.18918  F
## id-4   1 1.626923 59.96567 29.57722  M
## id-5   9 1.921029 60.21067 29.70874  F
## id-6   1 1.481281 60.07876 29.99672  M
## id-7   6 1.792374 60.55085 30.33912  M
## id-8   7 1.427803 60.10722 29.84660  M
## id-9   8 1.328795 59.68638 29.78940  F
## id-10  1 1.612029 60.18606 29.89071  F

Vamos verificar as propriedades (atributos) do objeto dad:

attributes(dad)
## $names
## [1] "Y1" "Y2" "Y3" "Y4" "Y5"
## 
## $class
## [1] "data.frame"
## 
## $row.names
##  [1] "id-1"  "id-2"  "id-3"  "id-4"  "id-5"  "id-6"  "id-7"  "id-8"  "id-9"  "id-10"

E os métodos associados à instância da classe, ou seja, do objeto dad: onde estão?

No caso do R, diferentemente da maioria das outras linguagens, os objetos de armazenamento de dados (vetores, matrizes, arrays, data.frames, listas, tabelas, etc) não possuem métodos.

O que existem são funções genéricas (print, summary, plot, mean, etc) que provêem métodos para os objetos que armazenam dados:

methods(summary)
##  [1] summary.Abacaxi                summary.aov                    summary.aovlist*               summary.aspell*               
##  [5] summary.check_packages_in_dir* summary.connection             summary.data.frame             summary.Date                  
##  [9] summary.default                summary.ecdf*                  summary.factor                 summary.Frutas                
## [13] summary.glm                    summary.infl*                  summary.jcfaria                summary.lm                    
## [17] summary.loess*                 summary.manova                 summary.matrix                 summary.mlm*                  
## [21] summary.nls*                   summary.packageStatus*         summary.POSIXct                summary.POSIXlt               
## [25] summary.ppr*                   summary.prcomp*                summary.princomp*              summary.proc_time             
## [29] summary.sockclientconn*        summary.srcfile                summary.srcref                 summary.stepfun               
## [33] summary.stl*                   summary.table                  summary.teste                  summary.tukeysmooth*          
## [37] summary.warnings              
## see '?methods' for accessing help and source code
methods(plot)
##  [1] plot.Abacaxi        plot.acf*           plot.data.frame*    plot.decomposed.ts* plot.default        plot.dendrogram*   
##  [7] plot.density*       plot.ecdf           plot.factor*        plot.formula*       plot.Frutas         plot.function      
## [13] plot.hclust*        plot.histogram*     plot.HoltWinters*   plot.isoreg*        plot.jcfaria        plot.lm*           
## [19] plot.medpolish*     plot.mlm*           plot.ppr*           plot.prcomp*        plot.princomp*      plot.profile.nls*  
## [25] plot.raster*        plot.spec*          plot.stepfun        plot.stl*           plot.table*         plot.ts            
## [31] plot.tskernel*      plot.TukeyHSD*     
## see '?methods' for accessing help and source code
methods(mean)
## [1] mean.Date     mean.default  mean.difftime mean.POSIXct  mean.POSIXlt 
## see '?methods' for accessing help and source code
summary(dad)
##        Y1            Y2              Y3              Y4             Y5           
##  Min.   :1.0   Min.   :1.329   Min.   :59.69   Min.   :29.19   Length:10         
##  1st Qu.:1.5   1st Qu.:1.514   1st Qu.:59.98   1st Qu.:29.61   Class :character  
##  Median :5.0   Median :1.687   Median :60.09   Median :29.82   Mode  :character  
##  Mean   :4.7   Mean   :1.667   Mean   :60.12   Mean   :29.78                     
##  3rd Qu.:7.0   3rd Qu.:1.790   3rd Qu.:60.20   3rd Qu.:29.97                     
##  Max.   :9.0   Max.   :1.946   Max.   :60.55   Max.   :30.34
plot(dad[-5],
     col='red',
     pch=19)

plot of chunk plot.dad

Cada um desses métodos (genéricos) necessita saber a que classe o objeto passado como argumento para a função genérica pertence para usar um método apropriado (segundo as propriedades do objeto) na intenção de apresentar as informações (numéricas no caso de summary e gráficas no caso de plot) contidas nos objetos que armazenam dados. Esse mecanismo no R é denominado dispatch (despachar, expedir).

Todos os métodos genéricos possuem necessariamente um método padrão (default).

str(summary.default)
## function (object, ..., digits, quantile.type = 7)
str(plot.default)
## function (x, y = NULL, type = "p", xlim = NULL, ylim = NULL, log = "", main = NULL, sub = NULL, xlab = NULL, ylab = NULL, ann = par("ann"), 
##     axes = TRUE, frame.plot = axes, panel.first = NULL, panel.last = NULL, asp = NA, xgap.axis = NA, ygap.axis = NA, ...)
str(mean.default)
## function (x, trim = 0, na.rm = FALSE, ...)

Todos os métodos do R são genéricos? Não! Existem métodos específicos para determinados tipos de objetos. Os métodos genéricos são apenas para as ações mais corriqueiras, comuns e usuais a vários objetos.

Os métodos default sempre tentam retirar o máximo possível de informação de um objeto, INDEPENDENTE DE SUA CLASSE. NA AUSÊNCIA DE UM MÉTODO ESPECÍFICO PARA DETERMINADO TIPO DE OBJETO (CLASSE) É SEMPRE O MÉTODO SELECIONADO/EXECUTADO.

Exemplificando. Quando digitamos:

summary(dad)

Acontece o seguinte:

class(dad)
## [1] "data.frame"
grep('data.frame',
     methods(summary),
     value=TRUE)
## [1] "summary.data.frame"
summary(dad)
##        Y1            Y2              Y3              Y4             Y5           
##  Min.   :1.0   Min.   :1.329   Min.   :59.69   Min.   :29.19   Length:10         
##  1st Qu.:1.5   1st Qu.:1.514   1st Qu.:59.98   1st Qu.:29.61   Class :character  
##  Median :5.0   Median :1.687   Median :60.09   Median :29.82   Mode  :character  
##  Mean   :4.7   Mean   :1.667   Mean   :60.12   Mean   :29.78                     
##  3rd Qu.:7.0   3rd Qu.:1.790   3rd Qu.:60.20   3rd Qu.:29.97                     
##  Max.   :9.0   Max.   :1.946   Max.   :60.55   Max.   :30.34

A classe de um objeto é uma informação que o próprio objeto armazena, como um atributo:

attributes(dad)
## $names
## [1] "Y1" "Y2" "Y3" "Y4" "Y5"
## 
## $class
## [1] "data.frame"
## 
## $row.names
##  [1] "id-1"  "id-2"  "id-3"  "id-4"  "id-5"  "id-6"  "id-7"  "id-8"  "id-9"  "id-10"

Esse atributo pode ser livremente alterado pelo usuário

old.class <- class(dad)  # Armazenando a classe original
class(dad) <- 'test'     # Atribuindo uma nova classe

attributes(dad)
## $names
## [1] "Y1" "Y2" "Y3" "Y4" "Y5"
## 
## $class
## [1] "test"
## 
## $row.names
##  [1] "id-1"  "id-2"  "id-3"  "id-4"  "id-5"  "id-6"  "id-7"  "id-8"  "id-9"  "id-10"
summary(dad)             # Observar a diferença em relação ao 'summary' anterior
##    Length Class  Mode     
## Y1 10     -none- numeric  
## Y2 10     -none- numeric  
## Y3 10     -none- numeric  
## Y4 10     -none- numeric  
## Y5 10     -none- character
grep('^summary.test',
      methods(summary),
      value=TRUE)        # Observar que não existe o método 'summary.test'.
## [1] "summary.teste"

Qual método foi aplicado?

summary.default

Que embora tente, não retira todas as informações de interesse do objeto dad, pois não é específico para essa finalidade.

Retornando a classe original do objeto dad:

class(dad) <- old.class
class(dad)
## [1] "data.frame"
summary(dad)  # Novamente foi executado o método 'summary.data.frame'
##        Y1            Y2              Y3              Y4             Y5           
##  Min.   :1.0   Min.   :1.329   Min.   :59.69   Min.   :29.19   Length:10         
##  1st Qu.:1.5   1st Qu.:1.514   1st Qu.:59.98   1st Qu.:29.61   Class :character  
##  Median :5.0   Median :1.687   Median :60.09   Median :29.82   Mode  :character  
##  Mean   :4.7   Mean   :1.667   Mean   :60.12   Mean   :29.78                     
##  3rd Qu.:7.0   3rd Qu.:1.790   3rd Qu.:60.20   3rd Qu.:29.97                     
##  Max.   :9.0   Max.   :1.946   Max.   :60.55   Max.   :30.34

Nesse ponto as coisas (e o poder da OO) devem estar começando a ficar um pouco mais claras para você. Adicionalmente, este mecanismo dispatch (despachar, expedir) do R escolher um método específico de acordo com a classe do objeto pode ser considerado POLIMORFISMO DE CLASSE.

Brincando com as classes (segredos da OO)
class(dad) <- c('test',
                'jcfaria',
                'data.frame')
attributes(dad)
## $names
## [1] "Y1" "Y2" "Y3" "Y4" "Y5"
## 
## $class
## [1] "test"       "jcfaria"    "data.frame"
## 
## $row.names
##  [1] "id-1"  "id-2"  "id-3"  "id-4"  "id-5"  "id-6"  "id-7"  "id-8"  "id-9"  "id-10"

OPA…, o objeto pertence a mais de uma classe? Sim, um objeto no R (segundo a implementação OO S3) pode pertencer a mais de uma classe!

Como assim S3? É que S3 foi a primeira implementação de OO no R, depois (o mundo não para mesmo) os desenvolvedores acharam que poderia ser melhor e criaram (estão criando) a S4.

Mas relaxe, S3 e S4 convivem em harmonia! Estima-se que aproximadamente 80-85% do R está em S3 e 15-20% em S4. Detalhes de diferenciação entre S3 e S4 estão fora do escopo dessa introdução.

Tá bom…, um objeto pode pertencer a mais de uma classe! (Isso se relaciona com HERANÇA, como será visto um pouco mais adiante)

Nesses casos, como o R seleciona o método adequado via mecanismo dispatch?

O retorno da função class, como visto, é um vetor de caracteres. O R SEMPRE VERIFICA ESSE VETOR DA ESQUERDA PARA A DIREITA. SEMPRE! Tente guardar (e não esquecer) isso em alguma parte de sua mente. Ou seja, no sentido do mais novo para o mais velho (HERANÇA). A primeira ocorrência encontrada é a que será executada (selecionada pelo mecanismo dispatch).

No nosso caso, summary(dad):

  1. procura pelo método summary.test (como não encontra, passa par o segunda posição)
  2. procura pelo método summary.jcfaria (idem)
  3. procura por summary.data.frame (BINGO!)

Se também não tivesse encontrado summary.data.frame o método summary.default seria eleito/selecionado.

Fazendo uma alusão, isso pode ser entendido assim:

Brincando (um pouco mais) com OO
summary.jcfaria <- function(object,
                            clm=1, ...) summary(object[, clm], ...)

methods(summary)         # Observe a presença de 'summary.jcfaria'.
##  [1] summary.Abacaxi                summary.aov                    summary.aovlist*               summary.aspell*               
##  [5] summary.check_packages_in_dir* summary.connection             summary.data.frame             summary.Date                  
##  [9] summary.default                summary.ecdf*                  summary.factor                 summary.Frutas                
## [13] summary.glm                    summary.infl*                  summary.jcfaria                summary.lm                    
## [17] summary.loess*                 summary.manova                 summary.matrix                 summary.mlm*                  
## [21] summary.nls*                   summary.packageStatus*         summary.POSIXct                summary.POSIXlt               
## [25] summary.ppr*                   summary.prcomp*                summary.princomp*              summary.proc_time             
## [29] summary.sockclientconn*        summary.srcfile                summary.srcref                 summary.stepfun               
## [33] summary.stl*                   summary.table                  summary.teste                  summary.tukeysmooth*          
## [37] summary.warnings              
## see '?methods' for accessing help and source code
grep('summary.jcf',      # (se acha difícil localizar a função 'grep' pode ajudar)
     methods(summary),
     value=TRUE)
## [1] "summary.jcfaria"
summary(dad)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     1.0     1.5     5.0     4.7     7.0     9.0
summary(dad,
        clm=2)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.329   1.514   1.687   1.667   1.790   1.946
summary(dad,
        clm=3,
        digits=2)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##      60      60      60      60      60      61

Observe que na última chamada foi adicionado um argumento digits=2 que não consta na declaração do método summary.jcfaria. Esse é o significado de '…' na função summary.jcfaria. Ou seja, pode-se passar qualquer argumento (no caso digits) para outras funções internas da função (método) summary.jcfaria sem ter que se preocupar em declará-los ou tratá-los.

Outro exemplo
plot.jcfaria <- function(x, ...) boxplot(x, ...)

methods(plot)      # Observe a presença de 'plot.jcfaria'
##  [1] plot.Abacaxi        plot.acf*           plot.data.frame*    plot.decomposed.ts* plot.default        plot.dendrogram*   
##  [7] plot.density*       plot.ecdf           plot.factor*        plot.formula*       plot.Frutas         plot.function      
## [13] plot.hclust*        plot.histogram*     plot.HoltWinters*   plot.isoreg*        plot.jcfaria        plot.lm*           
## [19] plot.medpolish*     plot.mlm*           plot.ppr*           plot.prcomp*        plot.princomp*      plot.profile.nls*  
## [25] plot.raster*        plot.spec*          plot.stepfun        plot.stl*           plot.table*         plot.ts            
## [31] plot.tskernel*      plot.TukeyHSD*     
## see '?methods' for accessing help and source code
grep('plot.jcf',
     methods(plot),
     value=TRUE)
## [1] "plot.jcfaria"
plot(dad[3])

plot of chunk OO2

plot(dad[3],
     ylim=c(59.5, 60.6),
     xlab='Peso',
     col='red',
     main='plot.jcfaria')

plot of chunk OO2

Bom, as coisas relativas a OO no R devem estar um pouco mais claras. Adicionalmente, foi percebida e importância disso no uso, não?

Um exemplo completo de OO em S3: desde a criação de uma função genérica, implementação de métodos e testes

Criando uma nova função genérica: fruta

Vimos que o R tem várias funções genéricas (summary, plot, etc) e que elas realizam tarefas diferentes de acordo com a classe do objeto que é passado como argumento: summary(objeto).

Iremos a seguir criar uma função genérica denominada fruta.

fruta <- function(x, ...)
  UseMethod('fruta')
Criando métodos para a nova função genérica (fruta)
fruta.default   <- function(x, ...) print('Não sei qual fruta!')
fruta.Abacaxi   <- function(x, ...) print('Abacaxi!')
fruta.Carambola <- function(x, ...) print('Carambola!')
fruta.Pitanga   <- function(x, ...) print(paste(x,
                                                '-',
                                                'Pitanga!'))

O objeto '…' você já sabe para que serve, não? Se não, veja acima em: Brincando (um pouco mais) com OO

Criando outros métodos para as genéricas (default) summary e plot

Podemos criar métodos adicionais para as genéricas já existentes no R:

summary.Frutas <- function(object, ...)
  paste('Você gosta de ',
        tolower(class(object)),
        '?',
        sep='', ...)

summary.Abacaxi <- function(object, ...)
  paste(class(object),
        ' deve ser servido em rodelas!',
        sep='', ...)
plot.Frutas <- function(x, ...)
{
   plot(1,
        type='n', ...)

   text(x=1,
        y=1,
        paste(class(x),
              'não são para se plotar, \n mas para comer!',
              sep=' ', ...),
        col='red', ...)
}

plot.Abacaxi <- function(x, ...)
{
   plot(1,
        type='n', ...)

   text(x=1,
        y=1,
        paste(class(x),
              'não é para se plotar, \n mas para comer',
              '\n (servido em rodelas)!',
              sep=' ', ...),
        col='red', ...)
}
Testando a nova função genérica fruta e seus métodos
a <- 1:3

class(a)
## [1] "integer"
fruta(a)                     # 'fruta.default'
## [1] "Não sei qual fruta!"
class(a) <- 'Carambola'
a
## [1] 1 2 3
## attr(,"class")
## [1] "Carambola"
fruta(a)                     # 'a' pertence a classe 'Carambola'!
## [1] "Carambola!"
class(a) <- c('Jaboticaba',
              'Maçã',
              'Abacaxi')     # 'a' irá pertencer a mais de uma classe!
a
## [1] 1 2 3
## attr(,"class")
## [1] "Jaboticaba" "Maçã"       "Abacaxi"
fruta(a)                     # Busca no vetor classe para dispatch -> 'Abacaxi'
## [1] "Abacaxi!"
class(a) <- c('Mamão',
              'Pitanga',
              'Abacaxi')
a
## [1] 1 2 3
## attr(,"class")
## [1] "Mamão"   "Pitanga" "Abacaxi"
fruta(a)                     # Busca no vetor classe para dispatch -> 'Pitanga'
## [1] "1 - Pitanga!" "2 - Pitanga!" "3 - Pitanga!"
class(a) <- 'Framboesa'
fruta(a)                     # Irá usar o método fruta.default
## [1] "Não sei qual fruta!"
summary(a)                   # Usa o método 'summary.default'
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     1.0     1.5     2.0     2.0     2.5     3.0
plot(a,
     pch=19,
     cex=2)

plot of chunk fruta

Testando os métodos feitos para as genéricas default: summary e plot
class(a) <- 'Frutas'
summary(a)                   # Usa o método 'summary.Frutas'
## [1] "Você gosta de frutas?"
plot(a)                      # Idem

plot of chunk frutas

class(a) <- 'Abacaxi'
summary(a)                   # Usa o método 'summary.Abacaxi'
## [1] "Abacaxi deve ser servido em rodelas!"
plot(a)                      # Idem

plot of chunk frutas

a <- unclass(a)
attributes(a)
## NULL
summary(a)                   # Usa o método 'summary.default'
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##     1.0     1.5     2.0     2.0     2.5     3.0
plot(a)                      # Usa o método 'plot.default'

plot of chunk frutas

vogais <- letters[c(1, 5, 9, 15, 21)]
b <- paste('a',
           vogais[1:4],
           sep='')
b
## [1] "aa" "ae" "ai" "ao"
class(b)
## [1] "character"
Alterando os atributos dos objetos de dados.

Note que o atributo dim pode alterar a classe de um objeto (conversão de classe)!

attr(b, 'dim') <- c(2, 2)
b
##      [,1] [,2]
## [1,] "aa" "ai"
## [2,] "ae" "ao"
class(b)
## [1] "matrix" "array"
attributes(b)
## $dim
## [1] 2 2
fruta(b)
## [1] "Não sei qual fruta!"

Pode-se acrescentar novos atributos aos objetos!

Isso deve ser feito para acrescentar alguma informação adicional útil ao objeto. Seu principal uso é na contrução de métodos.

attr(b,
    'JCFaria') <- 'Professor da UESC/Ilhéus/Bahia'

attributes(b)
## $dim
## [1] 2 2
## 
## $JCFaria
## [1] "Professor da UESC/Ilhéus/Bahia"
attributes(b)$JCFaria
## [1] "Professor da UESC/Ilhéus/Bahia"
fruta(b)    # Usa o método 'fruta.default'
## [1] "Não sei qual fruta!"
summary(b)  # Usa o método 'summary.matrix'
##       V1                 V2           
##  Length:2           Length:2          
##  Class :character   Class :character  
##  Mode  :character   Mode  :character
class(b) <- 'teste'
b
##      [,1] [,2]
## [1,] "aa" "ai"
## [2,] "ae" "ao"
## attr(,"JCFaria")
## [1] "Professor da UESC/Ilhéus/Bahia"
## attr(,"class")
## [1] "teste"
rownames(b) <- paste('l',
                     1:2,
                     sep='')

colnames(b) <- paste('c',
                     1:2,
                     sep='')

Adicionando um novo método a função genérica summary:

summary.teste <- function(object, ...)
{
   print(object[,])
   cat('\n')
   cat(attributes(object)$JCFaria)
   cat('\n')
}

summary(b)
##    c1   c2  
## l1 "aa" "ai"
## l2 "ae" "ao"
## 
## Professor da UESC/Ilhéus/Bahia

Encapsulamento

Encapsulamento é uma opção dos desenvolvedores de um determinado objeto, via de regra funções. Basicamente consiste em ocultar detalhes da implementação do objeto para o usuário final.

Como exemplo peguemos um motor. Não é necessário saber o funcionamento dele, apenas temos que saber que o método ligar da nossa classe motor irá fazer com que o nosso motor entre em funcionamento.

Ou seja, do ponto de vista do criador do objeto motor, não é necessário o usuário saber quais componentes o método irá acessar, modificar ou criar para que o motor entre em funcionamento.

O que o método ligar faz, não diz respeito a quem usa, apenas se usa e sabe-se que irá funcionar.

No conceito de orientação a objetos pode-se proteger aquilo que não se quer que sofra intervenção externa: métodos e atributos.

Os objetos protegidos só podem ser alterados a partir dos métodos em que foram declarados ou dentro do objeto a que eles pertencem.

methods(summary)
##  [1] summary.Abacaxi                summary.aov                    summary.aovlist*               summary.aspell*               
##  [5] summary.check_packages_in_dir* summary.connection             summary.data.frame             summary.Date                  
##  [9] summary.default                summary.ecdf*                  summary.factor                 summary.Frutas                
## [13] summary.glm                    summary.infl*                  summary.jcfaria                summary.lm                    
## [17] summary.loess*                 summary.manova                 summary.matrix                 summary.mlm*                  
## [21] summary.nls*                   summary.packageStatus*         summary.POSIXct                summary.POSIXlt               
## [25] summary.ppr*                   summary.prcomp*                summary.princomp*              summary.proc_time             
## [29] summary.sockclientconn*        summary.srcfile                summary.srcref                 summary.stepfun               
## [33] summary.stl*                   summary.table                  summary.teste                  summary.tukeysmooth*          
## [37] summary.warnings              
## see '?methods' for accessing help and source code
methods(plot)
##  [1] plot.Abacaxi        plot.acf*           plot.data.frame*    plot.decomposed.ts* plot.default        plot.dendrogram*   
##  [7] plot.density*       plot.ecdf           plot.factor*        plot.formula*       plot.Frutas         plot.function      
## [13] plot.hclust*        plot.histogram*     plot.HoltWinters*   plot.isoreg*        plot.jcfaria        plot.lm*           
## [19] plot.medpolish*     plot.mlm*           plot.ppr*           plot.prcomp*        plot.princomp*      plot.profile.nls*  
## [25] plot.raster*        plot.spec*          plot.stepfun        plot.stl*           plot.table*         plot.ts            
## [31] plot.tskernel*      plot.TukeyHSD*     
## see '?methods' for accessing help and source code

Observe que vários métodos das funções genéricas (summary, plot) aparecem com um asterisco (*). Se você digitar o nome de um desses objetos (no caso funções) encapsuladas (ou mais tecnicamente do ponto de vista do R - não exportadas), por exemplo:

summary.aovlist  # Pacote 'stats'
## Error in eval(expr, envir, enclos): objeto 'summary.aovlist' não encontrado
summary.prcomp   # Pacote 'stats'
## Error in eval(expr, envir, enclos): objeto 'summary.prcomp' não encontrado
## ou

plot.data.frame  # Pacote 'graphics'
## Error in eval(expr, envir, enclos): objeto 'plot.data.frame' não encontrado
plot.prcomp      # Pacote 'stats'
## Error in eval(expr, envir, enclos): objeto 'plot.prcomp' não encontrado

não terá acesso diretamente ao código fonte do objeto da classe função, como é comum no R.

Do ponto de vista dos desenvolvedores desses objetos (funções) o usuário não tem motivos para ter acesso ao código, dai optou por os encapsular, não por maldade! Eles fazem o que fazem e ponto final. :(

Particularmente, não vejo muito sentido em encapsular objetos num ambiente tão aberto e democrático quanto o R.

Contudo, não fique triste. Não será necessário pegar o código fonte do R para ter acesso ao código fonte dos objetos encapsulados. Existe uma alternativa: pacote:::objeto-encapsulado

stats:::summary.prcomp      # Pacote 'stats'
## function (object, ...) 
## {
##     chkDots(...)
##     vars <- object$sdev^2
##     vars <- vars/sum(vars)
##     importance <- rbind(`Standard deviation` = object$sdev, `Proportion of Variance` = round(vars, 
##         5), `Cumulative Proportion` = round(cumsum(vars), 5))
##     k <- ncol(object$rotation)
##     colnames(importance) <- c(colnames(object$rotation), rep("", 
##         length(vars) - k))
##     object$importance <- importance
##     class(object) <- "summary.prcomp"
##     object
## }
## <bytecode: 0x000002181c4b4f60>
## <environment: namespace:stats>
graphics:::plot.data.frame  # Pacote 'graphics'
## function (x, ...) 
## {
##     plot2 <- function(x, xlab = names(x)[1L], ylab = names(x)[2L], 
##         ...) plot(x[[1L]], x[[2L]], xlab = xlab, ylab = ylab, 
##         ...)
##     if (!is.data.frame(x)) 
##         stop("'plot.data.frame' applied to non data frame")
##     if (ncol(x) == 1) {
##         x1 <- x[[1L]]
##         if (class(x1)[1L] %in% c("integer", "numeric")) 
##             stripchart(x1, ...)
##         else plot(x1, ...)
##     }
##     else if (ncol(x) == 2) {
##         plot2(x, ...)
##     }
##     else {
##         pairs(data.matrix(x), ...)
##     }
## }
## <bytecode: 0x00000218199e3b90>
## <environment: namespace:graphics>
stats:::plot.prcomp         # Pacote 'stats'
## function (x, main = deparse1(substitute(x)), ...) 
## screeplot.default(x, main = main, ...)
## <bytecode: 0x000002181c581718>
## <environment: namespace:stats>

Ou seja, o objeto da classe operador ::: é a chave da capsula!

Considere ainda a existência de algumas funções que facilitam a vida nessas situações de métodos encapsulados ou não exportados:

argsAnywhere(plot.prcomp)
## function (x, main = deparse1(substitute(x)), ...) 
## NULL
getAnywhere(summary.prcomp)
## A single object matching 'summary.prcomp' was found
## It was found in the following places
##   registered S3 method for summary from namespace stats
##   namespace:stats
## with value
## 
## function (object, ...) 
## {
##     chkDots(...)
##     vars <- object$sdev^2
##     vars <- vars/sum(vars)
##     importance <- rbind(`Standard deviation` = object$sdev, `Proportion of Variance` = round(vars, 
##         5), `Cumulative Proportion` = round(cumsum(vars), 5))
##     k <- ncol(object$rotation)
##     colnames(importance) <- c(colnames(object$rotation), rep("", 
##         length(vars) - k))
##     object$importance <- importance
##     class(object) <- "summary.prcomp"
##     object
## }
## <bytecode: 0x000002181c4b4f60>
## <environment: namespace:stats>

Bom, espero que seu uso do R seja mais eficiente de agora em diante!

Repasse esse script sempre que tiver dúvidas, talvez você não entenda tudo da primeira vez.

Querendo contribuir com sua abrangência e qualidade: