12

实际问题

对于R6不支持多重继承这一事实,我有什么选择?

免责声明

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

  1. 知道您正在为 C#、Java 等面向对象的语言进行原型设计。

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

  3. 您拥有像 R6 这样出色的“原型制作技术”,并且可以随意使用

语境

我的 Web 应用程序R原型需要“全栈”/自给自足,尽可能接近我们的生产语言 (C#/.网)。

在这方面,我非常喜欢使用接口(或抽象类)来解耦代码模块并遵守OOD的SOLID原则的D依赖倒置原则)(“鲍伯叔叔”的详细解释) )。

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

但是,当我实际上想从其他具体(而不是“类抽象”模仿的接口类)继承时,我需要放弃我的价值,inherit因为这意味着不定义一个但在.R6Classinherit

例子

依赖反转之前:

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" 

依赖反转后:

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

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实现方式。它只需要知道它可以在该对象上调用哪些方法。为了知道这一点,知道字段中的对象实现的接口就足够了(在我们的例子中是方法)。barIBardoSomething()

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

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

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已经被“仅仅”用于模仿接口实现,因此我失去了继承对于我的实际具体类的“实际”好处。

另类的想法:

或者,以某种方式显式支持接口具体类之间的区别会很棒。例如像这样

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

2 回答 2

8

对于那些感兴趣的人:

我重新考虑了一下,意识到我想要/需要的并不是真正的多重继承 ,而是某种更好地模仿接口/抽象类的使用而不放弃它。inherit

所以我试着稍微调整R6一下,这样我就可以区分inheritimplement调用R6Class.

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

您可以从我的分叉分支安装调整后的版本。

例子

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

依赖注入的概念证明

这是一个小草稿,详细阐述了 R6 中接口和依赖倒置的动机和可能的实现方法。

于 2016-02-16T13:18:53.563 回答
6

另外:当您知道您正在为 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 做了类似的事情(但在我看来很糟糕)。

于 2016-02-15T17:32:07.510 回答