56

現在、私はソフトウェア エンジニアリング、ソフトウェア デザイン、デザイン パターンなどについて多くの本を読んでいます。まったく異なるバックグラウンドを持っているので、それは私にとってすべて新しい魅力的なものです。正しい技術用語を使用していない場合はご容赦ください。特定の側面を説明するために;-)

ほとんどの場合、参照クラス(R の OOP の方法)を使用することになりました。これは、オブジェクト指向が、私が行っている多くのことにとって正しい選択であるように思われるためです。

さて、できれば参照クラスを使用して、RでMVC(モデルビューコントローラー、MVP :モデルビュープレゼンターとも呼ばれます)パターンを実装することに関して、誰かが良いアドバイスや経験を持っているかどうか疑問に思っていました.

また、 observerblackboardなどの他の「標準的な」デザイン パターンに関する情報にも非常に興味がありますが、これをあまり大雑把な質問にしたくはありません。最もクールなことは、最小限のサンプルコードを見ることだと思いますが、ポインター、「スキーマ」、図、またはその他のアイデアも大歓迎です!

同様のことに興味がある人には、次の本を本当にお勧めします。

  1. 実用的なプログラマー
  2. デザインパターン

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

基になるモデルがその状態の変更をオブザーバーに公開すると、両方の登録済みビューが自動的に更新され、オブザーバーがすべてのサブスクライバー (つまり、コントローラー) に通知することに注意してください。

未解決の質問

まだ完全には理解できていないと感じるのは次の点です。

  1. これは MVC パターンの正しい実装ですか? そうでない場合、私は何を間違えましたか?
  2. モデルの「処理」メソッド (データの集約、サブセットの取得など) は、モデルまたはコントローラー クラスに「属する」必要があります。これまでは、特定のオブジェクトが「実行」できるすべてのことを、このオブジェクトのメソッドとして常に定義してきました。
  3. コントローラーは、モデルとビューの間のすべての相互作用を制御する「プロキシ」のようなもの (「双方向」のようなもの) である必要がありますか、それともユーザー入力をモデルに伝達することのみを担当する必要がありますか (「一方向」のようなものですか?)
4

1 に答える 1

3
  1. かなり良さそうに見えますが、他のクラスにオブザーバーを追加する理由がよくわかりません (教えていただけるかもしれません) 通常、コントローラーはオブザーバーです。R でこれを行うのは本当に良い考えです。なぜなら、私が Java でそれを学んだとき、理解するのはそれほど簡単ではなかったからです (Java はいくつかの良い部分を隠しています)。

  2. はい、いいえ。このパターンにはさまざまな解釈があります。オブジェクトにメソッドを含めるのが好きです。それはモデルに属していると思います。簡単な例としては、GUI で解決手順を表示する数独ソルバーがあります。それをいくつかの部分に分割して、M、V、C に分けてみましょう: 生データ (おそらく 2D 配列)、数独関数 (次のステップの計算、...)、GUI、GUI に新しいことを伝える人ステップは次のように計算されます: M: 生データ + 数独関数、C: 変更について GUI に伝える人 / GUI 入力についてモデルに伝える人、V: ロジックのない GUI 他の人が数独関数をコントローラーに入れる、また、いくつかの問題に対してはより適切に機能する可能性があります

  3. あなたがそれを呼び出すような「一方向」コントローラーを持つことが可能であり、ビューはモデルのオブザーバーですコントローラーにすべてを実行させ、モデルとビューがお互いを認識しないようにすることも可能です(モデルビュープレゼンターを見てください、それはそれについてです)

于 2012-07-27T17:43:16.193 に答える