【发布时间】:2016-02-15 16:41:23
【问题描述】:
实际问题
我有哪些选项可以解决R6 不支持multiple inheritance 的问题?
免责声明
我知道 R 主要是一种函数式语言。但是,它确实还内置了非常强大的面向对象。另外:我看不出模仿 OOD 原则/行为有什么问题
知道您正在为面向对象的语言(例如 C#、Java 等)原型设计。
您的应用原型需要自给自足(“全栈”,包括数据库后端、业务逻辑和前端/UI)
您拥有像 R6 这样的伟大“原型设计技术”,并且任您使用
上下文
我的网络应用 R 原型需要“全栈”/自给自足和尽可能接近我们生产中使用的设计模式/原则和dependency injection containers (proof of concept of simple DI in R)语言 (C#/.NET)。
在这方面,我非常喜欢使用接口(或抽象类)来解耦代码模块并遵守@987654326 的D(dependency 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"
依赖倒置后:
Foo 和 Bar 现在已解耦。两者都依赖于由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 的价值已经被“仅仅”用于模仿接口实现,因此我失去了继承对于我的实际具体类的“实际”好处。
另类想法:
另外,以某种方式明确支持 interface 和 concrete 类之间的区别会很棒。比如这样的
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