すべてのメソッドが同じ方法で開始されるクラスがあります。
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
クラス内のすべてのパブリックメソッドにfooIsEnabled
部分を要求する(そして、毎回記述しないように)良い方法はありますか?
エレガントについては知りませんが、ここにJavaの組み込みJava.lang.reflect.Proxy
that-enforcesを使用して、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
checkのクロスカッティングの問題を心配する必要がないため、懸念の特定の分離が達成されます。代わりに、メソッドのコードは、メソッドの主な目的が何であるかを気にするだけでよく、それ以上何もしません。Foo
クラスに新しいメソッドを追加し、誤ってenabled
チェックを追加することを「忘れる」方法はありません。 enabled
checkの動作は、新しく追加されたメソッドによって自動的に継承されます。enabled
チェックを強化する必要がある場合は、安全かつ一箇所で簡単に行うことができます。Spring
のような他のフレームワークを統合する必要はありませんが、それらも間違いなく良いオプションです。公平を期すために、いくつかの欠点は次のとおりです。
FooImpl
クラスのインスタンス化を防ぐために内部クラスを持つことはいことだと言う人もいます。Foo
に新しいメソッドを追加する場合は、実装クラスとインターフェースの2つの場所で変更を行う必要があります。大したことではありませんが、まだ少し手間がかかります。編集:
Fabian Streitelのコメントは、上記のソリューションで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);
}
}
}
たくさんの良い提案があります。問題を打つためにできることは、State Patternで考えて実装することです。
このコードスニペットをご覧ください。このシナリオでは、オブジェクトの内部状態に基づいてメソッド実装全体を変更するように見えます。オブジェクト内のメソッドの合計は動作として認識されることを思い出してください。
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() {}
}
}
あなたがそれを好き願っています!
P.D:状態パターンの実装です(コンテキストに応じて戦略とも呼ばれますが、原則はまったく同じです)。
はい、しかし、それは少し作業ですので、それはそれがあなたにとってどれほど重要であるかによります。
クラスをインターフェイスとして定義し、デリゲート実装を記述し、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ドキュメント を参照してください。
この質問は アスペクト指向プログラミング と密接に関連しています。 AspectJはJavaのAOP拡張であり、いくつかのインスピレーションを得るためにそれを見てみることができます。
私の知る限り、JavaでのAOPの直接的なサポートはありません。例えば Template Method や Strategy など、それに関連するいくつかのGOFパターンがありますが、実際にはコードの行を節約しません。
Javaおよび他のほとんどの言語では、関数で必要な再帰ロジックを定義し、適切なタイミングで呼び出すいわゆる統制のとれたコーディングアプローチを採用できます。
public void checkBalance() {
checkSomePrecondition();
...
checkSomePostcondition();
}
ただし、ファクタリングされたコードをcheckBalance
から返せるようにするため、これはケースに当てはまりません。マクロをサポートする言語(C/C++など)では、checkSomePrecondition
およびcheckSomePostcondition
をマクロとして定義できます。これらは、コンパイラが呼び出される前にプリプロセッサに置き換えられるだけです。
#define checkSomePrecondition \
if (!fooIsEnabled) return;
Javaにはこれがすぐに使用できるわけではありません。これは誰かを怒らせるかもしれませんが、私は過去に反復的なコーディングタスクを自動化するために自動コード生成とテンプレートエンジンを使用しました。適切なプリプロセッサ(たとえば、Jinja2)でコンパイルする前にJavaファイルを処理すると、Cで可能なことと同様のことができます。
純粋なJavaソリューションを探している場合、おそらく簡潔ではないでしょう。ただし、プログラムの一般的な部分を除外して、コードの重複やバグを回避することは可能です。あなたはこのようなことをすることができます(それはある種の Strategy -inspiredパターンです)。 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.left
、right
、up
、または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 */);
}});
}
}
リファクタリングを検討します。このパターンは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())
デバッグが有効な場合、logger
自分自身に尋ねない。ただログに記録します。
代わりに、呼び出しクラスはこれを確認する必要があります。
if (this.logger.isDebugEnabled()) {
this.logger.debug(createResourceExpensiveDump())
}
これがライブラリであり、このクラスの呼び出しを制御できない場合、IllegalStateException
をスローします。これは、この呼び出しが違法で問題を引き起こす場合、その理由を説明します。
これに対する最もエレガントで最高のパフォーマンスのソリューションであるIMHOは、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
}
};
}
Sstanが指摘しているように、インターフェースを使用することはさらに良いでしょう。
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を作成するか、同じインスタンスを再利用するなど)。
私には別のアプローチがあります:
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
またはNullFoo.DEFAULT
を保持するFooImpl
変数に置き換えることもできます。その後、呼び出しは再び簡単になります。
Foo toBeUsed = isFooEnabled ? foo : NullFoo.DEFAULT;
toBeUsed.bar();
toBeUsed.baz();
toBeUsed.bat();
ところで、これは「ヌルパターン」と呼ばれます。
Java 8のラムダ関数 を使用した@Colinの答えに対する同様の機能的アプローチでは、条件付き機能トグルの有効化/無効化コードをガードメソッド(executeIfEnabled
)にラップして、条件付きで実行されるコードを渡すことができるアクションラムダ。
あなたの場合、このアプローチはコードを一切保存しませんが、これを乾燥させることで、他の機能切り替えの懸念に加えて、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();
}
他の回答で指摘されているように、 Strategy Design Pattern は、このコードを簡素化するために従うべき適切な設計パターンです。ここでは、リフレクションによるメソッド呼び出しを使用して説明しましたが、同じ効果を得るために使用できるメカニズムはいくつもあります。
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...
}
}
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();
}
そして、bar
、baz
、および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のnullableソリューション を使用しますが、以下のクラスのFooImpl
およびNullFoo
内部クラス(プライベートコンストラクターを使用)を作成します。
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)
の使用を覚えておく必要はありません。
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
}
}
Fooが有効になっていない場合、クラスは何もしないようですので、Fooインスタンスを作成または取得する上位レベルでこれを表現しないのはなぜですか?
class FooFactory
{
static public Foo getFoo()
{
return isFooEnabled ? new Foo() : null;
}
}
...
Foo foo = FooFactory.getFoo();
if(foo!=null)
{
foo.bar();
....
}
ただし、isFooEnabledが定数の場合にのみ機能します。一般的な場合、独自の注釈を作成できます。
基本的に、フラグが設定されている場合、関数呼び出しをスキップする必要があります。だから、私の解決策はばかげていると思うが、ここにある。
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 ()
{
// ..
}
}
デリゲート(関数へのポインター)を使用する別のソリューションがあります。最初に検証を実行し、次に呼び出される関数(パラメーター)に応じて関連するメソッドを呼び出す一意のメソッドを持つことができます。 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");
}
}