实际问题
免责声明
我知道R主要是一种函数式语言。但是,它也具有非常强大的面向对象编程能力。此外:当你
知道你正在为C#,Java等面向对象语言进行原型设计时
你的应用程序原型需要是自给自足的(包括DB后端、业务逻辑和前端/UI)
你可以使用诸如R6和shiny之类的出色的“原型技术”
背景
我的面向Web应用的R原型需要同时具备“全栈”/自给自足的能力和尽可能接近设计模式/原则以及我们生产语言(C#/.NET)中使用的依赖注入容器(简单DI在R中的概念验证)。
在这方面,我非常喜欢使用接口(或抽象类),以便解耦代码模块并符合依赖反转原则(OOD SOLID原则(“叔叔鲍勃”的详细解释)。
尽管 R6 并不明确支持接口,但我仍然可以使用定义了“抽象方法”的 R6 类来完美地模仿它们(请参见下面的示例)。这有助于我向那些不太熟悉 R 的面向对象程序员传达我的软件设计。我努力减少他们的“概念转换工作”。然而,为了实际从其他具体类(而非“抽象”-类似的模拟接口类)继承,我需要放弃在 R6Class 中对 inherit 的价值,这在实际上意味着需要定义两个类而不是一个。
示例
在依赖反转之前:
Foo 依赖于具体类 Bar。从面向对象设计原则的角度来看,这很糟糕,因为它会导致代码紧密耦合。
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 应该完全不知道 field bar 中存储的对象是如何实现的。它所需要知道的只是可以在该对象上调用哪些方法。为了知道这一点,只需要知道 field bar 中的对象实现了哪个接口(在我们的情况下是具有 doSomething() 方法的 IBar 接口)。
使用从基类继承来简化设计:
到目前为止,一切都很好。但是,我还想通过定义某些具体基类来简化我的设计,让我的其他一些具体类可以从中继承。
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)
)