web-dev-qa-db-ja.com

文字列との結合はクラスメソッドよりも「緩い」ですか?

Swingを使用して、Javaで学校グループプロジェクトを開始しています。これは、データベースデスクトップアプリの単純なGUIです。

教授は昨年のプロジェクトのコードを教えてくれたので、彼がどのようにしているかを見ることができました。私の最初の印象は、コードが本来あるべきものよりはるかに複雑であるということですが、プログラマーは、自分が書いただけではないコードを見るときにこれをよく考えると思います。

私は彼のシステムが良いか悪いかの理由を見つけたいと思っています。 (私は教授に尋ねた、そして彼は私がなぜそれがより良いのか後で見るであろうと言った、それは私を満足させない。)

基本的に、彼の永続化可能なオブジェクト、モデル(ビジネスロジック)、およびビューの間の結合を回避するために、すべてが文字列で行われます。データベースに格納される永続化可能なオブジェクトは文字列のハッシュテーブルであり、モデルとビューは互いに「サブスクライブ」し、サブスクライブする「イベント」に文字列キーを提供します。

イベントがトリガーされると、ビューまたはモデルは、そのイベントの処理方法を決定するすべてのサブスクライバーに文字列を送信します。たとえば、ビューのアクションリスナーメソッドの1つ(これは、永続オブジェクトにBicycleMakeFieldを設定するだけだと思います):

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

その呼び出しは、最終的にVehicleモデルのこのメソッドに到達します。

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

教授は、このような方法は、ビューがビジネスロジックオブジェクトのメソッドを呼び出すだけの場合よりも拡張性と保守性に優れていると述べています。ビューとモデルはお互いの存在を知らないので、ビューとモデルの間には結合がないと彼は言います。

ビューとモデルが機能するために同じ文字列を使用する必要があるため、これはより悪い種類の結合であると思います。ビューやモデルを削除したり、文字列にタイプミスをしたりしても、コンパイルエラーは発生しません。また、コードは必要以上に長くなります。

私は彼とこれについて話し合いたいのですが、彼は業界経験を使用して、経験の浅い学生である私がするかもしれない議論に対抗します。私が見逃している彼のアプローチにはいくつかの利点がありますか?


明確にするために、私は上記のアプローチを、ビューをモデルに明らかに結合させることで比較したいと思います。たとえば、ビークルモデルオブジェクトをビューに渡し、ビークルの「メーカー」を変更するには、次のようにします。

vehicle.make = bicycleMakeField.getText();

これにより、現在、車両の型式を1か所に設定するためにのみ使用されている15行のコードが、読み取り可能な1行のコードに削減されます。 (そして、この種の操作はアプリ全体で何百回も行われるので、読みやすさと安全性の両方で大きな勝利になると思います。)


更新

私のチームリーダーと私は、静的型付けを使用してやりたいようにフレームワークを再構築し、教授に知らせ、最終的に彼にデモを提供しました。彼は私たちに彼の助けを求めない限り、そして私たちのチームの他のメンバーをスピードを保つことができれば私たちのフレームワークを使用できるほど寛大です-私たちには公平に思えます。

18
Philip

あなたの教授が提案するアプローチは stringly typed として最もよく記述され、ほとんどすべてのレベルで間違っています。

カップリングは 依存関係の逆転 によって削減するのが最善です。

32
back2dos

あなたの教授はそれを間違っています。彼はcompletely通信を双方向(ビューはモデルに依存せず、モデルはビューに依存しません)、これはオブジェクト指向設計とMVCの目的を完全に無効にします。クラスを書いてOOベースのアーキテクチャを設計する代わりに、テキストベースのメッセージに単に応答する個別のモジュールを書いているようです。

教授に公平を期すために、彼はJavaと呼ばれる非常に一般的な.NETインターフェースに非常によく似たものを作成することを試みています)INotifyPropertyChanged。これは、変更されたプロパティを指定する文字列を渡すことにより、変更イベントをサブスクライバーに通知します。少なくとも.NETでは、このインターフェイスは、主にデータモデルからオブジェクトを表示して、 GUI要素(バインディング)。

正しく行われた場合、このアプローチを取るcanにはいくつかの利点があります¹:

  1. ビューはモデルに依存しますが、モデルはビューに依存する必要はありません。これは適切ですInversion of Control
  2. Viewコードには、モデルからの変更通知への応答を管理する場所が1つと、サブスクライブ/サブスクライブ解除する場所が1つしかないため、参照メモリリークがハングする可能性が低くなります。
  3. イベントパブリッシャーに対してイベントを追加または削除する場合、コーディングのオーバーヘッドが大幅に少なくなります。 .NETにはeventsと呼ばれる組み込みのパブリッシュ/サブスクライブメカニズムがありますが、Javaでは、クラスまたはオブジェクトを作成し、各イベントのサブスクライブ/サブスクライブ解除コードを記述する必要があります。
  4. このアプローチの最大の欠点は、サブスクライブコードがパブリッシングコードと同期しなくなる可能性があることです。ただし、これは一部の開発環境でもメリットがあります。モデルで新しいイベントが開発され、それをサブスクライブするようにビューがまだ更新されていない場合でも、ビューは機能する可能性があります。同様に、イベントが削除された場合、いくつかのデッドコードがありますが、それでもコンパイルして実行できます。
  5. 少なくとも.NETでは、Reflectionが非常に効果的に使用され、サブスクライバーに渡された文字列に応じてゲッターとセッターを自動的に呼び出します。

つまり、要するに(そしてあなたの質問に答えるために)それは可能ですが、教授が取っているアプローチは、ビューとモデルの間の少なくとも一方向の依存関係を許さないのは誤りです。それは実用的ではなく、過度に学術的であり、手元にあるツールの使用方法の良い例ではありません。


Google GoogleでINotifyPropertyChangedを検索して、人々が見つけた100万通りの方法を見つけてください 実装時にマジックストリングを使用しないでください

19
Kevin McCormick

enums(またはpublic static final Strings)は、マジックストリングの代わりに使用する必要があります。

あなたの教授はOOを理解していません。たとえば、具体的なVehicleクラスがありますか?
そしてこのクラスに自転車の型を理解させるには?

Vehicleは抽象的で、Bikeという具象サブクラスが必要です。私は彼のルールに従って遊んで良い成績を得てから、彼の教えを忘れました。

11
Farmor

良い点があると思いますが、魔法の弦は悪いです。私のチームの誰かが数週間前にマジックストリングによって引き起こされた問題をデバッグしなければなりませんでした(しかし、彼らが書いたのは自分のコードに含まれていたので、口を閉じました)。そしてそれは起こった... 業界で !!

彼のアプローチの利点は、特に小規模なデモプロジェクトの場合、コーディングが高速になることです。短所は、タイプミスが発生しやすく、文字列が頻繁に使用されるほど、タイプミスコードが誤解する可能性が高くなることです。また、マジックストリングをchange変更したい場合は、リファクタリングツールがに含まれる文字列に触れる可能性があるため、リファクタリングは変数のリファクタリングよりも困難ですマジックストリング(サブストリングとして)がnot自体がマジックストリングであり、notに触れないでください。また、新しい開発者が同じ目的で新しいマジックストリングを発明し始めたり、既存のマジックストリングの検索に時間を浪費したりしないように、マジックストリングの辞書またはインデックスを保持する必要があるかもしれません。

このコンテキストでは、Enumはマジック文字列やグローバル文字列定数よりも優れているように見えます。また、定数文字列または列挙型を使用すると、IDEは、ある種のコード支援(オートコンプリート、リファクタリング、すべて参照の検索など)を提供します。 IDEは、マジックストリングを使用する場合、あまり役に立ちません。

「業界での経験」を使用することは、あまり論じられません。あなたの教授はそれよりも良い議論を考え出す必要があります:-)。

他の人が述べたように、魔法の文字列の使用は確かに嫌われ、列挙型を使用する方がはるかに安全であると考えられています。 enumには意味scopeがありますが、マジックストリングにはありません。それとは別に、教授が懸念を切り離す方法は、現在使用されている手法よりも古くて壊れやすい手法であると考えられています。

私がこれに答えている間、@ backtodosがこれをカバーしていたので、 Inversion of Control を使用するという私の意見を追加します(Dependency Injectionはフォームであり、Spring全体で実行されます)以前から業界のフレームワーク...)は、このタイプのデカップリングに対処するためのより近代的な方法と考えられています。

6
Martijn Verburg

明確にしましょう。 必然的にそこにいくつかの結合があります。そのような購読可能なトピックがあるという事実は、メッセージの残りの性質(「単一の文字列」など)と同様に、結合のポイントです。それを考えると、質問は、文字列または型を介して行われる場合、結合がより大きいかどうかの1つになります。その答えは、時間または空間にわたって分散されるカップリングが心配であるかどうかによって異なります。メッセージが後で読み込まれる前にファイルに保存されるか、別のプロセスに送信されるか(特に同じ言語で書かれているわけではありません)。文字列を使用すると、処理がはるかに簡単になります。欠点は、型チェックがないことです。失敗は、実行時まで検出されません(場合によっては検出されないこともあります)。

Javaの緩和/妥協戦略は型付きインターフェイスを使用することですが、その型付きインターフェイスは別のパッケージにあります。これはJava interfacesと基本的な値のタイプ(列挙、例外など)。noの実装がパッケージに含まれている必要があります。次に、それに対して実装し、メッセージを複雑な形で伝達する場合はJavaデリゲートの特定の実装では、必要に応じてマジック文字列を使用できます。また、JEEやOSGiなどのより専門的な環境にコードを移行することがはるかに簡単になり、テストなどの小さなことを支援します…

4
Donal Fellows

あなたの教授が提案しているのは、「魔法のひも」の使用です。これは、クラスを相互に「疎結合」し、変更を容易にしますが、アンチパターンです。文字列は、コード命令を格納または制御するために使用されることはほとんどなく、絶対に必要な場合は、ロジックを制御するために使用する文字列の値を一元的に定義し、期待される値に対して1つの権限が存在するようにする必要があります。特定の文字列。

その理由は非常に単純です。文字列は、構文や他の値との整合性についてコンパイラチェックされていません(文字列内のエスケープ文字やその他の言語固有の形式に関する適切な形式がチェックされています)。文字列には何でも入れることができます。そのため、絶望的に壊れたアルゴリズム/プログラムをコンパイルすることができ、実行するまでエラーが発生しません。次のコード(C#)を検討してください。

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

...このコードはすべてコンパイルされます。最初に試してください。しかし、エラーは再確認すると明らかです。この種のプログラミングには多数の例があり、これらはすべてこの種のエラーの影響を受けます。文字列を定数として定義した場合でも(.NETの定数は、使用される各ライブラリのマニフェストに書き込まれるため、次に、定義された値が変更されたときにすべて再コンパイルされ、値が「グローバルに」変更されます)。エラーはコンパイル時にキャッチされないため、ランタイムテスト中にキャッチする必要があります。これは、適切なコード演習を伴うユニットテストで100%のコードカバレッジでのみ保証されます。これは通常、最も絶対的なフェイルセーフでのみ見られます。 、リアルタイムシステム。

4
KeithS

実際、これらのクラスは依然として密に結合されています。今のところを除いて、それらは、物事が壊れたときにコンパイラーがあなたに伝えることができないような方法で密結合されています!

誰かが「BicycleMake」を「BicyclesMake」に変更した場合、実行時まで問題が発生していることは誰にもわかりません。

実行時エラーは、コンパイル時エラーよりも修正にコストがかかります。これはあらゆる種類の悪です。

2
17 of 26