このアーキテクチャでOOPプラクティスを破っていますか?


23

Webアプリケーションがあります。テクノロジーが重要だとは思わない。構造は、左の画像に示されているN層アプリケーションです。3つの層があります。

UI(MVCパターン)、ビジネスロジックレイヤー(BLL)およびデータアクセスレイヤー(DAL)

私が抱えている問題は、ロジックとアプリケーションイベントコールを介したパスがあるため、BLLが非常に大きいことです。

アプリケーションの一般的なフローは次のとおりです。

UIで発生したイベントは、BLLのメソッドにトラバースし、ロジック(おそらくBLLの複数の部分で)を実行し、最終的にDALに戻り、BLLに戻り(さらにロジックが高い場合)、UIに値を返します。

この例のBLLは非常に忙しいため、これを分割する方法を考えています。また、ロジックとオブジェクトが組み合わされており、それらは好きではありません。

ここに画像の説明を入力してください

右側のバージョンは私の努力です。

ロジックは依然としてアプリケーションがUIとDALの間を流れる方法ですが、おそらくプロパティはありません...メソッドのみ(このレイヤーのクラスの大部分は、状態を格納しないため、静的である可能性あります)。Pocoレイヤーは、プロパティ(名前、年齢、身長などが存在するPersonクラスなど)を持つクラスが存在する場所です。これらはアプリケーションのフローとは関係なく、状態のみを保存します。

フローは次のとおりです。

UIからトリガーされ、UIレイヤーコントローラー(MVC)にデータを渡します。これにより、生データが変換され、pocoモデルに変換されます。その後、pocoモデルはLogicレイヤー(BLL)に渡され、最終的には途中で操作される可能性のあるコマンドクエリレイヤーに渡されます。コマンドクエリレイヤーは、POCOをデータベースオブジェクトに変換します(ほぼ同じものですが、1つは永続化用に設計され、もう1つはフロントエンド用に設計されています)。アイテムが保存され、データベースオブジェクトがコマンドクエリレイヤーに返されます。その後、POCOに変換され、そこでロジックレイヤーに戻り、さらに処理され、最後にUIに戻される可能性があります

共有ロジックとインターフェイスは、MaxNumberOf_XやTotalAllowed_Xなどの永続データとすべてのインターフェイスを保持する場所です。

共有ロジック/インターフェイスとDALの両方が、アーキテクチャの「ベース」です。これらは外の世界について何も知りません。

共有ロジック/インターフェースとDAL以外のすべてがpocoについて知っています。

フローはまだ最初の例と非常によく似ていますが、各レイヤーが1つのこと(状態、フロー、その他)に責任を負います...しかし、このアプローチでOOPを壊していますか?

LogicとPocoをデモする例は次のとおりです。

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method1(PocoB pocoB) 
    { 
        return cmdQuery.Save(pocoB); 
    }

    /*This has no state objects, only ways to communicate with other 
    layers such as the cmdQuery. Everything else is just function 
    calls to allow flow via the program */
    public PocoA Method2(PocoB pocoB) 
    {         
        pocoB.UpdateState("world"); 
        return Method1(pocoB);
    }

}

public struct PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     public int DataC {get;set;}

    /*This simply returns something that is part of this class. 
     Everything is self-contained to this class. It doesn't call 
     trying to directly communicate with databases etc*/
     public int GetValue()
     {

         return DataB * DataC; 
     }

     /*This simply sets something that is part of this class. 
     Everything is self-contained to this class. 
     It doesn't call trying to directly communicate with databases etc*/
     public void UpdateState(string input)
     {        
         DataA += input;  
     }
}

現在説明しているように、アーキテクチャに根本的な問題はありません。
ロバートハーヴェイ

19
コード例には、さらなる洞察を提供するのに十分な機能詳細がありません。Foobarの例で十分な説明が得られることはほとんどありません。
ロバートハーベイ


4
オンラインで簡単に見つけられるように、この質問のより良いタイトルを見つけることができますか?
ソナーゴニュル

1
丁寧に言うと、層と層は同じものではありません。「層」は展開について、「層」はロジックについて話します。データ層は、サーバー側のコード層とデータベース層の両方にデプロイされます。UIレイヤーは、Webクライアント側とサーバー側コード層の両方にデプロイされます。表示するアーキテクチャは3層アーキテクチャです。層は「Webクライアント」、「サーバー側コード」、および「データベース」です。
ローランラリザ

回答:


54

はい、あなたはコア OOPの概念を壊している可能性が非常に高いです。しかし、気を悪くしないでください、人々はこれを常にします、それはあなたのアーキテクチャが「間違っている」ことを意味しません。おそらく適切なオブジェクト指向設計よりも保守性が低いと思いますが、これはどちらかというと主観的なものであり、あなたの質問ではありません。(ここでは一般的にn層アーキテクチャを批判鉱山の物品です)。

推論:OOPの最も基本的な概念は、データとロジックが単一のユニット(オブジェクト)を形成するということです。これは非常に単純化された機械的な記述ですが、たとえそうであっても、設計では実際には守られていません(正しく理解している場合)。ほとんどのデータをほとんどのロジックから明確に分離しています。たとえば、ステートレス(静的な)メソッドを持つことは「プロシージャ」と呼ばれ、一般にOOPに相反します。

もちろん例外は常にありますが、この設計はこれらのことを原則として違反します。

繰り返しますが、「OOPに違反する」!=「間違っている」と強調したいので、これは必ずしも価値判断ではありません。それはすべて、アーキテクチャの制約、保守性のユースケース、要件などに依存します。


9
賛成票を持っています、これは良い答えです、私が自分で書いているなら、これをコピー&ペーストしますあなたは、あなたがそれを使用していない場合はなしで行うことができます余分なオーバーヘッドの多くが付属しています
TheCatWhisperer

2
@TheCatWhisperer:現代のエンタープライズアーキテクチャは、OOPを完全に捨てるのではなく、選択的に(たとえばDTOの場合)捨てるだけです。
ロバートハーベイ

@RobertHarvey同意しました。デザインのどこにもOOPをほとんど使用しないという意味です
TheCatWhisperer

@TheCatWhisperer c#のようなoopの利点の多くは、必ずしも言語のoop部分ではなく、ライブラリ、ビジュアルスタジオ、メモリ管理などの利用可能なサポートにあります

@Orangesandlemonsよくサポートされている言語は他にもたくさんあるはずです...
TheCatWhisperer

31

関数型プログラミングの基本原則の1つは、純粋な関数です。

オブジェクト指向プログラミングの基本原則の1つは、機能を機能するデータと一緒に機能させることです。

アプリケーションが外部と通信する必要がある場合、これらの基本原則は両方ともなくなります。実際、システムに特別に用意されたスペースでのみ、これらの理想に忠実であることができます。コードのすべての行がこれらの理想を満たしている必要はありません。しかし、コードのどの行もこれらの理想を満たさない場合、OOPまたはFPを使用していると主張することはできません。

そのため、関心のあるコードを移動するためにリファクタリングできない単純な境界を越える必要があるため、飛び回るデータのみの「オブジェクト」を持つことは問題ありません。それがOOPではないことを知ってください。それが現実です。OOPとは、その境界内に入ると、そのデータに作用するすべてのロジックを1か所に集めることです。

あなたもそうしなければならないわけではありません。OOPはすべての人にとってすべてのものではありません。それが現実さ。OOPに続かないと主張したり、コードを維持しようとする人々を混乱させたりしないでください。

POCOにはビジネスロジックが含まれているように見えるので、貧血を心配する必要はありません。私が心配しているのは、それらがすべて非常に変更可能に見えることです。ゲッターとセッターは実際のカプセル化を提供しないことに注意してください。POCOがその境界線に向かっている場合は問題ありません。これが実際のカプセル化されたOOPオブジェクトの利点を完全に提供しているわけではないことを理解してください。これをデータ転送オブジェクトまたはDTOと呼びます。

私がうまく使ったトリックは、DTOを食べるOOPオブジェクトを作成することです。DTOをパラメーターオブジェクトとして使用します。私のコンストラクターはそれから状態を読み取り(防御コピーとして読み取り)、それを捨てます。これで、完全にカプセル化された不変バージョンのDTOができました。このデータに関係するすべてのメソッドは、その境界のこちら側にあれば、ここに移動できます。

ゲッターやセッターは提供していません。私は教えて、尋ねないでください。あなたは私のメソッドを呼び出し、彼らは何をする必要があるかを実行します。彼らはおそらく彼らが何をしたかさえあなたに話さないでしょう。彼らはただそれをします。

今、最終的に何か、どこかが別の境界にぶつかり、すべてが再びばらばらになります。それはいいです。別のDTOをスピンアップして、壁の上に投げます。

これが、ポートとアダプターのアーキテクチャの本質です。私は機能的な観点からそれについて読んでいます。たぶんそれもあなたの興味を引くでしょう。


5
ゲッターとセッターは実際のカプセル化を提供しません」-はい!
スパイダーボリス

3
@BoristheSpider-ゲッターとセッターはカプセル化を絶対に提供しますが、カプセル化の狭い定義には適合しません。
DavorŽdralo

4
@DavorŽdralo:これらは回避策として時折有用ですが、その性質上、ゲッターとセッターはカプセル化を破ります。内部変数を取得および設定する方法を提供することは、自分自身の状態とそれに基づいて行動することに責任を持つことの反対です。
cHao

5
@cHao-ゲッターが何であるか理解できません。オブジェクトプロパティの値を返すメソッドを意味するものではありません。これは一般的な実装ですが、データベースから値を返し、httpを介して要求し、その場で計算します。先ほど言ったように、ゲッターとセッターは、人々が独自の狭い(そして誤った)定義を使用する場合にのみカプセル化を解除します。
DavorŽdralo

4
@cHao-カプセル化は、実装を隠すことを意味します。それがカプセル化されます。Squareクラスに「getSurfaceArea()」getterがある場合、表面積がフィールドであるかどうか、オンザフライで計算される場合(高さ*幅を返す)または3番目のメソッドがわからないため、内部実装を変更できます。カプセル化されているので、いつでも好きなときに。
DavorŽdralo

1

あなたの説明を正しく読んだ場合、オブジェクトは次のようになります:(コンテキストなしでトリッキー)

public class LogicClass
{
    private ICommandQueryObject cmdQuery;
    public PocoA Method(PocoB pocoB) { ... }
}

public class PocoX
{
     public string DataA {get;set;}
     public int DataB {get;set;}
     ... etc
}

Pocoクラスにはデータのみが含まれ、Logicクラスにはそのデータに作用するメソッドが含まれています。はい、「クラシックOOP」の原則を破りました

繰り返しますが、あなたの一般化された説明から伝えるのは難しいですが、あなたが書いたものが貧血領域モデルとして分類されるのは危険です。

これは特に悪いアプローチではないと思います。また、Pocoを構造体とみなすと、より具体的な意味でOOPを壊すことはできません。その点で、オブジェクトはLogicClassesになりました。実際、Pocoを不変にすると、デザインは非常に機能的であると見なされます。

ただし、共有ロジックを参照する場合、ほとんど同じではないPocoと静的なデザインの詳細について心配し始めます。


基本的にあなたの例をコピーして、私の投稿に追加しました。申し訳ありませんが、最初は明確ではありませんでした
MyDaftQuestions

1
つまり、アプリケーションが何をするのかを教えてくれれば、例を書くのが簡単になるでしょう。LogicClassの代わりに、PaymentProviderなどを使用できます
Ewan

1

デザインで見た潜在的な問題の1つは(非常に一般的です)、これまでに遭遇した絶対に最悪の「OO」コードの一部は、「コード」オブジェクトから「データ」オブジェクトを分離するアーキテクチャが原因でした。これは悪夢のようなものです!問題は、ビジネスコードのどこでも、データオブジェクトにアクセスするときに、インラインでコーディングするだけであるということです(ユーティリティクラスまたはそれを処理する別の関数を作成する必要はありませんが、これは何ですか私は時間の経過とともに繰り返し発生するのを見てきました)。

通常、アクセス/更新コードは収集されないため、どこでも機能が重複します。

一方、これらのデータオブジェクトは、たとえばデータベースの永続性として便利です。私は3つの解決策を試しました:

値を「実際の」オブジェクトにコピーしたり、データオブジェクトを破棄したりするのは面倒です(ただし、そのようにしたい場合は有効な解決策になる可能性があります)。

データラングリングメソッドをデータオブジェクトに追加することはできますが、複数のことを実行している大きな乱雑なデータオブジェクトを作成できます。また、多くの永続化メカニズムがパブリックアクセサーを必要とするため、カプセル化をより困難にする可能性があります...完了したときは気に入らなかったが、有効なソリューションです

私にとって最も効果的なソリューションは、「データ」クラスをカプセル化し、すべてのデータラングリング機能を含む「ラッパー」クラスの概念です。その後、データクラスをまったく公開しません(セッターやゲッターさえも公開しません)絶対に必要でない限り)。これにより、オブジェクトを直接操作する誘惑がなくなり、代わりにラッパーに共有機能を追加するように強制されます。

もう1つの利点は、データクラスが常に有効な状態であることを保証できることです。簡単な擬似コードの例を次に示します。

// Data Class
Class User {
    String name;
    Date birthday;
}

Class UserHolder {
    final private User myUser // Cannot be null or invalid

    // Quickly wrap an object after getting it from the DB
    public UserHolder(User me)
    {
        if(me == null ||me.name == null || me.age < 0)
            throw Exception
        myUser=me
    }

    // Create a new instance in code
    public UserHolder(String name, Date birthday) {
        User me=new User()
        me.name=name
        me.birthday=birthday        
        this(me)
    }
    // Methods access attributes, they try not to return them directly.
    public boolean canDrink(State state) {
        return myUser.birthday.year < Date.yearsAgo(state.drinkingAge) 
    }
}

さまざまな領域でコード全体に年齢チェックを分散させておらず、誕生日が何であるかを把握することさえできないので、それを使用する誘惑に駆られていないことに注意してくださいその場合は追加できます)。

このカプセル化と安全性の保証を失うため、データオブジェクトを単に拡張する傾向はありません。その時点で、メソッドをデータクラスに追加するだけでもよいでしょう。

そうすることで、ビジネスロジックに大量のデータアクセスジャンク/イテレータが分散されず、はるかに読みやすくなり、冗長性が低くなります。また、同じ理由でコレクションを常にラップする習慣を身に付けることをお勧めします-ループ/検索構造をビジネスロジックから除外し、常に良好な状態であることを確認します。


1

あなたが考えているか、誰かがあなたにそれがこれまたはそうでないとあなたに言うので、あなたのコードを決して変えないでください。問題が発生した場合にコードを変更し、他のコードを作成せずにこれらの問題を回避する方法を見つけました。

あなたが物事を好きではないこととは別に、あなたは変化を起こすために多くの時間を投資したいのです。あなたが今抱えている問題を書き留めてください。新しいデザインが問題を解決する方法を書き留めてください。改善の価値と変更のコストを把握します。そして、これが最も重要です-それらの変更を完了する時間があることを確認してください。そうしないと、この状態で半分、その状態で半分になり、それが最悪の状況になります。(かつて、13種類の文字列と、1つの種類で標準化するための3つの識別可能な半ば努力のプロジェクトに取り組みました)


0

「OOP」カテゴリは、説明しているものよりもはるかに大きく、抽象的なものです。これはすべて気にしません。明確な責任、結束、結合を重視します。したがって、あなたが求めているレベルでは、「OOPSの実践」について尋ねるのはあまり意味がありません。

それはあなたの例に言った:

MVCの意味について誤解があるように思えます。ビジネスロジックや「バックエンド」コントロールとは別に、UIを「MVC」と呼んでいます。しかし、私にとっては、MVCにはWebアプリケーション全体が含まれています。

  • モデル-ビジネスデータ+ロジックを含む
    • モデルの実装の詳細としてのデータ層
  • ビュー-UIコード、HTMLテンプレート、CSSなど。
    • JavaScriptなどのクライアント側の側面、または「1ページ」Webアプリケーションなどのライブラリが含まれます。
  • コントロール-他のすべてのパーツ間のサーバー側の接着剤
  • (ViewModelやBatchなどの拡張機能がありますが、ここでは説明しません)

ここには、非常に重要な基本的な前提事項がいくつかあります。

  • モデルクラス/オブジェクトは、他の部分(ビュー、コントロールなど)についてまったく知識を持ちません。それらを呼び出すことは決してなく、それらによって呼び出されるとは想定していません。セッション属性/パラメーター、またはこの行に沿ったその他のものは取得しません。それは完全に単独です。これをサポートする言語(Rubyなど)では、手動のコマンドラインを起動し、Modelクラスをインスタンス化し、それらを心のコンテンツに合わせて操作し、すべてを実行できます。合わせて操作し、ControlやViewまたは他のカテゴリのインスタンスなしでを実行できます。最も重要なのは、セッション、ユーザーなどに関する知識がないことです。
  • モデルを介さない限り、データレイヤーには何も触れません。
  • ビューは、モデルに軽く触れるだけで(ものを表示するなど)、他には何もありません。(優れた拡張機能は、複雑な方法でデータをレンダリングするためのより実質的な処理を行う特別なクラスである「ViewModel」であり、モデルまたはビューのいずれにも適合しないことに注意してください-これは、純粋なモデル)。
  • コントロールは可能な限り軽量ですが、他のすべてのプレーヤーを集め、それらの間でデータを転送します(つまり、フォームからユーザーエントリを抽出してモデルに転送し、ビジネスロジックから有用な例外を転送します)ユーザー向けのエラーメッセージなど)。Web / HTTP / REST APIなどの場合、すべての承認、セキュリティ、セッション管理、ユーザー管理などはここ(およびここのみ)で行われます。

重要:UIはMVCの一部です。逆ではありません(図のように)。あなたがそれを受け入れるなら、脂肪モデルは実際にはかなり良いです-本当にそうすべきでないものが含まれていなければ。

「脂肪モデル」とは、すべてのビジネスロジックがモデルカテゴリ(パッケージ、モジュール、選択した言語の名前に関係なく)に含まれることを意味することに注意してください。個々のクラスは、コーディングガイドライン(たとえば、クラスまたはメソッドごとの最大コード行数など)ごとに、適切な方法でOOP構造にする必要があります。

また、データレイヤーの実装方法には非常に重要な結果があることに注意してください。特に、モデルレイヤーがデータレイヤーなしで機能できるかどうか(例:単体テスト用、または高価なOracle DBの代わりに開発者のラップトップ上の安価なインメモリDBなど)。しかし、これは実際に私たちが見ているアーキテクチャのレベルでの実装の詳細です。明らかにここでは、あなたはまだ分離したい、つまり、データアクセスに直接インターリーブされた純粋なドメインロジックを持つコードを見たくないでしょう。別の質問のトピック。

あなたの質問に戻ると、あなたの新しいアーキテクチャと私が説明したMVCスキームとの間には大きな重複があるように思われるので、あなたは完全に間違った方法ではありませんが、何かを再発明しているようです、または、現在のプログラミング環境/ライブラリがそのようなことを示唆しているため、それを使用します。私に言うのは難しい。ですから、あなたが意図していることが特に良いか悪いかについて正確な答えをすることはできません。すべての「もの」が、その責任を負うクラスを1つだけ持っているかどうかを確認することで確認できます。すべてが非常に粘着的で低結合であるかどうか。これは良い兆候を示しており、私の意見では、良いOOP設計(または、もしそうなら同じものの良いベンチマーク)に十分です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.