そのため、最近、コードにいくつかの主要なリファクタリングを行いました。私がしようとした主なことの1つは、クラスをデータオブジェクトとワーカーオブジェクトに分割することでした。これは、とりわけ クリーンコード のこのセクションに触発されました。
ハイブリッド
この混乱は、半分のオブジェクトと半分のデータ構造である不幸なハイブリッドデータ構造につながる場合があります。重要なことを行う関数があり、パブリック変数またはパブリックアクセサーとミューテーターのいずれかがあり、すべての目的と目的のために、プライベート変数をパブリックにして、他の外部関数がそれらの変数を手続き型プログラムが使用する方法で使用するように誘いますデータ構造。
そのようなハイブリッドは、新しい関数を追加することを困難にしますが、新しいデータ構造を追加することも困難にします。彼らは両方の世界で最悪です。それらを作成しないでください。それらは、作者が関数や型からの保護を必要とするかどうか、またはさらに悪いことに無知である混乱した設計を示しています。
最近、私はワーカーオブジェクトの1つ(たまたま Visitor Pattern を実装する)のコードを見て、これを確認しました。
@Override
public void visit(MarketTrade trade) {
this.data.handleTrade(trade);
updateRun(trade);
}
private void updateRun(MarketTrade newTrade) {
if(this.data.getLastAggressor() != newTrade.getAggressor()) {
this.data.setRunLength(0);
this.data.setLastAggressor(newTrade.getAggressor());
}
this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}
私はすぐに「機能が羨ましいです。このロジックはData
クラス、特にhandleTrade
メソッドにあるはずです。handleTrade
とupdateRun
は常に一緒に起こります "。しかし、「データクラスは単なるpublic
データ構造です。それを始めれば、ハイブリッドオブジェクトになるでしょう!」
何が良いのか、そしてその理由は?どちらを行うかをどのように決定しますか?
あなたが引用したテキストには良いアドバイスがありますが、構造体のようなものを意味すると仮定して、「データ構造」を「レコード」に置き換えます。レコードは、データの単なる集約です。それらは変更可能である可能性があります(したがって、関数型プログラミングの考え方ではステートフルです)が、内部状態はなく、保護する必要のある不変条件はありません。使用を容易にする操作をレコードに追加することは完全に有効です。
たとえば、3Dベクトルはダムレコードであると主張できます。ただし、これによってadd
のようなメソッドを追加でき、ベクターの追加が簡単になります。動作を追加しても、(それほど馬鹿ではない)レコードはハイブリッドになりません。
オブジェクトのパブリックインターフェイスでカプセル化を解除できるようになると、この行が交差します。直接アクセスできる内部がいくつかあり、オブジェクトが無効な状態になります。 Data
には状態があり、無効な状態になる可能性があるようです。
データに対して有効な状態があれば、コードはすべて正常であり、続行できます。それ以外の場合:Data
クラスは、独自のデータ整合性を担当します。取引の処理に常にアグレッサーの更新が含まれる場合、この動作はData
クラスの一部である必要があります。アグレッサーの変更で実行長をゼロに設定する必要がある場合、この動作はData
クラスの一部である必要があります。 Data
は、決してばかげた記録ではありませんでした。パブリックセッターを追加することで、すでにハイブリッドになっています。
これらの厳格な責任を緩和する検討できるシナリオが1つあります:Data
がプロジェクトにプライベートであり、したがって、パブリックインターフェースがあっても、クラスを適切に使用できます。ただし、これにより、Data
の一貫性をコード全体で維持する必要があり、中央の場所で収集するのではありません。
私は最近 カプセル化についての回答 を書きました。
handleTrade()
とupdateRun()
は常に一緒に発生する(そして、2番目のメソッドは実際にはビジター上にあり、データオブジェクトで他のいくつかのメソッドを呼び出す)という匂い 時間結合 。これは、特定の順序でメソッドを呼び出す必要があることを意味します。順序外でメソッドを呼び出すと、最悪の場合、何かが壊れるか、提供できなくなると思いますせいぜい意味のある結果。良くない。
通常、その依存関係をリファクタリングする正しい方法は、各メソッドが結果を返すことです。この結果は、次のメソッドにフィードするか、直接操作することができます。
古いコード:
MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();
新しいコード:
MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();
これにはいくつかの利点があります。
私の観点からは、 クラス には「状態の値(メンバー変数)と動作の実装(メンバー関数、メソッド)」が含まれている必要があります。
「不幸なハイブリッドデータ構造」は、パブリックではないクラス状態のメンバー変数(またはそのゲッター/セッター)をパブリックにすると発生します。
そのため、データデータオブジェクトとワーカーオブジェクトに別々のクラスを用意する必要はありません。
状態メンバー変数を非公開に保つことができるはずです(データベース層は非公開メンバー変数を処理できる必要があります)
フィーチャー羨望は、別のクラスのメソッドを過度に使用するクラスです。 Code_smell を参照してください。メソッドと状態を持つクラスがあれば、これはなくなります。