r-lib / R6

Encapsulated object-oriented programming for R
https://R6.r-lib.org
Other
412 stars 57 forks source link

Support for mimicking interfaces/abstract classes #82

Closed jankowtf closed 7 years ago

jankowtf commented 8 years ago

It'd be great to support at least mimicking (after all we're mostly only prototyping OO-related stuff in R) the use of interfaces/abstract classes.

My intention is not trying to make R6 and/or "OOP in R" a full-fledged equivalent of features in other "true" OO-languages. I simply think we could add a little useful feature here and there :-)

Motivation

My actual motivation and/or use case for this is as follows:

As my R prototypes for web-app-like stuff need to be as close as possible to design patterns and dependency injection containers used in our production language (C#), I've come to like to use interface (or abstract) classes to decouple my code and comply with the D (dependency inversion principle) of the SOLID principles of OOD (detailed explanation by "Uncle Bob")

Even though R6 does not explicitly support interfaces, I can nevertheless perfectly mimick them with R6 and this helps me a lot with communicating my software designs to our OO-programmers.

However, I need to give up my value for inherit in R6Class for that which becomes a bit of a problem when I actually want to inherit from other concrete (as opposed to the abstract interface class).

Example

One probably could go at it from the perspective of multpile inheritance, but that seems rather complicated to implement.

Another option is to mimick the use of abstract classes or interfaces. I did try to sketch a (hopefully) pragmatic solution for that in this pull request. This is also related to my comments in #9.

devtools::install_github("rappster/R6", ref = "feat_interface")
library(R6)

Correct implementation of interface and "standard inheritance":

  IFoo <- R6Class("IFoo",
    public = list(foo = function() stop("I'm the inferace method"))
  )
  BaseClass <- R6Class("BaseClass",
    public = list(foo = function(n = 1) private$x[1:n])
  )
  Foo <- R6Class("Foo", implement = IFoo, inherit = BaseClass,
    private = list(x = letters)
  )

> Foo$new()
<Foo>
  Implements interface: <IFoo>
  Inherits from: <BaseClass>
  Public:
    clone: function (deep = FALSE) 
    foo: function (n = 1) 
  Private:
    x: a b c d e f g h i j k l m n o p q r s t u v w x y z

Interface not implemented correctly (method not implemented):

 Bar <- R6Class("Bar", implement = IFoo,
    private = list(x = letters)
  )
> Bar$new()
Error in Bar$new() : 

Non-implemented interface method: foo

Proof of concept for dependency injection

This is a draft that elaborates a bit on the motivation and possible implementation approaches for interfaces and inversion of dependency in R6

hadley commented 7 years ago

I think this is a reasonable idea, but it's out of scope for R6 which is deliberately very minimal.

chrisknoll commented 3 weeks ago

Please reconsider this: it's an opt-in feature (you don't need to say you implement a feature, and existing code doesn't need to know the existence of it).

The implementation seems to be light weight: new field for implements on an R6 definition, and you check that the functions from the interface definition exist when you $new it. Ez-Pz.