Webアプリケーションがあります。テクノロジーが重要だとは思いません。構造は、左の図に示すN層アプリケーションです。 3つの層があります。
UI(MVCパターン)、ビジネスロジックレイヤー(BLL)、データアクセスレイヤー(DAL)
私が抱えている問題は、アプリケーションイベント呼び出しを介したロジックとパスがあるため、私のBLLが巨大であることです。
アプリケーションの一般的なフローは次のとおりです。
UIで発生したイベント、BLLのメソッドにトラバースし、ロジック(おそらくBLLの複数の部分で)を実行し、最終的にDALに戻り、BLL(おそらくより多くのロジック)に戻り、UIにいくつかの値を返します。
この例のBLLは非常にビジーであり、これをどのように分割するかを考えています。また、私は好きではないロジックとオブジェクトを組み合わせています。
右のバージョンは私の努力です。
ロジックは、アプリケーションがUIとDALの間をどのように流れるかですが、おそらくプロパティはありません...メソッドのみ(このレイヤーのクラスの大部分はできません )状態を保存しないため、静的である可能性があります)。 Pocoレイヤーは、プロパティを持つクラスが存在する場所です(名前、年齢、身長などが存在するPersonクラスなど)。これらはアプリケーションのフローとは何の関係もなく、状態を保存するだけです。
フローは次のとおりです。
UIからトリガーされ、一部のデータをUIレイヤーコントローラー(MVC)に渡します。これにより、生データが変換され、pocoモデルに変換されます。その後、pocoモデルはロジックレイヤー(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;
}
}
はい、あなたはcoreOOPの概念を壊している可能性が非常に高いです。しかし、気を悪くしないでください、人々はこれをすべて行います時間、それはあなたのアーキテクチャが「間違っている」という意味ではありません。適切なOO設計よりもおそらくメンテナンス性が低いと思いますが、これはどちらかというと主観的であり、とにかくあなたの質問ではありません。( ここ は、n層アーキテクチャー全般を批判する私の記事です)。
理由:OOPの最も基本的な概念は、データとロジックが単一のユニット(オブジェクト)を形成するということです)。これは非常に単純化された機械的なステートメントですが、それでも、設計では実際には実行されません(私が正しく理解していれば)。ほとんどのデータがほとんどのロジックから明確に分離されています。ステートレス(静的のような)たとえば、メソッドは「プロシージャ」と呼ばれ、一般にOOPに対して反対です。
もちろん例外は常にありますが、このデザインは原則としてこれらのことに違反しています。
繰り返しますが、 "OOP違反"!= "間違っている"と強調したいので、これは必ずしも価値判断ではありません。それはすべて、アーキテクチャの制約、保守性のユースケース、要件などに依存します。
関数型プログラミングの基本原則の1つは、純粋な関数です。
オブジェクト指向プログラミングの中心となる原則の1つは、関数とそれらが作用するデータを組み合わせることです。
アプリケーションが外部の世界と通信する必要がある場合、これらのコア原則の両方が失われます。実際、システムの特別に準備されたスペースでのみ、これらの理想に忠実であることができます。コードのすべての行がこれらの理想を満たす必要があるわけではありません。しかし、コードのどの行もこれらの理想を満たさない場合、OOPまたはFPを使用していると実際に主張することはできません。
したがって、関心のあるコードを移動するために単純にリファクタリングできない境界を越える必要があるため、あちこちにあるデータのみの「オブジェクト」を保持することは問題ありません。それがOOPではないことを知ってください。それが現実です。 OOPは、その境界の内側に入ると、そのデータに作用するすべてのロジックを1つの場所に集めるときです。
あなたもそれをしなければならないということではありません。 OOPはすべての人にとってすべてではありません。それはそれがそうであるものです。何かが続くと主張しないでくださいOOPあなたのコードを維持しようとする人々を混乱させるつもりです。
あなたのPOCOにはビジネスロジックがうまくあるように見えるので、貧血であることをあまり心配しません。私が心配しているのは、すべてが非常に変化しやすいように見えることです。ゲッターとセッターは実際のカプセル化を提供しないことに注意してください。 POCOがその境界に向かっている場合は問題ありません。これが実際のカプセル化されたOOPオブジェクトの完全な利点を与えていないことを理解してください。これをデータ転送オブジェクトまたはDTOと呼ぶ人もいます。
私が首尾よく使用したトリックは、OOP DTOを食べるオブジェクトを作成することです。私はDTOを パラメータオブジェクト として使用しています。私のコンストラクタはそれから状態を読み取ります( 防御的コピー )そしてそれを放棄します。これで、完全にカプセル化された不変のバージョンのDTOを取得しました。このデータに関係するすべてのメソッドは、境界のこの側にあれば、ここに移動できます。
ゲッターやセッターは提供していません。私は 教えてください、尋ねないでください に従います。あなたは私のメソッドを呼び出し、それらは必要なことを行います。彼らはおそらく彼らが何をしたかさえあなたに言わないでしょう。彼らはただそれをします。
今や最終的に何かが、どこか別の境界にぶつかることになり、これはすべて再び崩壊します。それはいいです。別のDTOをスピンアップし、壁の上に投げます。
これが、ポートとアダプタのアーキテクチャの本質です。私はそれについて 機能的観点 から読んでいます。多分それもあなたに興味があります。
説明を正しく読んだ場合、オブジェクトは次のようになります:(コンテキストなしでトリッキー)
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になりました。実際、Pocosを不変にすると、デザインは非常に機能的であると見なすことができます。
ただし、共有ロジック、ほとんど同じではないが静的なPocosを参照すると、設計の詳細について心配し始めます。
あなたが考えるか、誰かがこれではないか、そうでないとあなたに言ったので、コードを変更しないでください。問題が発生し、他の問題を作成せずにこれらの問題を回避する方法を見つけた場合は、コードを変更してください。
だからあなたは物事が好きではないことを除いて、あなたは変化を起こすために多くの時間を費やしたいと思います。現在抱えている問題を書き留めます。新しいデザインが問題をどのように解決するかを書き留めてください。改善の価値と変更を加えるコストを把握します。次に、これが最も重要です。これらの変更を完了する時間があることを確認してください。そうしないと、最終的にこの状態で半分、その状態で半分になり、それが可能な最悪の状況になります。 (私はかつて、13種類の文字列を含むプロジェクトに取り組み、1つのタイプで標準化するために、3つの識別可能な半ばの努力をしました)
私があなたのデザインで見た潜在的な問題の1つ(そしてそれは非常に一般的です)-私が今まで遭遇した絶対的に最悪の「OO」コードのいくつかは、「コード」オブジェクトから「データ」オブジェクトを分離するアーキテクチャによって引き起こされました。これは悪夢のようなものです!問題は、データオブジェクトにアクセスする必要があるときに、ビジネスコード内のどこにでもインラインでコーディングする傾向があることです(必要はありません。ユーティリティクラスまたは別の関数を作成して処理できますが、これは私は時間の経過とともに繰り返し起こることを見てきました)。
アクセス/更新コードは通常収集されないため、どこでも機能が重複することになります。
一方、これらのデータオブジェクトは、たとえばデータベースの永続化として役立ちます。私は3つの解決策を試しました:
「実際の」オブジェクトに値をコピーしたり、データオブジェクトを破棄したりするのは面倒です(ただし、そのようにしたい場合は有効な解決策になる場合があります)。
データオブジェクトにデータラングリングメソッドを追加することは機能しますが、複数の処理を実行する大きな乱雑なデータオブジェクトを作成する可能性があります。多くの永続化メカニズムがパブリックアクセサーを必要とするため、カプセル化をより困難にすることもできます...私はそれを行ったときは好きではありませんでしたが、それは有効なソリューションです
私にとって最も効果的な解決策は、「データ」クラスをカプセル化し、すべてのデータラングリング機能を含む「ラッパー」クラスのコンセプトです。この場合、データクラスはまったく公開しません(セッターやゲッターでさえもありません)。絶対に必要でない限り)。これにより、オブジェクトを直接操作する誘惑がなくなり、代わりに共有機能をラッパーに追加するように強制されます。
他の利点は、データクラスが常に有効な状態であることを保証できることです。ここに簡単な擬似コードの例があります:
// 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)
}
}
さまざまな領域のコード全体に年齢チェックが広がっていないことに注意してください。また、誕生日が何であるかさえわからないので、それを使用したくありません(他の何かに必要でない限り、追加できます)。
このカプセル化と安全性の保証が失われるため、私はデータオブジェクトを拡張するだけではありません。この時点で、データクラスにメソッドを追加することもできます。
そうすることで、ビジネスロジック全体にデータアクセスジャンク/イテレータの束が広がらず、読みやすくなり、冗長性が低くなります。また、同じ理由でコレクションを常にラップする習慣を身に付けることをお勧めします。つまり、ループや検索の構造をビジネスロジックから除外し、常に良好な状態に保つようにします。
「OOP」カテゴリは、あなたが説明しているものよりもはるかに大きく、より抽象的なものです。これはすべて問題ではありません。それは明確な責任、結束、結合を気にします。したがって、あなたが求めているレベルでは、「OOPSの実践」について尋ねることはあまり意味がありません。
つまり、あなたの例に:
MVCの意味について誤解があるようです。ビジネスロジックおよび「バックエンド」コントロールとは別に、UIを「MVC」と呼びます。しかし、私にとって、MVCにはWebアプリケーション全体が含まれています。
ここには非常に重要な基本的な仮定がいくつかあります。
重要なことに、UIはMVCのpartです。逆ではありません(図のように)。あなたがそれを受け入れるなら、脂肪モデルは実際にはかなり良いです-彼らが実際に彼らがすべきでないものを含んでいなければ。
「ファットモデル」とは、すべてのビジネスロジックがモデルカテゴリ(パッケージ、モジュール、選択した言語の名前が何であれ)にあることを意味することに注意してください。個々のクラスは、自分で与えたコーディングガイドライン(つまり、クラスごとまたはメソッドごとのコード行の最大行数など)ごとに、明らかにOOP構造になっている必要があります。
また、データ層の実装方法には非常に重要な影響があることにも注意してください。特に、モデルレイヤーがデータレイヤーなしで機能できるかどうか(たとえば、単体テスト用、または高価なOracle DBなどの代わりに開発者のラップトップ上の安価なインメモリDB用)。しかし、これは実際に私たちが現在見ているアーキテクチャのレベルでの実装の詳細です。明らかにここでは、まだ分離が必要です。つまり、データアクセスと直接インターリーブされた純粋なドメインロジックがあり、これを強く結合しているコードを見たくありません。別の質問のトピック。
あなたの質問に戻ると、新しいアーキテクチャと前述のMVCスキームの間には大きなオーバーラップがあるように思われるので、あなたは完全に間違った方法ではありませんが、何かを再発明しているようです、または、現在のプログラミング環境/ライブラリがそのことを示唆しているため、それを使用します。私にはわかりにくい。ですから、あなたが意図していることが特に良いのか悪いのかについて正確な答えを出すことはできません。すべての「もの」がそれを担当するクラスを1つだけ持っているかどうかを確認することで確認できます。すべてが非常にまとまりがあり、結合度が低いかどうか。それはあなたに良い指標を与えます、そして私の意見では、良いOOP=設計(またはそうするなら、同じの良いベンチマーク))には十分です。