クラス内のすべてのメソッドを特定のコードブロックで開始するエレガントな方法はありますか?


143

すべてのメソッドが同じように開始するクラスがあります:

class Foo {
  public void bar() {
    if (!fooIsEnabled) return;
    //...
  }
  public void baz() {
    if (!fooIsEnabled) return;
    //...
  }
  public void bat() {
    if (!fooIsEnabled) return;
    //...
  }
}

fooIsEnabledクラスのすべてのパブリックメソッドの部分を要求する(そして、できれば毎回書き込むのではない)良い方法はありますか?


45
アスペクト指向プログラミングを調べてください(特にアドバイスの前に)。
Sotirios Delimanolis 2015年

15
このボイラープレートを使用する方法はいくつありますか?AOPを導入する前に、このコードを少しだけ繰り返すことを検討してください。場合によっては、少しだけコピーして貼り付けることが最も簡単な解決策です。
bhspencer

51
あなたの将来のメンテナーは、AOPフレームワークを学ぶ必要があるよりも、余分なボイラープレートに満足していると思います。
bhspencer 2015年

7
クラスのすべてのメソッドがコードの最初の行でまったく同じことを行う必要がある場合、設計が悪いことになります。
TulainsCórdova15年

7
@ user1598390:質問はここでは主題外ではなく、質問を特に意味のあるものにするプログラマのスコープについては何もありません。
Robert Harvey

回答:


90

私はエレガントについて知っているが、ここで使用して働いて実装されていないJavaの組み込みのjava.lang.reflect.Proxyその施行上のすべてのメソッド呼び出しがあることFooをチェックすることから始めるenabledの状態を。

main 方法:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

Foo インターフェース:

public interface Foo {
    boolean getEnabled();
    void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory クラス:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {
        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class &&
                !method.getName().equals("getEnabled") &&
                !method.getName().equals("setEnabled")) {

                if (!this.fooImpl.getEnabled()) {
                    return null;
                }
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

他の人が指摘したように、心配するメソッドがほんの少ししかない場合、必要なものに対してはやり過ぎのように思えます。

そうは言っても、確かに利点があります:

  • Fooのメソッド実装では、enabledチェックの分野横断的な問題を心配する必要がないため、問題の特定の分離が実現されます。代わりに、メソッドのコードは、メソッドの主な目的が何であるかを気にするだけでよく、それ以上は必要ありません。
  • 罪のない開発者がFooクラスに新しいメソッドを追加して、誤ってenabledチェックを「忘れ」てしまう方法はありません。enabledチェックの動作は自動的に新しく追加された方法によって継承されます。
  • 別の分野横断的な懸念事項を追加する必要がある場合、またはenabledチェックを強化する必要がある場合は、安全かつ1か所で簡単に実行できます。
  • 組み込みのJava機能を使用して、このAOPのような動作を実現できるのは、ちょっといいことです。のような他のフレームワークを統合する必要はありませんがSpring、これらも間違いなく優れたオプションです。

公平に言うと、いくつかの欠点があります。

  • プロキシの呼び出しを処理する実装コードの一部は醜いです。クラスのインスタンス化を防ぐために内部クラスを持つことFooImplは醜いと言う人もいます。
  • 新しいメソッドをに追加するFoo場合は、実装クラスとインターフェースの2つの場所を変更する必要があります。大したことではありませんが、それでもまだ少し作業が必要です。
  • プロキシ呼び出しは無料ではありません。特定のパフォーマンスのオーバーヘッドがあります。ただし、一般的な使用では目立ちません。詳細については、こちらをご覧ください。

編集:

Fabian Streitelのコメントにより、私は上記の解決策について2つの問題について考えるようになりました。それは認めますが、自分自身については不満です。

  1. 呼び出しハンドラは、マジックストリングを使用して、「getEnabled」および「setEnabled」メソッドの「enabled-check」をスキップします。メソッド名がリファクタリングされている場合、これは簡単に壊れる可能性があります。
  2. 「有効化されたチェック」の動作を継承しない新しいメソッドを追加する必要がある場合、開発者がこれを誤解するのは非常に簡単で、少なくとも、魔法を追加することになります。文字列。

ポイント#1を解決し、ポイント#2の問題を少なくとも緩和するために、「」を実行したくないインターフェイスBypassCheckのメソッドをマークするために使用できる注釈(または同様の何か)を作成しますFoo有効なチェック」。この方法では、マジックストリングはまったく必要ありません。この特殊なケースでは、開発者が新しいメソッドを正しく追加することがはるかに簡単になります。

注釈ソリューションを使用すると、コードは次のようになります。

main 方法:

public static void main(String[] args) {
    Foo foo = Foo.newFoo();
    foo.setEnabled(false);
    foo.bar(); // won't print anything.
    foo.setEnabled(true);
    foo.bar(); // prints "Executing method bar"
}

BypassCheck 注釈:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}

Foo インターフェース:

public interface Foo {
    @BypassCheck boolean getEnabled();
    @BypassCheck void setEnabled(boolean enable);

    void bar();
    void baz();
    void bat();

    // Needs Java 8 to have this convenience method here.
    static Foo newFoo() {
        FooFactory fooFactory = new FooFactory();
        return fooFactory.makeFoo();
    }
}

FooFactory クラス:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class FooFactory {

    public Foo makeFoo() {
        return (Foo) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                new Class[]{Foo.class},
                new FooInvocationHandler(new FooImpl()));
    }

    private static class FooImpl implements Foo {

        private boolean enabled = false;

        @Override
        public boolean getEnabled() {
            return this.enabled;
        }

        @Override
        public void setEnabled(boolean enable) {
            this.enabled = enable;
        }

        @Override
        public void bar() {
            System.out.println("Executing method bar");
        }

        @Override
        public void baz() {
            System.out.println("Executing method baz");
        }

        @Override
        public void bat() {
            System.out.println("Executing method bat");
        }

    }

    private static class FooInvocationHandler implements InvocationHandler {

        private FooImpl fooImpl;

        public FooInvocationHandler(FooImpl fooImpl) {
            this.fooImpl = fooImpl;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Foo.class
                    && !method.isAnnotationPresent(BypassCheck.class) // no magic strings
                    && !this.fooImpl.getEnabled()) {

                return null;
            }

            return method.invoke(this.fooImpl, args);
        }
    }
}

11
これは賢い解決策だと思いますが、本当にこれを使用しますか?
bhspencer 2015年

1
良い回避策です。動的プロキシパターンを使用して、各メソッドの最初にあるこの一般的な動作でオブジェクトを装飾します。
ビクター

11
@bhspencer:非常に正当な質問です。私は実際に何度も例外処理、ロギング、トランザクション処理などを行うためにそれを使用しました。小さなクラスの場合、それはやり過ぎのように思われ、非常によくあるかもしれません。しかし、クラスが非常に複雑になると予想し、その際にすべてのメソッドで一貫した動作を保証したい場合は、このソリューションを気にしません。
スタン、2015年

1
ここでは悪の97%の一部ではありませんが、このプロキシクラスのパフォーマンスにはどのような影響がありますか?
corsiKa 2015年

5
@corsiKa:いい質問ですね。動的プロキシの使用は、直接のメソッド呼び出しよりも遅いことは間違いありません。ただし、一般的な用途では、パフォーマンスのオーバーヘッドはごくわずかです。関連するSOスレッド(興味がある場合):Java動的プロキシのパフォーマンスコスト
sstan 2015年

51

良い提案がたくさんあります。問題を解決するためにできることは、状態パターンで考え、それを実装することです。

このコードスニペットを見てみましょう。たぶん、それがアイデアにつながるでしょう。このシナリオでは、オブジェクトの内部状態に基づいてメソッド実装全体を変更したいようです。オブジェクト内のメソッドの合計は動作として認識されることを思い出してください。

public class Foo {

      private FooBehaviour currentBehaviour = new FooEnabledBehaviour (); // or disabled, or use a static factory method for getting the default behaviour

      public void bar() {
        currentBehaviour.bar();
      }
      public void baz() {
        currentBehaviour.baz();
      }
      public void bat() {
        currentBehaviour.bat();
      }

      public void setFooEnabled (boolean fooEnabled) { // when you set fooEnabel, you are changing at runtime what implementation will be called.
        if (fooEnabled) {
          currentBehaviour = new FooEnabledBehaviour ();
        } else {
          currentBehaviour = new FooDisabledBehaviour ();
        }
      }

      private interface FooBehaviour {
        public void bar();
        public void baz();
        public void bat();
      }

      // RENEMBER THAT instance method of inner classes can refer directly to instance members defined in its enclosing class
      private class FooEnabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is enabled
        }
        public void baz() {}
        public void bat() {}

      }

      private class FooDisabledBehaviour implements FooBehaviour {
        public void bar() {
          // do what you want... when is desibled
        }
        public void baz() {}
        public void bat() {}

      }
}

あなたがそれを好き願っています!

PD:状態パターンの実装です(コンテキストによっては戦略とも呼ばれます。ただし、原則は同じです)。


1
OPは、すべてのメソッドの最初に同じコード行を繰り返す必要がないことを望んでおり、ソリューションには、すべてのメソッドの最初に同じコード行を繰り返すことが含まれます。
TulainsCórdova15年

2
@ user1598390評価を繰り返す必要はありません。FooEnabledBehaviourの内部では、このオブジェクトのクライアントがfooEnabledをtrueに設定していると想定しているため、チェッキングする必要はありません。FooDisabledBehaviourクラスも同様です。もう一度確認して、コードを記述してください。
ビクター

2
@ bayou.ioに感謝します。OPの答えが出るまで待ちましょう。コミュニティはここで地獄のような仕事をしていると思います、ここにはたくさんの良いヒントがあります!
ビクター

2
@dyesdyesに同意する、これを本当に簡単なクラス以外に実装することは想像できません。bar()in FooEnabledBehaviorbar()in FooDisabledBehaviorが同じコードの多くを共有する可能性があることを考えると、問題が多すぎます。おそらく、2つの行が1行でも異なる可能性があります。非常に簡単に、特にこのコードが(私などの)ジュニア開発者によって保守されている場合、保守およびテストができない巨大ながらくたになる可能性があります。これはどのコードでも発生する可能性がありますが、これは非常に速く失敗するのでとても簡単に思えます。+1ですが、良い提案です。
Chris Cirefice 2015

1
うーん...私はみんなではない..しかし、最初に、コメントをありがとう。私にとって、コードのサイズは、「クリーン」で「読み取り可能」であれば問題ありません。私の好意では、外部クラスを使用していないことを主張する必要があります。そして、いくつかの一般的な動作がある場合は、それをCommonBehaviourClassにカプセル化し、必要な場所に委任しましょう。GOFの本(私のお気に入りの本ではありませんが、優れたレシピがあります)で、この例を見つけました:en.wikipedia.org/wiki/…。多かれ少なかれ私がここでやっていることと同じです。
ビクター

14

はい、しかしそれは少しの仕事なので、それがあなたにとってどれほど重要であるかによります。

クラスをインターフェイスとして定義し、デリゲート実装を記述java.lang.reflect.Proxyしてから、共有部分を実行するメソッドを使用してインターフェイスを実装し、条件付きでデリゲートを呼び出すことができます。

interface Foo {
    public void bar();
    public void baz();
    public void bat();
}

class FooImpl implements Foo {
    public void bar() {
      //... <-- your logic represented by this notation above
    }

    public void baz() {
      //... <-- your logic represented by this notation above
    }

    // and so forth
}

Foo underlying = new FooImpl();
InvocationHandler handler = new MyInvocationHandler(underlying);
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
     new Class[] { Foo.class },
     handler);

あなたMyInvocationHandlerはこのようなものにすることができます(fooIsEnabledアクセス可能な場所で定義されていると仮定して、エラー処理とクラスの足場は省略されます):

public Object invoke(Object proxy, Method method, Object[] args) {
    if (!fooIsEnabled) return null;
    return method.invoke(underlying, args);
}

それは信じられないほどきれいではありません。しかし、さまざまなコメンテーターとは異なり、繰り返しはこの種の密度よりも重要なリスクであると私は考えています。このやや不可解なラッパーを追加することで、実際のクラスの「感触」を生み出すことができます。ほんの数行のコードで非常にローカルに。

動的プロキシクラスの詳細については、Javaのドキュメントを参照してください。


14

この質問は、アスペクト指向プログラミングに密接に関連しています。AspectJはJavaのAOP拡張であり、いくつかのインスピレーションを得るために外観を与えることができます。

私の知る限り、JavaでのAOPの直接サポートはありません。たとえば、それに関連するいくつかのGOFパターンがあります。テンプレートメソッド戦略実際にはコード行を節約できません。

Javaや他のほとんどの言語では、関数に必要な反復ロジックを定義し、それらを適切なタイミングで呼び出す、いわゆる規律のあるコーディングアプローチを採用できます。

public void checkBalance() {
    checkSomePrecondition();
    ...
    checkSomePostcondition();
}

ただし、分解されたコードがから戻ることができるようにしたいため、これはケースに適合しませんcheckBalance。マクロをサポートする言語(C / C ++など)では、次のように定義できます。checkSomePreconditioncheckSomePostconditionなどのマクロをコンパイラがさえ呼び出される前に、彼らは単にプリプロセッサに置き換えられます。

#define checkSomePrecondition \
    if (!fooIsEnabled) return;

Javaには、これはそのままではありません。これは誰かを怒らせるかもしれませんが、私は以前に自動コード生成とテンプレートエンジンを使用して繰り返しコーディングタスクを自動化しました。Jinja2などの適切なプリプロセッサでコンパイルする前にJavaファイルを処理すると、Cで可能なことと同様のことができます。

可能な純粋なJavaアプローチ

純粋なJavaソリューションを探している場合、見つかる可能性があるのはおそらく簡潔ではありません。ただし、プログラムの共通部分を除外して、コードの重複やバグを回避することはできます。あなたはこのようなことをすることができます(それはある種の戦略に触発されたパターンです)。C#とJava 8、および関数の処理が少し簡単な他の言語では、このアプローチは実際には見栄えがよいことに注意してください。

public interface Code {
    void execute();
}

...

public class Foo {
  private bool fooIsEnabled;

  private void protect(Code c) {
      if (!fooIsEnabled) return;
      c.execute();
  }

  public void bar() {
    protect(new Code {
      public void execute() {
        System.out.println("bar");
      }
    });
  }

  public void baz() {
    protect(new Code {
      public void execute() {
        System.out.println("baz");
      }
    });
  }

  public void bat() {
    protect(new Code {
      public void execute() {
        System.out.println("bat");
      }
    });
  }
}

実世界のシナリオのちょっと

データフレームを産業用ロボットに送信するクラスを開発しています。ロボットはコマンドを完了するのに時間がかかります。コマンドが完了すると、制御フレームが返されます。ロボットがまだ実行されている間に新しいコマンドを受信すると、ロボットが損傷する可能性があります。プログラムはDataLinkクラスを使用して、ロボットとの間でフレームを送受信します。DataLinkインスタンスへのアクセスを保護する必要があります。

ユーザー・インターフェース・スレッドの呼び出しRobotController.leftrightupまたはdownユーザーがボタンをクリックするだけでなく、呼び出したときにBaseController.tickプライベートに再度有効にコマンドを転送するためには、一定の間隔でDataLinkインスタンス。

interface Code {
    void ready(DataLink dataLink);
}

class BaseController {
    private DataLink mDataLink;
    private boolean mReady = false;
    private Queue<Code> mEnqueued = new LinkedList<Code>();

    public BaseController(DataLink dl) {
        mDataLink = dl;
    }

    protected void protect(Code c) {
        if (mReady) {
            mReady = false;
            c.ready(mDataLink);
        }
        else {
            mEnqueue.add(c);
        }
    }

    public void tick() {
        byte[] frame = mDataLink.readWithTimeout(/* Not more than 50 ms */);

        if (frame != null && /* Check that it's an ACK frame */) {
          if (mEnqueued.isEmpty()) {
              mReady = true;
          }
          else {
              Code c = mEnqueued.remove();
              c.ready(mDataLink);
          }
        }
    }
}

class RobotController extends BaseController {
    public void left(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'left' by amount */);
        }});
    }

    public void right(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'right' by amount */);
        }});
    }

    public void up(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'up' by amount */);
        }});
    }

    public void down(float amount) {
        protect(new Code() { public void ready(DataLink dataLink) {
            dataLink.write(/* Create a byte[] that means 'down' by amount */);
        }});
    }
}

4
これは単に缶をキックするだけではありません。つまり、将来のメンテナは(!fooIsEnabled)が戻るかどうかを覚えておく必要があったということです。すべての機能の開始時、そして今、保護することを覚えておく必要があります(新しいコード{...すべての機能の開始時。これはどのように役立ちますか?
bhspencer

私はあなたの分析とバックグラウンドコンテキストdamix911が好きです...コードが時間の経過とともに変化しないと仮定して、コンパイル時に新しいプライベートコードインスタンスをビルドし(プライベート静的メンバーを使用)、変更の名前を「executeIf」に変更し、引数として条件を渡します(述語クラスのように)とコード。しかし、それはより個人的な伐採と味です。
ビクター

1
@bhspencerこれはJavaではやや不格好に見えますが、ほとんどの場合、この戦略は実際には他の単純なコードのオーバーエンジニアリングです。多くのプログラムはそのようなパターンから利益を得ることができません。ただしprotect、再利用や文書化が簡単な新しいシンボルを作成したのは素晴らしいことです。重要なコードをで保護する必要があることを将来のメンテナに伝える場合protect、すでに彼に何をすべきかを伝えています。保護ルールが変更されても、新しいコードは引き続き保護されます。これは関数を定義する背後にある理論的根拠ですが、OPは関数がreturn実行できない「返す」必要がありました。
damix911 2015

11

リファクタリングを検討します。このパターンは、DRYパターンを大きく壊しています(繰り返してはいけません)。これがこの階級の責任を破ると私は信じています。しかし、これはコードの制御に依存します。あなたの質問は非常にオープンです-どこでFooインスタンスを呼び出していますか?

私はあなたが次のようなコードを持っていると思います

foo.bar(); // does nothing if !fooEnabled
foo.baz(); // does also nothing
foo.bat(); // also

多分あなたはそれをこのように呼ぶべきです:

if (fooEnabled) {
   foo.bat();
   foo.baz();
   ...
}

そしてそれをきれいに保ちなさい。たとえば、ロギング:

this.logger.debug(createResourceExpensiveDump())

デバッグが有効になっている場合、a logger は自分自身質問していません。記録するだけです。

代わりに、呼び出しクラスはこれをチェックする必要があります:

if (this.logger.isDebugEnabled()) {
   this.logger.debug(createResourceExpensiveDump())
}

これがライブラリであり、このクラスの呼び出しを制御できないIllegalStateException場合、この呼び出しが違法で問題が発生する場合は、理由を説明するをスローします。


6
それは間違いなくシンプルで目に優しいです。しかし、OPの目標が、新しいメソッドが追加されたときに、有効化されたロジックがバイパスされないことを確認することである場合、このリファクタリングはそれを実施することを容易にしません。
スタン、2015年

4
また、ログの例では、これには多くの繰り返しが含まれると思います。ログを記録するたびに、ロガーが有効になっていることを確認する必要があります。どのクラスのメソッド数よりも多くの行をログに記録する傾向があります...
T. Kiley

4
これはモジュール性を壊します。なぜなら、呼び出し側はfooの内部について何かを知っている必要があるからです(この場合はfooEnabledかどうか)。これは、ルールが競合するため、ベストプラクティスルールに従っても問題を解決できない典型的な例です。(私はまだ誰かが「なぜそれを考えなかったのか?」と答えるだろうと期待しています)
Ian Goldby

2
まあ、それが理にかなっているかどうかに関しては、コンテキストに大きく依存します。
Ian Goldby

3
ロギングはまさにその例であり、コードで繰り返しを必要としない場合です。LOG.debug( "....");と書きたいだけです。-そして、ロガーは本当にデバッグしたいかどうかをチェックするべきです。-別の例は、クローズ/クリーンアップです。-AutoClosableを使用している場合、既に閉じているので例外は必要ありませんが、何もしません。
Falco、2015

6

これに対する最もエレガントで最もパフォーマンスの高いソリューションである私見は、Fooの複数の実装と、それを作成するためのファクトリメソッドを組み合わせることです。

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : new NopFoo();
  }
}

class NopFoo extends Foo {
  public void bar() {
    // Do nothing
  }
}

またはバリエーション:

class Foo {
  protected Foo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }

  public static void getFoo() {
    return fooEnabled ? new Foo() : NOP_FOO;
  }

  private static Foo NOP_FOO = new Foo() {
    public void bar() {
      // Do nothing
    }
  };
}

スタンが指摘するように、インターフェースを使用するのがさらに良いでしょう:

public interface Foo {
  void bar();

  static Foo getFoo() {
    return fooEnabled ? new FooImpl() : new NopFoo();
  }
}

class FooImpl implements Foo {
  FooImpl() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do something
  }
}

class NopFoo implements Foo {
  NopFoo() {
    // Prevent direct instantiation
  }

  public void bar() {
    // Do nothing
  }
}

これを残りの状況に適応させます(毎回新しいFooを作成するか、同じインスタンスを再利用するかなど)。


1
これはコンラートの答えと同じではありません。私はこれが好きですが、クラス継承を使用する代わりに、他の人が答えで示唆しているようにインターフェイスを使用した方が安全だと思います。その理由は単純です。開発者がにメソッドを追加することは簡単Fooであり、拡張クラスにメソッドのno-opバージョンを追加することを忘れているため、望ましい動作がバイパスされます。
スタン、2015年

2
@sstanそうですね。気が散らないようにクリスティーナの元の例をできるだけ変更しないようにこのようにしましたが、これは重要です。私の答えにあなたの提案を追加します。
Pepijn Schmitz 2015

1
ポイントを逃したと思います。isFooEnabledのインスタンス化時かどうかを決定しますFoo。早すぎる。元のコードでは、これはメソッドが実行されるときに行われます。isFooEnabledその間、の値は変わる可能性があります。
Nicolas Barbulesco 2015

1
@NicolasBarbulesco fooIsEnabledが定数である可能性があることはわかりません。または、効果的に一定にする方法で使用することもできます。または、いくつかの明確に定義された場所に設定して、毎回Fooの新しいインスタンスを簡単に取得できるようにすることもできます。または、使用されるたびにFooの新しいインスタンスを取得することもできます。あなたが知らない、それが私が書いた理由です:「これをあなたの状況の残りに適応させなさい」。
Pepijn Schmitz、2015

@PepijnSchmitz-もちろん、一定のfooIsEnabled 場合あります。しかし、それが一定であることを何も教えてくれません。一般的なケースを考えます。
Nicolas Barbulesco、2015

5

私には別のアプローチがあります:

interface Foo {
  public void bar();
  public void baz();
  public void bat();
}

class FooImpl implements Foo {
  public void bar() {
    //...
  }
  public void baz() {
    //...
  }
  public void bat() {
    //...
  }
}

class NullFoo implements Foo {
  static NullFoo DEFAULT = new NullFoo();
  public void bar() {}
  public void baz() {}
  public void bat() {}
}

}

そしてあなたはできる

(isFooEnabled ? foo : NullFoo.DEFAULT).bar();

たぶん、を使用するかを保持isFooEnabledするFoo変数に置き換えることもできます。その後、呼び出しは再び簡単になります。FooImplNullFoo.DEFAULT

Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();

ところで、これは「ヌルパターン」と呼ばれています。


全体的なアプローチは良いですが、式を使用するの(isFooEnabled ? foo : NullFoo.DEFAULT).bar();は少し不格好です。既存の実装の1つに委任する3番目の実装を用意します。フィールドの値を変更する代わりにisFooEnabled、委任のターゲットを変更できます。これにより、コード内の分岐の数が減ります
SpaceTrucker

1
しかし、あなたはFoo呼び出しコードでクラスの内部クッキングをデポートしています!どのようにして私たちはどうisFooEnabledかを知ることができますか?これはクラスの内部フィールドFooです。
Nicolas Barbulesco、2015

3

@Colinの答えに対する同様の機能的アプローチでは、 Java 8のラムダ関数を使用して、条件付き機能のトグルの有効化/無効化コードexecuteIfEnabledを、アクションラムダを受け入れるガードメソッド()にラップすることができます。合格しました。

あなたのケースでは、このアプローチはコード行を保存しませんが、これをDRYすることにより、他の機能トグルの懸念に加えて、ロギング、診断、プロファイリングなどのAOPまたはデバッグの懸念を集中化するオプションがあります。

ここでラムダを使用する利点の1つは、executeIfEnabledメソッドをオーバーロードする必要を回避するためにクロージャーを使用できることです。

例えば:

class Foo {
    private Boolean _fooIsEnabled;

    public Foo(Boolean isEnabled) {
        _fooIsEnabled = isEnabled;
    }

    private void executeIfEnabled(java.util.function.Consumer someAction) {
        // Conditional toggle short circuit
        if (!_fooIsEnabled) return;

        // Invoke action
        someAction.accept(null);
    }

    // Wrap the conditionally executed code in a lambda
    public void bar() {
        executeIfEnabled((x) -> {
            System.out.println("Bar invoked");
        });
    }

    // Demo with closure arguments and locals
    public void baz(int y) {
        executeIfEnabled((x) -> {
            System.out.printf("Baz invoked %d \n", y);
        });
    }

    public void bat() {
        int z = 5;
        executeIfEnabled((x) -> {
            System.out.printf("Bat invoked %d \n", z);
        });
    }

テストあり:

public static void main(String args[]){
    Foo enabledFoo = new Foo(true);
    enabledFoo.bar();
    enabledFoo.baz(33);
    enabledFoo.bat();

    Foo disabledFoo = new Foo(false);
    disabledFoo.bar();
    disabledFoo.baz(66);
    disabledFoo.bat();
}

また、Damixのアプローチと同様ですが、メソッドをオーバーライドするインターフェースと匿名クラスの実装は必要ありません。
StuartLC 2015

2

他の回答で指摘されているように、戦略設計パターンは、このコードを簡略化するために従うべき適切な設計パターンです。ここでは、リフレクションによるメソッド呼び出しを使用してそれを説明しましたが、同じ効果を得るために使用できるメカニズムはいくつもあります。

class Foo {

  public static void main(String[] args) {
      Foo foo = new Foo();
      foo.fooIsEnabled = false;
      foo.execute("bar");
      foo.fooIsEnabled = true;
      foo.execute("baz");
  }

  boolean fooIsEnabled;

  public void execute(String method) {
    if(!fooIsEnabled) {return;}
    try {
       this.getClass().getDeclaredMethod(method, (Class<?>[])null).invoke(this, (Object[])null);
    }
    catch(Exception e) {
       // best to handle each exception type separately
       e.printStackTrace();
    }
  }

  // Changed methods to private to reinforce usage of execute method
  private void bar() {
    System.out.println("bar called");
    // bar stuff here...
  }
  private void baz() {
    System.out.println("baz called");
    // baz stuff here...
  }
  private void bat() {
    System.out.println("bat called");
    // bat stuff here...
  }
}

すでに述べたように、これを行うクラスがすでにある場合、リフレクションを処理する必要があることは少し厄介ですProxy
SpaceTrucker 2015年

どのようにできますかfoo.fooIsEnabled ...?先験的に、これはオブジェクトの内部フィールドであり、外部に表示することはできません。
Nicolas Barbulesco、2015

2

Javaだけが機能的に少し優れていた場合。ほとんどのOOOソリューションは、単一の関数をラップするクラスを作成して、fooが有効な場合にのみ呼び出されるようにすることです。

abstract class FunctionWrapper {
    Foo owner;

    public FunctionWrapper(Foo f){
        this.owner = f;
    }

    public final void call(){
        if (!owner.isEnabled()){
            return;
        }
        innerCall();
    }

    protected abstract void innerCall();
}

その後、実装barbazおよびbat匿名クラスとしてそれが延びていますFunctionWrapper

class Foo {
    public boolean fooIsEnabled;

    public boolean isEnabled(){
        return fooIsEnabled;
    }

    public final FunctionWrapper bar = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    public final FunctionWrapper baz = new FunctionWrapper(this){
        @Override
        protected void innerCall() {
            // do whatever
        }
    };

    // you can pass in parms like so 
    public final FunctionWrapper bat = new FunctionWrapper(this){
        // some parms:
        int x,y;
        // a way to set them
        public void setParms(int x,int y){
            this.x=x;
            this.y=y;
        }

        @Override
        protected void innerCall() {
            // do whatever using x and y
        }
    };
}

別のアイデア

使用glglglのNULL可能なソリューションが、メイクFooImplNullFooクラス以下の(プライベートコンストラクタで)内部クラス:

class FooGateKeeper {

    public boolean enabled;

    private Foo myFooImpl;
    private Foo myNullFoo;

    public FooGateKeeper(){
        myFooImpl= new FooImpl();
        myNullFoo= new NullFoo();
    }

    public Foo getFoo(){
        if (enabled){
            return myFooImpl;
        }
        return myNullFoo;
    }  
}

これにより、の使用を覚える必要がなくなります(isFooEnabled ? foo : NullFoo.DEFAULT)


あなたが持っていると言う:あなたがFoo foo = new Foo()電話をかけるbarと書くでしょうfoo.bar.call()
コリン

1

Fooが有効になっていない場合、クラスは何もしないようです。Fooインスタンスを作成または取得する上位レベルでこれを表現してみませんか?

class FooFactory
{
 static public Foo getFoo()
 {
   return isFooEnabled ? new Foo() : null;
 }
}
 ...
 Foo foo = FooFactory.getFoo();
 if(foo!=null)
 {
   foo.bar();
   ....
 }     

ただし、これはisFooEnabledが定数の場合にのみ機能します。一般的なケースでは、独自の注釈を作成できます。


コンラッド、あなたは注釈で開発できますか?
Nicolas Barbulesco、2015

元のコードfooIsEnabledは、メソッドがいつ呼び出されるかを決定します。これは、のインスタンス化の前に行いますFoo。早すぎる。その間、値は変わる可能性があります。
Nicolas Barbulesco 2015

ポイントを逃したと思います。アプリオリisFooEnabledは、Fooオブジェクトのインスタンスフィールドです。
Nicolas Barbulesco、2015

1

私はJava構文に慣れていません。Javaには、ポリモーフィズム、静的プロパティ、抽象クラスとメソッドがあると仮定します。

    public static void main(String[] args) {
    Foo.fooIsEnabled = true; // static property, not particular to a specific instance  

    Foo foo = new bar();
    foo.mainMethod();

    foo = new baz();
    foo.mainMethod();

    foo = new bat();
    foo.mainMethod();
}

    public abstract class Foo{
      static boolean fooIsEnabled;

      public void mainMethod()
      {
          if(!fooIsEnabled)
              return;

          baMethod();
      }     
      protected abstract void baMethod();
    }
    public class bar extends Foo {
        protected override baMethod()
        {
            // bar implementation
        }
    }
    public class bat extends Foo {
        protected override baMethod()
        {
            // bat implementation
        }
    }
    public class baz extends Foo {
        protected override baMethod()
        {
            // baz implementation
        }
    }

有効化はクラスの静的プロパティであると誰が言うのですか?
Nicolas Barbulesco、2015

どういうnew bar()意味ですか?
Nicolas Barbulesco、2015

JavaではName、大文字でクラスを記述します。
Nicolas Barbulesco、2015

これには、呼び出しコードを変更しすぎる必要があります。通常はメソッドを呼び出しますbar()それを変える必要があるなら、あなたは運命にあります。
Nicolas Barbulesco、2015

1

基本的には、フラグが設定されている場合、関数呼び出しをスキップする必要があります。だから私の解決策はばかげていると思いますが、ここではそれです。

Foo foo = new Foo();

if (foo.isEnabled())
{
    foo.doSomething();
}

関数を実行する前にコードを実行したい場合のために、簡単なプロキシの実装を以下に示します。

class Proxy<T>
{
    private T obj;
    private Method<T> proxy;

    Proxy(Method<T> proxy)
    {
        this.ojb = new T();
        this.proxy = proxy;
    }

    Proxy(T obj, Method<T> proxy)
    {
        this.obj = obj;
        this.proxy = proxy;
    }

    public T object ()
    {
        this.proxy(this.obj);
        return this.obj;
    }
}

class Test
{
    public static void func (Foo foo)
    {
        // ..
    }

    public static void main (String [] args)
    {
        Proxy<Foo> p = new Proxy(Test.func);

        // how to use
        p.object().doSomething();
    }
}

class Foo
{
    public void doSomething ()
    {
        // ..
    }
}

コードの最初のブロックでは、目に見えるメソッドが必要isEnabled()です。アプリオリに有効化されるのFoo、公開されていないの内部クッキングです。
Nicolas Barbulesco、2015

呼び出し元のコードではできない、としたくない、オブジェクトがされているかどうかを知っている有効
Nicolas Barbulesco 2015

0

デリゲート(関数へのポインター)を使用する別の解決策があります。最初に検証を行い、次に呼び出される関数(パラメーター)に応じて関連するメソッドを呼び出す独自のメソッドを持つことができます。C#コード:

internal delegate void InvokeBaxxxDelegate();

class Test
{
    private bool fooIsEnabled;

    public Test(bool fooIsEnabled)
    {
        this.fooIsEnabled = fooIsEnabled;
    }

    public void Bar()
    {
        InvokeBaxxx(InvokeBar);
    }

    public void Baz()
    {
        InvokeBaxxx(InvokeBaz);
    }

    public void Bat()
    {
        InvokeBaxxx(InvokeBat);
    }

    private void InvokeBaxxx(InvokeBaxxxDelegate invoker)
    {
        if (!fooIsEnabled) return;
        invoker();
    }

    private void InvokeBar()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bar");
    }

    private void InvokeBaz()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Baz");
    }

    private void InvokeBat()
    {
        // do Invoke bar stuff
        Console.WriteLine("I am Bat");
    }
}

2
正解です。Javaとマークされているので、Javaを知らないため、「C#のコード」を強調して書いたのはこのためです。それはデザインパターンの質問なので、言語は重要ではありません。
ehh

ああ!申し訳ありませんが、解決策を見つけるための支援のみを試みました。ありがとう
ehh 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.