R6类的多重继承

Rap*_*ter 11 oop r multiple-inheritance r6 dependency-inversion

实际问题

有什么方法可以解决R6不支持多重继承的问题

放弃

我知道R主要是一种功能语言.但是,它确实也有非常强大的面向对象的内置加:我看不出这有什么错模仿OOD原则/行为,当你

  1. 知道你是面向对象语言的原型,如C#,Java等.

  2. 您的应用程序原型需要自给自足("完整堆栈",包括DB后端,业务逻辑和前端/ UI)

  3. 你拥有像R6 这样伟大的 "原型技术"并且可以随意使用

上下文

我的用于Web应用程序的R原型需要"完全堆栈"/自给自足,并且尽可能接近我们的生产语言中使用的设计模式/原则和依赖注入容器(R中简单DI的概念证明)(C#/.净).

在这方面,我开始喜欢使用接口(或抽象类)来解耦代码模块并遵守OODSOLID原则D(依赖性反转原则)(详见 "Uncle Bob") ).

尽管R6没有明确支持接口,但我仍然可以完全模仿它们,除了"抽象方法"之外什么都没有定义R6类(参见下面的例子).这有助于我我的软件设计传达给我们不熟悉R的OO程序员.我努力为他们提供尽可能少的"概念转换努力".

但是,当我真正想从其他具体(而不是"类似抽象的"模仿接口类)继承时,我需要放弃我的inheritin R6Classfor 值,因为这将意味着定义不是一个但是两节课.inherit

在依赖倒置之前:

Foo取决于具体的课程Bar.从OOD原则来看,这非常糟糕,因为它导致代码紧密耦合.

Bar <- R6Class("Bar",
  public = list(doSomething = function(n) private$x[1:n]),
  private = list(x = letters)
)
Foo <- R6Class("Foo",
  public = list(bar = Bar$new())
)
inst <- Foo$new()
> class(inst)
> class(inst$bar)
[1] "Bar" "R6" 
Run Code Online (Sandbox Code Playgroud)

反转依赖后:

Foo并且Bar现在解耦了.两者都依赖于一个被类模仿的接口IBar.我可以决定在运行时将该接口的哪个实现插入到实例中Foo(通过Property Injection:field bar实现Foo)

IBar <- R6Class("IBar",
  public = list(doSomething = function(n = 1) stop("I'm the inferace method"))
)
Bar <- R6Class("Bar", inherit = IBar,
  public = list(doSomething = function(n = 1) private$x[1:n]),
  private = list(x = letters)
)
Baz <- R6Class("Baz", inherit = IBar,
  public = list(doSomething = function(n = 1) private$x[1:n]),
  private = list(x = 1:24)
)
Foo <- R6Class("Foo",
  public = list(bar = IBar$new())
)

inst <- Foo$new()
inst$bar <- Bar$new()
> class(inst$bar)
[1] "Bar"  "IBar" "R6"  
> inst$bar$doSomething(5)
[1] "a" "b" "c" "d" "e"

inst$bar <- Baz$new()
[1] "Baz"  "IBar" "R6"  
> inst$bar$doSomething(5)
[1] 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

为什么这使得关于OOD感觉有点铁道部:Foo应该是完全不可知的存储领域中的对象的方式bar实现.它需要知道的是它可以在该对象上调用哪些方法.并且为了知道这一点,它足以知道字段中的对象实现的接口bar(在我们的示例中IBar使用方法doSomething()).

使用基类的继承来简化设计:

到现在为止还挺好.不过,我想通过definining某些简化我的设计具体的,我的一些其他的基类水泥类可以继承.

BaseClass <- R6Class("BaseClass",
  public = list(doSomething = function(n = 1) private$x[1:n])
)
Bar <- R6Class("Bar", inherit = BaseClass,
  private = list(x = letters)
)
Baz <- R6Class("Bar", inherit = BaseClass,
  private = list(x = 1:24)
)

inst <- Foo$new()
inst$bar <- Bar$new()
> class(inst$bar)
[1] "Bar"       "BaseClass" "R6"   
> inst$bar$doSomething(5)
[1] "a" "b" "c" "d" "e"

inst$bar <- Baz$new()
> class(inst$bar)
[1] "Baz"       "BaseClass" "R6"       
> inst$bar$doSomething(5)
[1] 1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

结合"接口实现"和基础clases继承:

这是我需要多继承的地方所以这样的东西可以工作(PSEUDO CODE):

IBar <- R6Class("IBar",
  public = list(doSomething = function() stop("I'm the inferace method"))
)
BaseClass <- R6Class("BaseClass",
  public = list(doSomething = function(n = 1) private$x[1:n])
)
Bar <- R6Class("Bar", inherit = c(IBar, BaseClass),
  private = list(x = letters)
)
inst <- Foo$new()
inst$bar <- Bar$new()
class(inst$bar)
[1] "Bar"       "BaseClass" "IBar" "R6"
Run Code Online (Sandbox Code Playgroud)

目前,我的价值inherit已经被"用于"模仿接口实现,因此我失去了实际具体类的继承的"实际"好处.

另类思路:

或者,以某种方式明确支持界面具体类之间的区别将是很好的.例如像这样的东西

Bar <- R6Class("Bar", implement = IBar, inherit = BaseClass,
  private = list(x = letters)
)
Run Code Online (Sandbox Code Playgroud)

Rap*_*ter 7

对于那些感兴趣的人:

我给它一个第二个想法,并意识到这是它不是真正的多重继承 本身,我想/需要,而是某种更好地模仿使用的接口/抽象类的不放弃inherit为。

因此,我尝试进行一些调整,R6使我能够区分inheritimplement调用R6Class

这可能不是一个好主意的大量原因,但就目前而言,它已经完成了工作;-)

您可以从我的分支分支安装经过调整的版本。

devtools::install_github("rappster/R6", ref = "feat_interface")
library(R6)
Run Code Online (Sandbox Code Playgroud)

正确实现接口 “标准继承”:

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
Run Code Online (Sandbox Code Playgroud)

如果接口未正确实现(即方法未实现):

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

Non-implemented interface method: foo
Run Code Online (Sandbox Code Playgroud)

依赖注入的概念证明

这是一份小草稿,详细说明了R6中接口和依赖关系反转的动机和可能的实现方法。


Kon*_*lph 5

另外:当您知道要使用面向对象的语言(例如C#,Java等)进行原型设计时,我看不出模仿OOD原理/行为有什么问题。

这样做的问题是,您需要问这个问题,因为R只是一个不足以制作OOD系统原型的工具,因为R不支持您所需要的。

或者只是对解决方案中依赖于数据分析的那些方面进行原型设计,而不对那些不适合该范式的API方面进行原型设计。

也就是说,R的优势在于您可以编写自己的对象系统。毕竟,这就是R6。R6恰好不足以满足您的目的,但是没有什么可以阻止您实施自己的系统。特别是,S3已经允许多重继承,它只是不支持编码接口(相反,它们是临时生成的)。

但是,没有什么可以阻止您提供执行此编码的包装函数。例如,您可以实现一组函数,interface并且class(请注意名称冲突)可以按以下方式使用:

interface(Printable,
    print = prototype(x, ...))

interface(Comparable,
    compare_to = prototype(x, y))

class(Foo,
    implements = c(Printable, Comparable),
    private = list(x = 1),
    print = function (x, ...) base::print(x$x, ...),
    compare_to = function (x, y) sign(x$x - y$x))
Run Code Online (Sandbox Code Playgroud)

然后将生成(例如):

print.Foo = function (x, ...) base::print(x$x, ...)

compare_to = function (x, y) UseMethod('compare_to')

compare_to.foo = function (x, y) sign(x$x - y$x)

Foo = function ()
    structure(list(x = 1), class = c('Foo', 'Printable', 'Comparable'))
Run Code Online (Sandbox Code Playgroud)

… 等等。实际上,S4做类似的事情(但我认为很糟糕)。