Rap*_*ter 11 oop r multiple-inheritance r6 dependency-inversion
我知道R主要是一种功能语言.但是,它确实也有非常强大的面向对象的内置加:我看不出这有什么错模仿OOD原则/行为,当你
知道你是面向对象语言的原型,如C#,Java等.
您的应用程序原型需要自给自足("完整堆栈",包括DB后端,业务逻辑和前端/ UI)
你拥有像R6 这样伟大的 "原型技术"并且可以随意使用
我的用于Web应用程序的R原型需要"完全堆栈"/自给自足,并且尽可能接近我们的生产语言中使用的设计模式/原则和依赖注入容器(R中简单DI的概念证明)(C#/.净).
在这方面,我开始喜欢使用接口(或抽象类)来解耦代码模块并遵守OOD的SOLID原则的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)
对于那些感兴趣的人:
我给它一个第二个想法,并意识到这是它不是真正的多重继承 本身,我想/需要,而是某种更好地模仿使用的接口/抽象类的不放弃inherit为。
因此,我尝试进行一些调整,R6以使我能够区分inherit和implement调用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中接口和依赖关系反转的动机和可能的实现方法。
另外:当您知道要使用面向对象的语言(例如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做类似的事情(但我认为很糟糕)。