フラグをチェックする必要をなくすためのデザインパターンはありますか?


28

データベースに文字列ペイロードを保存します。2つのグローバル構成があります。

  • 暗号化
  • 圧縮

これらは、構成を使用して有効または無効にすることができます。その場合、どちらか一方のみを有効にするか、両方を有効にするか、両方を無効にします。

私の現在の実装はこれです:

if (encryptionEnable && !compressEnable) {
    encrypt(data);
} else if (!encryptionEnable && compressEnable) {
    compress(data);
} else if (encryptionEnable && compressEnable) {
    encrypt(compress(data));
} else {
  data;
}

デコレータパターンについて考えています。それは正しい選択ですか、それとももっと良い選択肢がありますか?


5
現在持っているものの何が問題になっていますか?この機能の要件は変更される可能性がありますか?IE、新しいif声明がありそうですか?
ダレンヤング

いいえ、コードを改善するための他のソリューションを探しています。
ダミスガネーゴダ16年

46
これについては逆方向に進んでいます。パターンが見つからない場合、そのパターンに合うコードを記述します。要件に合わせてコードを記述し、必要に応じてパターンを使用してコードを記述します。
モニカとの軽さレース

1
質問が実際にこの質問の複製であると思われる場合は、質問者として、最近の再オープンを「オーバーライド」して、それ自体を単独で閉じるオプションがあります。私は自分の質問のいくつかにそれをしました、そしてそれは魅力のように働きます。ここで私はそれをやったか、簡単3つのステップ -私の「命令」との唯一の違いは、あなたがより少ない3K担当者を持っているので、あなたが通過する必要がありますということですフラグダイアログ「複製」を取得するオプション
ブヨ

8
@LightnessRacesinOrbit:あなたの言うことにはいくつかの真実がありますが、コードを構造化するより良い方法があるかどうかを尋ねることは完全に合理的であり、提案されたより良い構造を記述するために設計パターンを呼び出すことは完全に合理的です。(それでも、私はそれが設計を依頼するXY問題のビットだことに同意したパターン何をしたいことはある設計または厳密に任意の周知のパターンに従わない場合があり、。)また、「パターン」にするために合法的ですコードにわずかな影響を与えます。よく知られているパターンを使用している場合は、それに応じてコンポーネントに名前を付けるのが合理的です。
-ruakh

回答:


15

コードを設計する場合、常に2つのオプションがあります。

  1. ちょうどそれを成し遂げてください、その場合、ほとんどすべてのソリューションがあなたのために働くでしょう
  2. 独創的であり、その言語とそのイデオロギーの癖を活用するソリューションを設計します(この場合のオブジェクト指向言語-決定を提供する手段としての多型の使用)

言うべきことは何もないので、2つのうちの最初の1つに焦点を合わせるつもりはありません。動作させたいだけなら、コードをそのままにしておくことができます。

しかし、あなたがそれをつまらない方法で行うことを選択し、あなたが望んだ方法で実際にデザインパターンで問題を解決した場合、どうなりますか?

次のプロセスを見ることができます。

OOコードを設計するとき、コード内にあるのほとんどはifそこにある必要はありません。当然、intsやfloats などの2つのスカラー型を比較したい場合はを持つ可能性がありますがif、構成に基づいて手順を変更したい場合は、ポリモーフィズムを使用して目的を達成し、決定を移動できます(ifs)ビジネスロジックから、オブジェクトがインスタンス化される場所- 工場へ

現在、プロセスは4つの別々のパスを通過できます。

  1. data暗号化も圧縮もされていない(何も呼び出さず、return data
  2. data圧縮されている(呼び出しcompress(data)て返す)
  3. data暗号化されている(呼び出しencrypt(data)て返す)
  4. data圧縮および暗号化されている(呼び出しencrypt(compress(data))て返す)

4つのパスを見るだけで、問題が見つかります。

データを操作して返す3つの異なるメソッド(理論的には4を1つと見なさない場合は4)を呼び出す1つのプロセスがあります。メソッドには異なる名前、いわゆるパブリックAPI(メソッドが動作を通信する方法)があります。

アダプターパターンを使用して、発生した名前colision(パブリックAPIを統合できます)を解決できます。簡単に言うと、アダプターは、互換性のない2つのインターフェースが連携するのに役立ちます。また、アダプターは、新しいアダプターインターフェイスを定義することで機能します。クラスは、API実装を統合しようとします。

これは具体的な言語ではありません。これは一般的なアプローチであり、anyキーワードは任意のタイプであることを表すために存在します。C#のような言語では、generics(<T>)に置き換えることができます。

現時点では、圧縮と暗号化を担当する2つのクラスを持つことができると仮定します。

class Compression
{
    Compress(data : any) : any { ... }
}

class Encryption
{
    Encrypt(data : any) : any { ... }
}

企業の世界では、これらの特定のクラスでさえ、classキーワードに置き換えられるinterface(C#、Java、PHPなどの言語を扱う場合)、またはclassキーワードが残るなど、インターフェイスに置き換えられる可能性が非常に高くなりますが、C ++でコーディングする場合CompressEncryptメソッドは純粋仮想として定義されます。

アダプタを作成するには、共通のインターフェイスを定義します。

interface DataProcessing
{
    Process(data : any) : any;
}

次に、インターフェースの実装を提供して、便利なものにする必要があります。

// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
    public Process(data : any) : any
    {
        return data;
    }
}

// when only compression is enabled
class CompressionAdapter : DataProcessing
{
    private compression : Compression;

    public Process(data : any) : any
    {
        return this.compression.Compress(data);
    }
}

// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
    private encryption : Encryption;

    public Process(data : any) : any
    {
        return this.encryption.Encrypt(data);
    }
}

// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
    private compression : Compression;
    private encryption : Encryption;

    public Process(data : any) : any
    {
        return this.encryption.Encrypt(
            this.compression.Compress(data)
        );
    }
}

これを行うと、最終的に4つのクラスになり、それぞれがまったく異なることを行いますが、それぞれが同じパブリックAPIを提供します。Process方法。

none / encryption / compression / bothの決定を扱うビジネスロジックでは、DataProcessing以前に設計したインターフェイスに依存するようにオブジェクトを設計します。

class DataService
{
    private dataProcessing : DataProcessing;

    public DataService(dataProcessing : DataProcessing)
    {
        this.dataProcessing = dataProcessing;
    }
}

プロセス自体は次のように簡単になります。

public ComplicatedProcess(data : any) : any
{
    data = this.dataProcessing.Process(data);

    // ... perhaps work with the data

    return data;
}

条件なし。クラスDataServiceは、データがdataProcessingメンバーに渡されたときにデータで実際に何が行われるのか分かりません。また、それは実際には気にせず、その責任ではありません。

理想的には、作成した4つのアダプタークラスをテストする単体テストを実行して、それらが機能することを確認し、テストに合格するようにします。そして、それらが合格すれば、コードのどこで呼び出しても、確実に機能することを確信できます。

このようにifしてやると、コードにsが含まれなくなりますか?

いいえ。ビジネスロジックに条件が含まれる可能性は低くなりますが、それらはどこかにある必要があります。場所はあなたの工場です。

そしてこれは良いことです。作成の懸念と実際にコードを使用することを分離します。工場の信頼性を高めれば(Javaでは、Google のGuiceフレームワークのようなものまで使用できます)、ビジネスロジックでは、注入する適切なクラスを選択することを心配しません。あなたの工場が機能し、求められているものを提供することを知っているからです。

これらすべてのクラス、インターフェースなどが必要ですか?

これにより、最初に戻ります。

OOPで、ポリモーフィズムを使用するパスを選択する場合、実際にデザインパターンを使用する場合、言語の機能を活用する場合、および/またはオブジェクトイデオロギーであるすべてをフォローする場合は、そうです。その場合でも、この例では必要なすべてのファクトリーも示していないためCompressionEncryptionクラスとクラスをリファクタリングし、代わりにインターフェースを作成する場合は、実装も含める必要があります。

最終的には、非常に具体的なことに焦点を合わせた何百もの小さなクラスとインターフェースになります。これは必ずしも悪いことではありませんが、2つの数字を追加するだけの簡単なことをしたいだけであれば、最善の解決策ではないかもしれません。

あなたがそれをすぐに終わらせたいなら、Ixrecのソリューションをつかむことができます。彼は少なくともブロックelse ifelseブロックを削除することができました。私の意見では、それは平野よりも少し悪いですif

これが私の優れたオブジェクト指向設計の方法です。実装ではなくインターフェースにコーディングすることは、過去数年にわたってこれを行ってきた方法であり、私が最も満足しているアプローチです。

私は個人的にif-lessプログラミングが好きであり、5行のコードでより長いソリューションを評価したいと思っています。それは私がコードを設計するのに慣れている方法であり、それを非常に快適に読んでいます。


更新2:私のソリューションの最初のバージョンについては激しい議論がありました。議論の大部分は私が原因で、謝罪します。

私は答えを編集することにしました。それは解決策を見る方法の1つであり、唯一の方法ではないということです。また、代わりにファサードを意味するデコレータ部分も削除しましたが、アダプターはファサードのバリエーションであるため、最終的に完全に除外することにしました。


28
私はダウン投票しませんでしたが、その理論的根拠は、元のコードが8行で行ったこと(および5で他の答えがしたこと)を行うためのばかげた量の新しいクラス/インターフェースかもしれません。私の意見では、それが達成する唯一のことは、コードの学習曲線を増やすことです。
モーリシー

6
@Maurycy OPが求めたのは、一般的な設計パターンを使用して、そのような解決策が存在する場合、彼の問題の解決策を見つけようとすることでした。私のソリューションは彼またはIxrecのコードよりも長いですか?そうです。私はそれを認めます。私のソリューションは、デザインパターンを使用して彼の問題を解決し、彼の質問に答え、プロセスから必要なifをすべて効果的に削除しますか?します。Ixrecにはありません。
アンディ

26
明確で、信頼性があり、簡潔で、パフォーマンスがあり、保守可能コードを書くことが道だと思います。誰かがSOLIDを引用したり、ソフトウェアのパターンを引用したりするたびに、目標と理論的根拠を明確に述べずに1ドルもらえたら、私は金持ちになります。
ロバートハーベイ

12
ここで私は2つの問題を抱えていると思います。1つ目は、CompressionとのEncryptionインターフェイスがまったく不要だということです。それらが装飾プロセスに何らかの形で必要であることを提案しているのか、それとも抽出された概念を表していることを単に示唆しているのかはわかりません。2番目の問題は、クラスを次のようCompressionEncryptionDecoratorにすると、OPの条件と同じ種類の組み合わせ爆発につながることです。また、提案されたコードではデコレータパターンが十分に明確に表示されていません。
cbojar

5
SOLIDとSimpleの議論では、ちょっとポイントが欠けています。このコードはどちらでもなく、デコレータパターンも使用していません。多数のインターフェイスを使用しているという理由だけで、コードは自動的にSOLIDではありません。DataProcessingインターフェースの依存性注入はちょっといいです。それ以外はすべて不要です。SOLIDは、変更を適切に処理することを目的としたアーキテクチャレベルの関心事です。OPは、彼のアーキテクチャに関する情報も、コードの変更方法についても情報を提供しなかったため、回答でSOLIDについて実際に議論することすらできません。
カールレス

120

現在のコードで見られる唯一の問題は、設定を追加すると組み合わせが爆発するリスクです。これは、コードを次のように構造化することで簡単に軽減できます。

if(compressEnable){
  data = compress(data);
}
if(encryptionEnable) {
  data = encrypt(data);
}
return data;

私は、これが例と見なされる「デザインパターン」や「イディオム」を知りません。


18
@DamithGanegodaいいえ、私のコードを注意深く読むと、その場合はまったく同じことを行うことがわかります。だからこそelse、2つのifステートメントの間に何もありませんし、data毎回割り当てているのです。両方のフラグがtrueの場合、compress()が実行され、次にcompress()の結果に対してencrypt()が実行されます(希望どおり)。
Ixrec

14
@DavidPacker技術的には、すべてのプログラミング言語のifステートメントも同様です。これは非常に単純な答えが適切な問題のように見えたので、私は単純さを求めました。あなたの解決策も有効ですが、個人的には、2つ以上のブール値フラグを心配する場合に備えて保存しておきます。
Ixrec

15
@DavidPacker:正しいことは、コードがプログラミングのイデオロギーについての著者によるガイドラインに従っているかどうかによって定義されません。正しいのは、「コードは本来行うべきことを行い、妥当な時間内に実装された」ということです。「間違った方法」で行うのが理にかなっている場合、時は金なりなので間違った方法は正しい方法です。
-whatsisname

9
@DavidPacker:OPの立場で、その質問をした場合、OrbitのコメントのLightness Raceは本当に必要なものです。「デザインパターンを使用したソリューションの検索」は、すでに間違った方向から始まっています。
-whatsisname

6
@DavidPacker実際、質問をより詳しく読むと、パターンを主張しません。それは、述べて、「それは正しい選択です。私はDecoratorパターンを考えている、または、おそらくより良い代替手段がありますか?」。あなたは私の引用の最初の文を取り上げましたが、2番目の文は取り上げませんでした。他の人は、いや、それは正しい選択ではないというアプローチをとりました。その場合、あなただけが質問に答えていると主張することはできません。
ジョンベントレー

12

あなたの質問は実用性ではなく、lxrecの答えが正しいものであるように見えますが、設計パターンについて学ぶことです。

明らかに、コマンドパターンは、あなたが提案するような些細な問題に対する過剰なものですが、ここでは説明のために行きます:

public interface Command {
    public String transform(String s);
}

public class CompressCommand implements Command {
    @Override
    public String transform(String s) {
        String compressedString=null;
        //Compression code here
        return compressedString;
    }
}

public class EncryptCommand implements Command {
    @Override
    public String transform(String s) {
        String EncrytedString=null;
        // Encryption code goes here
        return null;
    }

}

public class Test {
    public static void main(String[] args) {
        List<Command> commands = new ArrayList<Command>();
        commands.add(new CompressCommand());
        commands.add(new EncryptCommand()); 
        String myString="Test String";
        for (Command c: commands){
            myString = c.transform(myString);
        }
        // now myString can be stored in the database
    }
}

ご覧のように、コマンド/変換をリストに入れると、順番に実行することができます。明らかにそれは両方を実行するか、if条件なしでリストに入れたものに依存してそれらの1つだけを実行します。

明らかに、条件式は、コマンドリストをまとめる何らかの種類のファクトリーになります。

@texacreのコメントの編集:

ソリューションの作成部分のif条件を回避する方法はたくさんあります。たとえば、デスクトップGUIアプリを見てみましょう。圧縮および暗号化オプションのチェックボックスを使用できます。on clicこれらのチェックボックスのイベントでは、対応するコマンドをインスタンス化してリストに追加するか、オプションの選択を解除している場合はリストから削除します。


基本的にIxrecの答えのように見えるコードなしで「コマンドリストをまとめるファクトリー」の例を提供できない限り、IMOは質問に答えません。これにより、圧縮および暗号化機能を実装するより良い方法が提供されますが、フラグを回避する方法は提供されません。
thexacre

@thexacre例を追加しました。
Tulainsコルドバ

あなたのチェックボックスイベントリスナーには「if checkbox.ticked then add command」がありますか?私はあなたがちょうど周りのステートメントifフラグをシャッフルしているように思えます...
-thexacre

@thexacreいいえ、チェックボックスごとに1つのリスナー。クリックイベントのみcommands.add(new EncryptCommand()); またはcommands.add(new CompressCommand());それぞれ。
Tulainsコルドバ

ボックスのチェックを外す処理はどうですか?私が遭遇したほぼすべての言語/ UIツールキットでは、イベントリスナーのチェックボックスの状態を確認する必要があります。私はこれがより良いパターンであることに同意しますが、フラグがどこかで何かをすれば基本的に必要になることを避けません。
thexacre

7

「デザインパターン」は不必要に「ooパターン」に向けられており、はるかに単純なアイデアを完全に避けていると思います。ここで話しているのは、(単純な)データパイプラインです。

私はclojureでそれをやろうとします。関数が第一級である他の言語もおそらく大丈夫です。あとでC#の例を作成できたかもしれませんが、あまり良くありません。これを解決するための私の方法は、非クロジュリア人のためのいくつかの説明を含む次のステップです。

1.一連の変換を表します。

(def transformations { :encrypt  (fn [data] ... ) 
                       :compress (fn [data] ... )})

これは、キーワードから関数へのマップ、つまりルックアップテーブル/辞書/なんでもです。別の例(文字列のキーワード):

(def employees { :A1 "Alice" 
                 :X9 "Bob"})

(employees :A1) ; => "Alice"
(:A1 employees) ; => "Alice"

したがって、暗号化関数を記述する(transformations :encrypt)か、(:encrypt transformations)返します。((fn [data] ... )単なるラムダ関数です。)

2.オプションを一連のキーワードとして取得します。

(defn do-processing [options data] ;function definition
  ...)

(do-processing [:encrypt :compress] data) ;call to function

3.提供されたオプションを使用して、すべての変換をフィルタリングします。

(let [ transformations-to-run (map transformations options)] ... )

例:

(map employees [:A1]) ; => ["Alice"]
(map employees [:A1 :X9]) ; => ["Alice", "Bob"]

4.関数を1つに結合します。

(apply comp transformations-to-run)

例:

(comp f g h) ;=> f(g(h()))
(apply comp [f g h]) ;=> f(g(h()))

5.そして、一緒に:

(def transformations { :encrypt  (fn [data] ... ) 
                       :compress (fn [data] ... )})

(defn do-processing [options data]
  (let [transformations-to-run (map transformations options)
        selected-transformations (apply comp transformations-to-run)] 
    (selected-transformations data)))

(do-processing [:encrypt :compress])

「debug-print」などの新しい関数を追加する場合にのみ変更されるのは次のとおりです。

(def transformations { :encrypt  (fn [data] ... ) 
                       :compress (fn [data] ... )
                       :debug-print (fn [data] ...) }) ;<--- here to add as option

(defn do-processing [options data]
  (let [transformations-to-run (map transformations options)
        selected-transformations (apply comp transformations-to-run)] 
    (selected-transformations data)))

(do-processing [:encrypt :compress :debug-print]) ;<-- here to use it
(do-processing [:compress :debug-print]) ;or like this
(do-processing [:encrypt]) ;or like this

何らかの方法で一連のifステートメントを本質的に使用せずに適用する必要がある関数のみを含めるために、funcsはどのように取り込まれますか?
thexacre

funcs-to-run-here (map options funcs)はフィルタリングを実行しているため、適用する一連の関数を選択しています。答えを更新して、もう少し詳しく説明する必要があるかもしれません。
NiklasJ

5

[基本的に、私の回答は、上記の@Ixrecによる回答の続きです。]

重要な質問:カバーする必要のある明確な組み合わせの数は増えますか?あなたの主題ドメインをよく知っている。これはあなたの判断です。
バリアントの数が増える可能性はありますか?まあ、それは考えられないことではありません。たとえば、より多くの異なる暗号化アルゴリズムに対応する必要がある場合があります。

個別の組み合わせの数が増えると予想される場合は、戦略パターンが役立ちます。アルゴリズムをカプセル化し、呼び出し元のコードに交換可能なインターフェイスを提供するように設計されています。特定の文字列ごとに適切な戦略を作成(インスタンス化)するとき、まだ少量のロジックがあります。

上記で、要件が変更されることはないとコメントしています。バリアントの数が増えると思わない場合(またはこのリファクタリングを延期できる場合)、ロジックをそのままにしてください。現在、小規模で管理可能な量のロジックがあります。(おそらく、戦略パターンへのリファクタリングの可能性についてのコメントに自分自身にメモを入れてください。)


1

scalaでこれを行う1つの方法は次のとおりです。

val handleCompression: AnyRef => AnyRef = data => if (compressEnable) compress(data) else data
val handleEncryption: AnyRef => AnyRef = data => if (encryptionEnable) encrypt(data) else data
val handleData = handleCompression andThen handleEncryption
handleData(data)

デコレータパターンを使用して上記の目標(個々の処理ロジックの分離とそれらのワイヤリング方法)を達成するのは冗長すぎるでしょう。

OOプログラミングパラダイムでこれらの設計目標を達成するために設計パターンが必要な場合、関数型言語は関数をファーストクラスの市民(コードの1行目と2行目)および機能構成(3行目)として使用することでネイティブサポートを提供します


これがOPのアプローチよりも優れている(または悪い)のはなぜですか?そして/または、デコレータパターンを使用するというOPのアイデアについてどう思いますか?
キャスパーヴァンデンバーグ

このコードスニペットは優れており、順序(暗号化前の圧縮)について明示的です。不要なインターフェイスを回避
ラグ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.