現在、私はソフトウェア エンジニアリング、ソフトウェア デザイン、デザイン パターンなどについて多くの本を読んでいます。まったく異なるバックグラウンドを持っているので、それは私にとってすべて新しい魅力的なものです。正しい技術用語を使用していない場合はご容赦ください。特定の側面を説明するために;-)
ほとんどの場合、参照クラス(R の OOP の方法)を使用することになりました。これは、オブジェクト指向が、私が行っている多くのことにとって正しい選択であるように思われるためです。
さて、できれば参照クラスを使用して、RでMVC(モデルビューコントローラー、MVP :モデルビュープレゼンターとも呼ばれます)パターンを実装することに関して、誰かが良いアドバイスや経験を持っているかどうか疑問に思っていました.
また、 observerやblackboardなどの他の「標準的な」デザイン パターンに関する情報にも非常に興味がありますが、これをあまり大雑把な質問にしたくはありません。最もクールなことは、最小限のサンプルコードを見ることだと思いますが、ポインター、「スキーマ」、図、またはその他のアイデアも大歓迎です!
同様のことに興味がある人には、次の本を本当にお勧めします。
2012 年 3 月 12 日更新
最終的に、MVC の解釈の小さな例を思いつきました (完全に正しいとは限りません ;-))。
パッケージの依存関係
require("digest")
クラス定義オブザーバー
setRefClass(
"Observer",
fields=list(
.X="environment"
),
methods=list(
notify=function(uid, ...) {
message(paste("Notifying subscribers of model uid: ", uid, sep=""))
temp <- get(uid, .self$.X)
if (length(temp$subscribers)) {
# Call method updateView() for each subscriber reference
sapply(temp$subscribers, function(x) {
x$updateView()
})
}
return(TRUE)
}
)
)
クラス定義モデル
setRefClass(
"Model",
fields=list(
.X="data.frame",
state="character",
uid="character",
observer="Observer"
),
methods=list(
initialize=function(...) {
# Make sure all inputs are used ('...')
.self <- callSuper(...)
# Ensure uid
.self$uid <- digest(c(.self, Sys.time()))
# Ensure hash key of initial state
.self$state <- digest(.self$.X)
# Register uid in observer
assign(.self$uid, list(state=.self$state), .self$observer$.X)
.self
},
multiply=function(x, ...) {
.self$.X <- .X * x
# Handle state change
statechangeDetect()
return(TRUE)
},
publish=function(...) {
message(paste("Publishing state change for model uid: ",
.self$uid, sep=""))
# Publish current state to observer
if (!exists(.self$uid, .self$observer$.X)) {
assign(.self$uid, list(state=.self$state), .self$observer$.X)
} else {
temp <- get(.self$uid, envir=.self$observer$.X)
temp$state <- .self$state
assign(.self$uid, temp, .self$observer$.X)
}
# Make observer notify all subscribers
.self$observer$notify(uid=.self$uid)
return(TRUE)
},
statechangeDetect=function(...) {
out <- TRUE
# Hash key of current state
state <- digest(.self$.X)
if (length(.self$state)) {
out <- .self$state != state
if (out) {
# Update state if it has changed
.self$state <- state
}
}
if (out) {
message(paste("State change detected for model uid: ",
.self$uid, sep=""))
# Publish state change to observer
.self$publish()
}
return(out)
}
)
)
クラス定義コントローラーとビュー
setRefClass(
"Controller",
fields=list(
model="Model",
views="list"
),
methods=list(
multiply=function(x, ...) {
# Call respective method of model
.self$model$multiply(x)
},
subscribe=function(...) {
uid <- .self$model$uid
envir <- .self$model$observer$.X
temp <- get(uid, envir)
# Add itself to subscribers of underlying model
temp$subscribers <- c(temp$subscribers, .self)
assign(uid, temp, envir)
},
updateView=function(...) {
# Call display method of each registered view
sapply(.self$views, function(x) {
x$display(.self$model)
})
return(TRUE)
}
)
)
setRefClass(
"View1",
methods=list(
display=function(model, x=1, y=2, ...) {
plot(x=model$.X[,x], y=model$.X[,y])
}
)
)
setRefClass(
"View2",
methods=list(
display=function(model, ...) {
print(model$.X)
}
)
)
ダミーデータを表現するためのクラス定義
setRefClass(
"MyData",
fields=list(
.X="data.frame"
),
methods=list(
modelMake=function(...){
new("Model", .X=.self$.X)
}
)
)
インスタンスを作成する
x <- new("MyData", .X=data.frame(a=1:3, b=10:12))
モデルの特性とオブザーバーの状態を調べる
mod <- x$modelMake()
mod$.X
> mod$uid
[1] "fdf47649f4c25d99efe5d061b1655193"
# Field value automatically set when initializing object.
# See 'initialize()' method of class 'Model'.
> mod$state
[1] "6d95a520d4e3416bac93fbae88dfe02f"
# Field value automatically set when initializing object.
# See 'initialize()' method of class 'Model'.
> ls(mod$observer$.X)
[1] "fdf47649f4c25d99efe5d061b1655193"
> get(mod$uid, mod$observer$.X)
$state
[1] "6d95a520d4e3416bac93fbae88dfe02f"
オブジェクトの uid は、初期化時にオブザーバーに自動的に登録されていることに注意してください。そうすれば、コントローラー/ビューは通知をサブスクライブでき、1:n の関係になります。
ビューとコントローラーをインスタンス化する
view1 <- new("View1")
view2 <- new("View2")
cont <- new("Controller", model=mod, views=list(view1, view2))
申し込む
コントローラーは、基になるモデルの通知をサブスクライブします
cont$subscribe()
サブスクリプションがオブザーバーに記録されていることに注意してください
get(mod$uid, mod$observer$.X)
登録済みビューの表示
> cont$updateView()
a b
1 1 10
2 2 11
3 3 12
[1] TRUE
開いているプロット ウィンドウもあります。
モデルの修正
> cont$model$multiply(x=10)
State change detected for model uid: fdf47649f4c25d99efe5d061b1655193
Publishing state change for model uid: fdf47649f4c25d99efe5d061b1655193
Notifying subscribers of model uid: fdf47649f4c25d99efe5d061b1655193
a b
1 10 100
2 20 110
3 30 120
[1] TRUE
基になるモデルがその状態の変更をオブザーバーに公開すると、両方の登録済みビューが自動的に更新され、オブザーバーがすべてのサブスクライバー (つまり、コントローラー) に通知することに注意してください。
未解決の質問
まだ完全には理解できていないと感じるのは次の点です。
- これは MVC パターンの正しい実装ですか? そうでない場合、私は何を間違えましたか?
- モデルの「処理」メソッド (データの集約、サブセットの取得など) は、モデルまたはコントローラー クラスに「属する」必要があります。これまでは、特定のオブジェクトが「実行」できるすべてのことを、このオブジェクトのメソッドとして常に定義してきました。
- コントローラーは、モデルとビューの間のすべての相互作用を制御する「プロキシ」のようなもの (「双方向」のようなもの) である必要がありますか、それともユーザー入力をモデルに伝達することのみを担当する必要がありますか (「一方向」のようなものですか?)