web-dev-qa-db-ja.com

オブジェクト設計と結束-問題と潜在的なリファクタリング

要約

私は、コードベースのいくつかの機能のまとまりに頭を悩ませようとしています。私はさまざまな方法でこの設計にアプローチしましたが、最近、単一責任の原則を誤って適用した場合など、間違ったアプローチを採用したと確信しています。

問題

問題の領域は井戸であり、地面の丸い穴としても知られています。現在のコードは次のようになります...データのみのオブジェクトにデータのセットがあります...

注:これは完全なコードではなく、物事を短くするためのメソッド名だけです...

public class WellData
{
    public string Name { get; set; }
    public ICollection<SurveyPoint> SurveyPoints { get; set; }
    public ICollection<GeometryItem> GeometryItems { get; set; }
    public ICollection<TemperaturePoint> WellTemperature { get; set; }
    public ICollection<FluidPoint> WellFluids { get; set; }
}

このデータを操作する機能をいくつかの小さなクラスに分割しました。以下のように…

public class ReferenceWellSurvey
{
   ICollection<SurveyPointCalculate> _calculatedSurveyPoints;
   double GetTvdAtDepth(double depth) {}
   double GetAzimuthAtDepthRad(double depth) {}
   double GetInclinationAtDepthRad(double depth){}
   double GetTortuosityPeriodAtDepth(double  depth ) {}
   double GetTortuosityAmplitudeAtDepth(double depth ) {}
}

public class ReferenceWellGeometry
{
   ICollection<GeometryItem> _geometryItems;
   double GetFrictionAtDepth(double depth) {}
   double GetHoleIdAtDepth(double depth) {}
}

public class ReferenceWellTemperature
{
   ICollection<TemperaturePoint> _wellTemperatures;
   double GetTemperatureAtDepth(double depth) {}
   double GetSurfaceTemperature(double depth) {}
}
Etc.

最近、コードを見て、アプリケーションロジックで必要な場所で使用できるようにするには、これらすべてを接着して戻す必要があることに気付きました。何かのようなもの…。

public class ReferenceWellData
{
private IReferenceWellGeometry _refWellGeometry;
private IReferenceWellTemperature _refWellGeometry;
          private IReferenceWell _refWellGeometry;
          private IReferenceFluid _refWellFluid;
       //Proxy functions from each class to glue it all back together…
}

私を現在の混乱に導いたいくつかのこと。ウェルデータクラスにはいくつかの情報のコレクションが含まれており、すべてが1つのクラスにある場合、ほとんどの関数はデータの小さなサブセットに対してのみ動作します。それがまとまりを低くすると感じていた当時、数ヶ月後、これらのものは実際にはすべて一緒に属していると感じています。さらに、私のコードには、ReferenceWellSurveyクラスの1つのメソッドであるGetTvdAtDepth()を呼び出す必要があるクラスがいくつかあります。本当に私がしたことはこの問題を解決しませんでした。

これについてもっと考えた後、そしてこのウィキペディアの記事を読んだ後 http://en.wikipedia.org/wiki/Cohesion_(computer_science) 、私はいくつかのプログラミングの罪を犯したことに気づき始めています。

貧血ドメインモデル

•低凝集度

•物事を接着しようとするコードのコンストラクターオーバーインジェクション

•GetTvdAtDepthに依存するクラスに、必要のない他のメソッドへのアクセスを許可すると、インターフェイス分離の原則に違反します。

ソリューション

ある程度のまとまりを保ちながら、どのクラスにもあまり責任を負わない(神オブジェクトなし)、Goldiloxのデザインを探しています。
これを修正することについての私の現在の考えは次のとおりです。井戸データを操作する機能を同じクラスに移動します。これは、この機能をコミュニケーション/情報のまとまりに近づけるのに役立つはずです。

public class WellData : IWellData 
{
    public string Name { get; set; }
    public ICollection<SurveyPoint> SurveyPoints { get; set; }
    public ICollection<GeometryItem> GeometryItems { get; set; }
    public ICollection<TemperaturePoint> WellTemperature { get; set; }
    public ICollection<FluidPoint> WellFluids { get; set; }

   // Operates on SurveyPoint collection 
   double GetTvdAtDepth(double depth) {}
   double GetAzimuthAtDepthRad(double depth) {}
   double GetInclinationAtDepthRad(double depth){}
   double GetTortuosityPeriodAtDepth(double  depth ) {}
   double GetTortuosityAmplitudeAtDepth(double depth ) {}
   //Operates on WellGeometry collection 
   double GetFrictionAtDepth(double depth) {}
   double GetHoleIdAtDepth(double depth) {}
   //Operates on WellTemperature collection
   double GetTemperatureAtDepth(double depth) {}
   double GetSurfaceTemperature(double depth) {}
}

次に、1つの関数だけを必要とする複数のクラスの問題に対処するために、この1つの関数を個別のインターフェイスIGetTvdAtDepthにすることができます。このように、その機能を必要とするクラスは、WellDataインターフェイスに継承してそこに実装できるこのインターフェイスのみに依存できますが、他のクラスをWellDataクラスにあるすべての機能に結び付けることはできません。

このアプローチと元のアプローチについての考えは?私のAPIで、このクラスの必要な依存関係を満たすためにろくでなしの注入を使用していることです...現在、3つの必要な依存関係、Survey計算機、およびデータを参照する特別なクラスがあります。異なる測定深度で。私はこれから離れたいのですが、それはサービスにもっと多くのリフティングを行わせることに戻っているようです...

2
GetFuzzy

Uberインターフェースではなく、クラスを深く定義することから始めます 閉塞 抽象化。

私はそれを私の「油井ドメイン」データ構造を定義するものと考えています。

アイデアは、独立した/個別の意味を持つビットを発見することです。クラス定義が進化するにつれて、それ自体がクラスになる可能性のある部分を抽出します。その後、必要に応じて合成します。物事が進展するにつれて、常にクラスを編集します。このアプローチにより、横断的関心事に対処する高レベルのクラスを進化させることができます。

  • ReferenceWellWellですか、それとも派生したWellですか?静的または「参照」データ/状態は、それを単なる別のWellにします。
  • HoleIDは、Wellオブジェクトを一意に識別します

これはSurveyPointとは何でしょうか?

public class SurveyPoint
{
    // Each property is typed. This is intentional.
    // It gives us a place to encapsulate properties and
    // behaviors/methods that are "atomic" to each.

    public int              HoleID      { get; set; }
    public Geometry         Geometry    {get; set;}
    public Depth            Depth       {get; set;}
    public TemperaturePoint Temperature { get; set; }
    public Inclination      Inclination { get; set; }
    public Tortousity       Torture     { get; set; }
    public Asmuth           Asmuth      { get; set; }
    public Tvd              Tvd         { get; set; }
}

そして、TemperaturePointFluidPointGeometryItemなどは、SurveyPointのサブセット(サブクラスではない)にすぎませんか?言い換えれば、さまざまな側面。そう..

public class SurveyPoint
{
    //. . .

    public Temperature GetTemperature() {return this.Temperature;}
    public FluidPoint GetFluidPoint () {return this.FluidPoint;}
    // etc. and overload as needed.
}

public class SurveyPointCollection : Collection<SurveyPoint> {
    public TemperatureCollection GetTemperatures() {}
    public FluidPointCollection GetFluidPoints() {}

    public GetTemperature(Depth depth) {
       // iterate collection to find the SurveyPoint with that depth
     }
    //etc.
}

public class Temperature {
    public HoleID {get; set;}
    public double Temperature {get; set;}
    // any other properties help *define* a temperature for a well

    // add any useful methods that operate only on properties defined in this class
}

// a similar class for each of the other SurveyPoint aspects.

コアデータ構造を中心に進化し、回転する結束の1つの側面がわかります。データクラスが(十分に)詳細に記述されると、動作をデータカプセル化の適切なレベルに「マッピング」し始め、非常にまとまりのあるオブジェクト指向クラスを作成できます(はい、@ FrankHilemanは正しいですが、機能的には客観的ではないと考えています)。

次に、Wellが次のようになり始めると思います

public class Well
{
    public string Name {get; set;}
    public int HoleID {get; set;}
    public SurveyPointCollection SurveyPoints {get; set;}

    public Well () { }

    public TemperatureCollection GetTemperatures () { 
         SurveyPoints.GetTemperatures();
    }

    public Temperature GetTemperature ( Depth depth ) { }

    // etc. for all other aspects.

}

行動の側面を探る

public class Surveyer
{
    protected Well MyWell { get; set; }
    protected DepthCalculator Calculator { get; set; }

    public Surveyer ( Well theWell ) {
        MyWell = theWell;
        Calculator = new DepthCalculator (MyWell.GetSurveyPoints());
    } 
}

「深さ」はReferenceWellSurveyメソッド名の統一的なもののように思われるので、私はDepthCalculatorを想像しました。ただし、さまざまなデータの側面に対応する計算がある可能性があります。だから私たちはこれを持っているかもしれません:

public class DepthCalculator {
    protected TemperatureCalculator {get; set;}
    protected InclinationCalculator {get; set;}
    protected FrictionCalculator    {get; set;}
    // etc.

    public DepthCalculator (SurveyPointCollection dataPoints) {
        TemperatureCalculator = new TemperatureCalculator (dataPoints.GetTemperatures());
        FrictionCalculator = new FrictionCalculator(dataPoints.GetFrictionPoints());
    }
}

「オイルウェルドメイン」については、これをどこに行けばよいのか十分にわかりません。ただし、インスピレーションを得るために Builderパターン を見るかもしれません。

3
radarbob

デザインパターンやデザインガイドラインが単純さの妨げになることがあります。あなたが説明する問題(設計上の問題ではなく、ソフトウェアによって解決される元の問題)は、構成可能な関数で構成されるカスタム言語によって最もよく解決されるように思えます。パーサーなどが必要であることを意味するのではなく、広い意味で「言語」という言葉を使用します。オブジェクトはデータを保持し、関数はそのデータに対する操作を提供します。これはオブジェクト指向分析ではなく、機能分析です。その後、よりオブジェクト指向になりたい場合は、データをカプセル化するいくつかのオブジェクトにいくつかの関数を割り当ててみることができます。おそらく、機能設計が最初に来るべきであり、次にオブジェクト指向設計が来るべきです。

2
Frank Hileman

私はあなたがあなたの仕事ではるかに悪い罪を犯したと思います:あなたは何のテストも書きませんでした。はるかに抽象的な方法から、コードを使用するコードの観点からコードを見ていませんでした。設計しようとしているものを使用するコードを実際に作成すると、抽象化と動作を分割するための可能な方法を確認するのがはるかに簡単になります。したがって、実際にはより良い設計につながります。

次に注意すべきことは、SRPは優れていますが、残りのOLID原則なしでは使用しないでください。 SRPは、責任と呼ばれるものがあり、それを認識しておく必要があることを私たちに思い出させます。しかし、責任とは「2つの数字を足す責任」から「すべての顧客の問題を解決する責任」まで、あらゆるものを意味する可能性があるため、設計の基礎となるのは非常に悪いルールです。これにより、SRPは非常にあいまいになります。ソフトウェア設計は複雑な分野であり、1つの原則だけに基づいているのはせいぜいばかげています。

2
Euphoric

注意

どんな答えも、私たちが与えられた問題/モデルの説明によってバイアスされます。より役立つドメインを見る他の方法があるかもしれません

脇の警告

ウェルクラスと、ウェルに関連する測定値のコレクションがいくつかあるようです。測定が異なる時間に繰り返される可能性がある場合、同じ種類であっても、単一のウェルに複数の測定セットが含まれる可能性があります

これは単純な構造につながります:オブジェクトとオブジェクトの一連の測定/計算-

Well - a concrete class for an instance of a well, including properties intrinsic to the well (like HoleID or Name)
WellOperation - an abstract class for a set of related Measurements on a specific Well
    ReferenceWellTemperature - a bunch of temperature measurements
    ReferenceWellGeometry - a bunch of geometry measurements
    etc.

各運用サブクラスには、特定の井戸を参照するデータが含まれています。単一のウェルには、これらが0個以上ある場合があり、異なる時点で同じ種類の倍数が取得される場合があります

したがって、ReferenceWellDataの例のような包括的なクラスは必要ありません。測定操作の数と種類が変更される可能性があります。これにより、包括的なクラスのインターフェイスが変更されますが、これは必須ではありません。

0
Steven A. Lowe

ReferenceWellSurveyReferenceWellGeometryなどが必要ない場合...コミュニケーションをとるには、この内訳を行うのが理にかなっています(実装の観点から)。ただし、クラス構造を確立するために実装レベルの詳細に依存することはお勧めできません。

「最近、コードを見て、アプリケーションロジックで必要な場所で使用できるようにするには、これらすべてを接着して戻す必要があることに気付きました。」つまり、このクラスのユーザーは、実装レベルの分解について気にしない(したがって、公開されないほうがよい)からです。しかし、この方法で物事を接着することは必ずしも悪いことではありません。

「私を現在の混乱に導いたいくつかのこと」...現在の混乱は何ですか?それぞれが関連データの小さなセットを担当するクラスのコレクションが別のクラスの実装をサポートするという事実は、混乱を構成するものではありません。ここで、ReferenceWellSurveyReferenceWellGeometry(または同様のもの)からインポートする必要がある場合これらのサポートクラス間の依存関係)、まあ、それは混乱し、「実装指向」の内訳を損なうでしょう。

  • WellDataは存続する必要があります(これに「Well」という名前を付けない理由はありますか?データにウェルの並行モデルはありますか)サービス指向の内訳かどうか。適切に設計されたクラスは、直感的に名前が付けられ、便利な機能を公開するクラスです。
  • IWellDataは存続する可能性があります。何かがそれを使用していると仮定します。
  • サービス指向クラスへの分類がすでにぎこちなく感じ始めている場合は、それを取り除きます。あなたのコードを維持しようとしている初心者の観点からこれを見てみてください。かわいそうな古いウェルオブジェクトは彼らにとって喜ばしいでしょう。
  • IGetTvdAtDepth(名前をITvdCalculator?Tvdは頭字語ですか?)は問題ないように見えますが、残りの機能を非表示にすることが正当であることを確認してください(誰かが別のウェルにアクセスしたい場合)将来のメソッドは問題ありませんか?つまり、これらのメソッドは不要なため、またはアクセスすべきではないため、非表示にしていますか?)

「物事のやり方で私を悩ませていることの1つは、私のAPIで、このクラスの必要な依存関係を満たすためにろくでなしの注入を使用していることです...現在3つの必要な依存関係があります」これは問題ですか?もしそうなら、それはIWellDataを定義するための理論的根拠を提供するかもしれません。

結論として、提案された解決策は問題ないかもしれないと思います。これは責任の原則に反しますか?たぶん、しかし、あなたのドメインで「Well」の定義を支持する設計は、実装レベルのクラスを作成するよりも優れた設計だと思います。

0
user142866

機能クラスには、実際の内部状態または可能なバリエーション/代替実装がありますか?そうでない場合は、ReferenceWellGeometryを、コレクションに1つの追加パラメーターを受け取るすべてのstaticメソッドを持つユーティリティクラスに書き換えることができますか?例えば.

static double GetFrictionAtDepth( ICollection<GeometryItem> _geometryItems, double depth) {}

このスタイルには欠点がありますが、あなたの場合、クラスの依存性/注入の問題をすべて単純化する可能性があります。

0
user949300