相互依存値の設計パターン


8

概要:密接に相互依存する値間での情報の重複を減らすための優れた設計パターンはありますか?

私の仕事では、他の数量を知っている場合に数量の1つを導出できるような数量間の関係があることはかなり一般的です。理想的なガスの法則がその例です。

Pv = RT

理想的なガスの状態を表すクラスを作成することを想像できます。クラスは当然、3つの特性を有するであろうPressureTemperatureSpecificVolume適切な種類のそれぞれ。

このクラスのオブジェクトのユーザーのために、あなたが両方の値を設定している場合ことを期待するのが自然と思われるPressureTemperature、あなたはその後の値を読み出すことができSpecificVolume、オブジェクトがあなたのためのことを計算していることを期待しています。

同様に、との両方に値を設定するPressureSpecificVolume、を読み取ることができますTemperature

ただし、このクラスを実際に実装するには、情報の重複が必要です。方程式のすべてのバリエーションを明示的にプログラムし、それぞれの場合に異なる変数を従属変数として扱う必要があります。

  1. T = P * v / R

  2. P = R * T / v

  3. v = R * T / P

これはDRYの原則に違反しているようです。それぞれが同じ関係を表していますが、これらのケースには独立したコーディングとテストが必要です。

実際のケースでは、私が考えているロジックはこの例よりも複雑ですが、同じ基本的な問題を示しています。したがって、ロジックを1回だけ、または少なくともより少ない回数で表現できれば、真の価値があります。

このようなクラスは、値が読み取られる前に適切に初期化されていることも確認する必要があることに注意してください。ただし、これは2番目の考慮事項です。


数学的例を挙げましたが、問題はデータ間の数学的関係だけに限定されません。これは、ポイントを説明するための簡単な例のように思われました。


5
あなたの例はどんな形の複製も示していないことに注意してください。DRYは、同じ動作を積極的に再現している場合にのみ適用されます。あなたの例の場合、方程式のすべてのバリエーションは異なる値を解きます-したがって、異なる振る舞いです。方程式のPv = RT形式は、オブジェクト指向の観点からは、それ自体では役に立たない一般化です。
T. Sar

重複はありません。
モニカへの危害を停止する

1
「違法な」状態を不可能にするという問題もありPv/T != Rます。そのアイデアが根本的な問題の解決に役立つかどうか、それがさらに複雑になるかどうかはわかりません。
Bernhard Hiller 2017

T. Sarが言ったように、ここでは繰り返しはありません。マーティン・マートを示唆する他のことは、それが解決するより多くの問題を引き起こすに違いありません。基本的に、数式を自動的に適用して別の数式から欠落している値を計算できるエンジンの検索ですが、それを実行する(知っている...)ライブラリをあきらめない限り、問題は発生しません。
Walfrat 2017

回答が指摘するように、命令型言語にはいくつかの問題があります。対照的に、Prologのような宣言/論理言語はそれを簡単にします。ウィキペディアを確認してください。それは命令型言語で行うことができ、それは「ただ」より多くの作業を必要とします。
Armaghast 2017年

回答:


12

OOの観点からは問題はありません。あなたは静的クラスGasLaw提供メソッドを持つことができます

GetVolume(R, T, P) 
GetPressure(R, T, v)

重複の問題はありません。オブジェクトもデータメンバーもありません。すべて異なる行動のみ。

ガスの法則は「一つのこと」と見なすこともできますが、操作はすべて異なります。法律のユースケースごとにメソッドを持つことは何の問題もありません。


3
これは、このようなクラスの内部で、情報/ロジックの重複をどのように減らすかという問題には実際には対応していません。外部インターフェイスは実際には問題ありません。
UuDdLrLrSs 2017

私はそれが質問を扱っていないことに同意しません。まったく同じことを達成する方法を理解する方がはるかに簡単であるため、マーティンはあなたがどうするかを尋ねていることをしないでくださいと言っているという答えは私には明らかです。
2017年

10

これはあなたが尋ねる方法では実際には不可能です。

考えてみましょう:理想的なガスオブジェクトがありますg。温度、圧力、および比容積の3つすべてを明示的に設定してから、次のように温度を再度取得したとします

IdealGas g;
g.setTemperature(t1);
g.setPressure(p1);
g.setVolume(v1);
assert(g.getTemperature() == what); // ?

必要があります:

  1. t1最初に設定した値を教えてください。
  2. 圧力と比容積を使用して値を計算しますか?
  3. 温度に設定した場合のみ、圧力と比容積を使用して値を計算しますか?
  4. 計算された値に十分近い場合は元の値を取得し(丸め誤差を許容する必要があります)、それ以外の場合は#2または#3として計算しますか?

これを行う必要がある場合は、各メンバーの多くの状態を追跡する必要があります。初期化されていないか、クライアントコードによって明示的に設定されているか、または計算された値をキャッシュしていますか?明示的に設定された値またはキャッシュされた値は、後で設定される別のメンバーによって無効にされていますか?

クライアントコードの読み取りから、動作を予測することは明らかに困難です。あなたがした答えを得た理由を理解することは常に難しいため、これは貧弱な設計です。複雑さは、この問題がオブジェクト指向に適さないこと、または少なくともステートフルな理想気体オブジェクトが抽象化の悪い選択であることを示しています。


8

最初に、3つの式すべてが異なる値を計算するため、DRY原則に違反するとは思わない。3つの値すべての間の依存関係であるという事実は、それをプログラム変数の割り当てではなく、数学の方程式として見るためです。

次のように不変クラスIdealGasであなたのケースを実装することをお勧めします

public class IdealGas {
    private static final double R = 8.3145;

    private final Pressure            pressure;
    private final Temperature         temperature;
    private final SpecificVolume      specificVolume;

    public IdealGas(Pressure pressure, Temperature temperature)
    {
        this.pressure = pressure;
        this.temperature = temperature;
        this.specificVolume = calculateSpecificVolume(pressure.getValue(), temperature.getValue());
    }


    public IdealGas(Pressure pressure, SpecificVolume specificVolume)
    {
        this.pressure = pressure;
        this.specificVolume = specificVolume;
        this.temperature = calculateTemperature(pressure.getValue(), specificVolume.getValue());
    }

    public IdealGas(Temperature temperature, SpecificVolume specificVolume)
    {
        this.temperature = temperature;
        this.specificVolume = specificVolume;
        this.pressure = calculatePressure(temperature.getValue(), specificVolume.getValue());
    }

    private SpecificVolume calculateSpecificVolume(double pressure, double temperature)
    {
        return new SpecificVolume(R * temperature / pressure);
    }

    private Temperature calculateTemperature(double pressure, double specificVolume)
    {
        return new Temperature(pressure * specificVolume / R);
    }

    private Pressure calculatePressure(double temperature, double specificVolume)
    {
        return new Pressure(R * temperature / specificVolume);
    }

    public Pressure getPressure()
    {
        return pressure;
    }

    public Temperature getTemperature()
    {
        return temperature;
    }

    public SpecificVolume getSpecificVolume()
    {
        return specificVolume;
    }
}

実装について説明させてください。クラスIdealGasは、不変性の最終値として3つのプロパティPressure、Temperature、SpecificVolumeをカプセル化しました。getXXXを呼び出すたびに、計算は行われません。秘訣は、与えられた2つのパラメーターの3つの組み合わせすべてに対して3つのコンストラクターを持つことです。すべてのコンストラクターで、不足している3番目の変数を計算します。計算は構築時に1回行われ、3番目の属性に割り当てられます。これはコンストラクタで行われるため、3番目の属性はfinalで不変にすることができます。不変性のために、クラスPressure、Temperature、SpecificVolumeも不変であると想定しています。

残りの部分は、すべての属性のゲッターを持つことです。パラメーターをコンストラクターに渡すため、セッターは存在しません。属性を変更する必要がある場合は、目的のパラメーターを使用して新しいIdealGasインスタンスを作成します。

私の例では、Pressure、Temperature、SpecificVolumeクラスは、double値の単純なラッパーです。サンプルコードはJavaですが、一般化することもできます。

このアプローチは一般化でき、すべての関連データをコンストラクターに渡し、コンストラクターで関連データを計算し、ゲッターのみを持ちます。


2
不変性とコンストラクターの注入でこれを解決することが、ここでの正しい答えです。オブジェクト指向の用語では、方程式の3つの異なるバリエーションは、3つの異なるコンストラクターに変換されます。IdealGasの各操作は別のオブジェクト(値)を生成します。+1これがここでの答えです。
Greg Burghardt 2017年

+1でソリューションを提供
keuleJ

5

これは本当に興味深い質問です!未知数が異なるだけで、3つのケースすべてで同じ方程式が使用されるため、情報を複製していることは原則として正しいです。

しかし、典型的な主流のプログラミング言語では、方程式を解くための組み込みのサポートはありません。プログラミングの変数は常に(実行時に)既知の量であるため、表面的な類似性にもかかわらず、プログラミング言語の式は数学の方程式に匹敵しません。

典型的なアプリケーションでは、あなたが説明するような方程式は、3つの別々の式として書かれるだけであり、あなたはその重複に対処します。しかし、方程式解法ライブラリは存在し、そのような場合に使用できます。

これはパターンではありませんが、制約の解決と呼ばれるプログラミングパラダイム全体であり、この種の問題のためにPrologのような専用のプログラミング言語があります。


3

これは、数学モデルをソフトウェアにエンコードするときによくある苛立ちです。どちらもガスの法則のような単純なモデルに非常によく似た表記法を使用しますが、プログラミング言語は数学ではないため、数学の世界で作業中に除外できる多くのことはソフトウェアで明示的に行う必要があります。

それはあなた自身を繰り返すことではありません。ほとんどのプログラミング言語には、方程式、代数変換、または「xの解法」の概念はありません。これらすべての概念のソフトウェア表現なしでは、ソフトウェアがT = P * v / R与えられたPv = RT方程式に到達する方法はありません。

気体の法則のような単純なモデルを見ると、プログラミング言語の不当な制限のように思えるかもしれませんが、ほんの少しだけ進んだ数学でも、閉じた代数解がない方程式や、効率的に解を導出できる既知の方法がない方程式を使用できます。ソフトウェアアーキテクチャでは、より複雑なケースではそれに依存できませんでした。

そして、単純なケースだけでは?その機能の仕様をプログラミング言語で読む必要があるかどうかはわかりません。モデルは変更されるのではなく、置き換えられる傾向があり、通常は解決する変数がいくつかあります。


1

クラスは当然、3つのプロパティを持つことになりPressureTemperatureSpecificVolume

いいえ、2つで十分です。しかし、それらをプライベートにととしてメソッドを公開pressure()temperature()specificVolume()。2つのプライベートプロパティに対応しないそのうちの1つは、適切なロジックを持つ必要があります。したがって、データの重複を排除できます。

そこパラメータを持つ3つのコンストラクタであるべき(P, T)(P, v)(T, v)。2つのプライベートプロパティに対応するそのうちの1つは、単にセッターとして機能します。他の2つには適切なロジックが必要です。もちろん、ロジックは3回(ここで2回、前の段落で1回)書かれていますが、重複ではありません。それらを重複と見なしても、それらは必要です。

これらの3つの表現は同じ関係を表していますか?

数学的にはあり、オブジェクト指向ではありません。オブジェクト指向では、オブジェクトは表現ではなく一流の市民です。表情に第一級オブジェクトを作成するには(または、同じことで表される関係を作るために)我々は言う、クラスを記述する必要がある、ExpressionまたはEquation簡単なタスク(いくつかのライブラリがあるかもしれません)されていません。

問題は、データ間の数学的関係だけに限定されません。

関係も一流の市民ではありません。そのため、Relationshipこれも簡単ではないクラスを記述する必要がある場合があります。しかし、重複して生活する方が簡単です。

重複に対処することを決定し、関係のパラメーターが高い場合は、ビルダーパターンの使用を検討してください。パラメーターが静的ファクトリーの使用をあまり考慮しない場合でも、コンストラクターのオーバーロードの制限を回避します。


1

理想的なガスの状態を表すクラスを作成することを想像できます。

クラスは理想気体の振る舞いを表す必要があります。クラスインスタンス-オブジェクト-は状態を表します。

これはシラミを摘むものではありません。クラスの設計は、動作と機能の観点から行う必要があります。動作は公開され、インスタンス化されたオブジェクト(そう、それは不要です)が動作を実行して状態を取得します。

そう:

Gas.HeatToFarenheit( 451 );

そして、次のことは単にナンセンスであり、現実の世界では不可能です。そして、コードでもそれをしないでください:

Gas.MoleculeDistiance = 2.34;  //nanometers

動作は「重複する情報」の質問を無効にする

異なるメソッドを介して同じ状態プロパティに影響を与えることは、重複ではありません。以下も温度に影響しますが、上記の複製ではありません。

Gas.PressurizeTo( 34 );  //pascals

0

おそらく、コード生成を使用して、指定した単一の形式からさまざまな形式の方程式を生成できます。

より複雑な式の場合、それはかなりトリッキーになると想像できます。しかし、あなたが与える単純なものについては、

  • 変数を片側から反対側に除算で移動します
  • 方程式の左側と右側を反転します

そのリストに乗算、加算、および減算を追加し、ベースの数式に各変数が1回だけある場合、文字列操作を使用して、適切なボイラープレートコードで数式のすべてのバージョンを自動的に生成し、既知の変数を指定して正しいものを使用することを想像できます。

Wolfram Alphaには、例えば、さまざまな方程式操作ツールがあります。

しかしながら!!生成するこれらのクラスの大きなリストがない限り、そのようなコードが時間を効果的に使用しているとは思えません。

このコードは関数ごとに1回だけ生成する必要があり、関数は複雑すぎて例のように簡単に解決できない可能性があります。

手動で各バージョンをコーディングすることは可能性の高い機能を生成する最速かつ最も信頼性の高い方法であることがあり、あなたは真実のために反復的に推測値とテストにあなたのコードソリューションを想像することができますが、私はあなたが考えて行う計算に関数を生成し、コンパイルする必要が処理能力が十分にある未知の変数。


0

あなたはこれを考えすぎていると思います、次の解決策を検討してください:

double computeIdealGasLaw(double a, double b, double c){
    return a * b / c;
}
// example
//T = P * v/R
double P = ....;
double v = ....;
// R is a constant;
double T = computeIdealGasLaw(P, v, R); 


//P = R * T/v
double T = ....;
double v = ....;
// R is a constant;
double P = computeIdealGasLaw(R, T, v); 

//v = R * T/P
double T = ....;
double P = ....;
// R is a constant;
double v = computeIdealGasLaw(R, T, P); 

定義する必要があるのは1つの関数だけで、重複はありません。これを内部クラスの実装に使用することも、この関数を使用して、どこで使用するパラメーターを変更するだけでもかまいません。

この同じパターンは、値が入れ替わる関数がある場合にも使用できます。

静的に型付けされた言語を使用していて、値が異なる型になる可能性がある関数を処理する必要がある場合は、テンプレートプログラミングを使用できます(注、C ++構文を使用)。

template<class A, class B, class C, class D>
D computeIdealGasLaw(A a, B b, C c){
    return D(a * b / c);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.