【问题标题】:Multiple inheritance for R6 classesR6 类的多重继承
【发布时间】:2016-02-15 16:41:23
【问题描述】:

实际问题

我有哪些选项可以解决R6 不支持multiple inheritance 的问题?

免责声明

我知道 R 主要是一种函数式语言。但是,它确实还内置了非常强大的面向对象。另外:我看不出模仿 OOD 原则/行为有什么问题

  1. 知道您正在为面向对象的语言(例如 C#、Java 等)原型设计

  2. 您的应用原型需要自给自足(“全栈”,包括数据库后端、业务逻辑和前端/UI)

  3. 您拥有像 R6 这样的伟大“原型设计技术”,并且任您使用

上下文

我的网络应用 R 原型需要“全栈”/自给自足尽可能接近我们生产中使用的设计模式/原则和dependency injection containers (proof of concept of simple DI in R)语言 (C#/.NET)。

在这方面,我非常喜欢使用接口(或抽象类)来解耦代码模块并遵守@987654326 的Ddependency inversion principle) @(detailed explanation 由“鲍勃叔叔”)。

尽管 R6 没有明确支持接口,但我仍然可以完美地模仿它们使用 R6 类,这些类只定义“抽象方法”(参见下面的示例)。这对我很多帮助我将我的软件设计传达给我们不太熟悉 R 的 OO 程序员。我力求他们尽可能少地进行“概念转换工作”。

但是,我需要在R6Class 中放弃我对inherit 的价值,因为当我真正想从其他具体 继承时,这会成为一个问题(而不是“ abstract-like" 模仿接口类),因为这意味着在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" 

依赖倒置后:

FooBar 现在已解耦。两者都依赖于由IBar 类模仿的接口。我可以决定我想在运行时将该接口的哪个实现插入到Foo 的实例中(通过属性注入实现:Foo 的字段bar )

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

关于为什么这对 OOD 有意义:Foo 应该完全不可知对象存储在字段 bar 中的方式实现。它只需要知道它可以在该对象上调用哪些方法。为了知道这一点,知道字段bar 中的对象实现的接口 就足够了(在我们的例子中,IBar 和方法doSomething())。

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

到目前为止,一切都很好。但是,我想通过定义某些 concrete 基类来简化我的设计,我的其他一些 concrete 类可以继承自这些基类。

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

结合“接口实现”和基类继承:

这是我需要多重继承的地方,所以这样的东西可以工作(伪代码):

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"

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

另类想法:

另外,以某种方式明确支持 interfaceconcrete 类之间的区别会很棒。比如这样的

Bar <- R6Class("Bar", implement = IBar, inherit = BaseClass,
  private = list(x = letters)
)

【问题讨论】:

  • 我发现它有很多问题,甚至(尤其是)原型设计:只是不要使用不合适的工具来对严重依赖继承和接口的 OOD 系统进行原型设计。
  • @KonradRudolph:你能详细说明一下原因吗?
  • 我不确定要详细说明什么。所以让我来问一个问题:为什么您使用明显不合适的原型制作工具?
  • IMO,R 确实在两个领域大放异彩:数据分析和原型设计。所以让我回到这个问题:为什么不如果您需要对与数据分析相关的功能进行原型设计,即使它碰巧是一种比 R 更依赖 OOD 的语言?我不明白为什么就 R 是函数式还是面向对象而言,它总是必须如此“黑白”。也许我只是不明白,但是 R 确实具有强大的面向对象特性(S3、S4、Ref Classes、R6)——那么充分利用它们有什么问题呢?
  • @NickUlle 是的,确实有几个:1) 我发现参考类与 R6 相比非常慢。 2)他们有一个更复杂的架构。 3) 它们是“仅限 S4”,而使用 R6,您可以在 S3 和 S4 世界之间灵活切换(只要您真正需要,就可以将 R6 课程变成正式的 S4 课程)

标签: r oop multiple-inheritance dependency-inversion r6


【解决方案1】:

有兴趣的朋友:

我仔细考虑了一下,意识到这并不是我真正想要/需要的多重继承 本身,而是某种更好地模仿接口的使用/abstract 类而不放弃inherit

所以我尝试了tweaking R6 a bit,这样我就可以在调用R6Class 时区分inheritimplement

这可能是一个坏主意的原因有很多,但现在,它完成了工作;-)

您可以从我的forked branch 安装调整后的版本。

示例

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

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

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

当一个接口没有正确实现时(即方法没有实现):

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

Non-implemented interface method: foo

依赖注入的概念证明

This is a little draft 详细阐述了 R6 中接口和依赖倒置的动机和可能的实现方法。

【讨论】:

    【解决方案2】:

    另外:当您知道自己正在为 C#、Java 等面向对象语言进行原型设计时,我看不出模仿 OOD 原则/行为有什么问题。

    问题在于你需要问这个问题,因为 R 只是一个不足以构建 OOD 系统原型的工具,因为它不支持你需要的东西。

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

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

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

    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))
    

    这将生成(例如):

    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'))
    

    ……等等。事实上,S4 做了类似的事情(但在我看来很糟糕)。

    【讨论】:

    • +1 抽出时间,说明您对为什么我想做的是一个坏主意的看法,并勾勒出您的方法。但我没有看到任何与我关于 R6 的实际问题相关的内容。对于所有与 OO 原型相关的事情,到目前为止,R6 对我来说都做得很好。多重继承是我真正缺少的唯一功能。
    • @Rappster 我同意 R6 是一个出色的 OO 系统。但正如您正确地观察到的那样,它根本不支持多重继承,而且我认为没有办法在不更改 R6 内部结构的情况下添加它。虽然这本身并不是一个糟糕的选择,但我对它的内部了解不够多,无法对此发表评论。
    • 很公平,谢谢!有人侵入了构成 R6 类的环境层,并以某种方式获得了多重继承,但这似乎需要逆向工程/理解的代码很多,而且确实是一个非常肮脏的 hack:github.com/wch/R6/issues/9
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-27
    • 2017-02-04
    • 2015-01-18
    • 2019-04-14
    • 1970-01-01
    • 2011-10-15
    • 2019-12-13
    相关资源
    最近更新 更多