12

私はこのインターフェースを持っています:

public interface Command<T> {
    T execute(String... args);
}

ほとんどの用途で問題なく動作します。しかし、副作用のみ (たとえば戻り値なし) のコマンドをモデル化しようとすると、次のように書きたくなります。

public class SideEffectCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
        return null; // null is fine?
    }
} 

これはよくある問題ですか?戻り値の有無にかかわらずモデルCommandsするためのベスト プラクティスはありますか?

このアダプターを試してみましたが、これはいくつかの理由で最適ではないと思います。

public abstract class VoidCommand implements Command<Void> {

    @Override
    public Void execute(String... args) {
       execute2(args);
       return null;
    }

    public abstract void execute2(String... args);
}
4

8 に答える 8

10

Void私は明示的に使用することに固執します。別のクラスが関与していなくても、何が起こっているかを簡単に確認できます。Voidリターンをvoid(およびなどIntegerで)オーバーライドできると便利ですがint、それは優先事項ではありません。

于 2009-07-24T13:12:14.347 に答える
4

これは、複数世界の最善の実装です。

// Generic interface for when a client doesn't care
// about the return value of a command.
public interface Command {
    // The interfaces themselves take a String[] rather
    // than a String... argument, because otherwise the
    // implementation of AbstractCommand<T> would be
    // more complicated.
    public void execute(String[] arguments);
}

// Interface for clients that do need to use the
// return value of a command.
public interface ValuedCommand<T> extends Command {
    public T evaluate(String[] arguments);
}

// Optional, but useful if most of your commands are ValuedCommands.
public abstract class AbstractCommand<T> implements ValuedCommand<T> {
    public void execute(String[] arguments) {
        evaluate(arguments);
    }
}

// Singleton class with utility methods.
public class Commands {
    private Commands() {} // Singleton class.

    // These are useful if you like the vararg calling style.
    public static void execute(Command cmd, String... arguments) {
        cmd.execute(arguments);
    }

    public static <T> void execute(ValuedCommand<T> cmd, String... arguments) {
        return cmd.evaluate(arguments);
    }

    // Useful if you have code that requires a ValuedCommand<?>
    // but you only have a plain Command.
    public static ValuedCommand<?> asValuedCommand(Command cmd) {
        return new VoidCommand(cmd);
    }

    private static class VoidCommand extends AbstractCommand<Void> {
        private final Command cmd;

        public VoidCommand(Command actual) {
            cmd = actual;
        }

        public Void evaluate(String[] arguments) {
            cmd.execute(arguments);
            return null;
        }
    }
}

この実装により、クライアントCommandは戻り値を気にしない場合とValuedCommand<T>、特定の値を返すコマンドが必要な場合について話すことができます。

Voidストレートアップを使用しない唯一の理由は、return null;挿入を余儀なくされるすべての見苦しいステートメントです。

于 2009-08-11T22:22:23.110 に答える
4

私にはうまく見えます。他の人が言っVoidたように、元々はリフレクション メカニズム用に設計されたものですが、現在ではジェネリックで、あなたのような状況を説明するために頻繁に使用されています。

さらに良いことに、Google は GWT フレームワークで、void を返すコールバックの例で同じアイデアを使用しています (例はこちら)。私は言います:Googleがそれを行うなら、少なくともOKに違いありません.. :)

于 2009-08-10T19:25:33.877 に答える
3

これは一般的な問題ではありません。対処する必要がある問題は、インターフェースの期待です。副作用のないインターフェイスの動作と、副作用を許可するインターフェイスを組み合わせています。

このことを考慮:

public class CommandMonitor {

    public static void main(String[] args)  {       
        Command<?> sec = new SideEffectCommand();       
        reportResults(sec);     
    }   

    public static void reportResults(Command<?> cmd){

        final Object results = cmd.execute("arg");
        if (results != null ) {
            System.out.println(results.getClass());
        }
    }
}

<Void>テンプレート型として使用することに問題はありませんが、「コマンド<T>」の実装と混合できるようにすることは、インターフェースの一部のクライアントが void の結果を予期しない可能性があることを意味します。インターフェイスを変更しないと、実装によって予期しない結果が生じる可能性があります。

Collection クラスを使用して一連のデータを渡す場合、私のチームは、構文的には問題ありませんが、決してnull を返さないことに同意しました。問題は、戻り値を使用するクラスが、NPE を防ぐために常に void をチェックする必要があることでした。上記のコードを使用すると、どこにでも次のように表示されます。

    if (results != null ){

実装が実際にオブジェクトを持っているか、null であるかを知る方法があるためです。特定のケースについては、実装に精通しているため、必ず知っているはずです。しかし、それらを集約し始めるか、コーディングの範囲を超えるとすぐに (ライブラリ、将来のメンテナンスなどとして使用されます)、ヌルの問題が発生します。

次に、これを試しました:

public class SideEffectCommand implements Command<String> {

    @Override
    public String execute(String... args) {
        return "Side Effect";
    }

}

public class NoSideEffectCommand implements Command<Void>{
    @Override
    public Void execute(String... args) {
        return null;
    }   
}
public class CommandMonitor {

    public static void main(String[] args)  {       
        Command<?> sec = new SideEffectCommand();
        Command<?> nsec = new NoSideEffectCommand();

        reportResults(sec.execute("args"));
        reportResults(nsec.execute("args")); //Problem Child
    }   

    public static void reportResults(Object results){
        System.out.println(results.getClass());
    }

    public static void reportResults(Void results){
        System.out.println("Got nothing for you.");
    }
}

reportResults への 2 回目の呼び出しは、(もちろん) オブジェクトを期待するバージョンをまだ呼び出していたため、オーバーロードは機能しませんでした。に変更することを検討しました

public static void reportResults(String results){

しかし、これは問題の根本を示しており、クライアント コードは実装の詳細を知る必要があります。インターフェイスは、可能であればコードの依存関係を分離するのに役立つはずです。この時点でそれらを追加するのは悪い設計のようです。

要するに、コマンドに副作用が予想される場合を明確にする設計を使用し、一連のコマンド (つまり、未知のコマンドの配列) をどのように処理するかを考える必要があるということです。

これは、漏れやすい抽象化のケースである可能性があります。

于 2009-08-10T19:14:27.377 に答える
3

インターフェースをそのままにしておいてください。これが、Void が標準ライブラリにある理由です。Command を呼び出しているものが null が戻ってくることを期待している限り。

はい、null は Void に対して返すことができる唯一の値です。

2017年更新

過去数年間Void、リフレクションが関係する場合を除いて、戻り値の型を避けてきました。私は、より明示的でヌル回避すると思う別のパターンを使用しました。Okつまり、OP のようなすべてのコマンドに対して返される、私が呼び出す成功タイプがあります。これは私のチームでは非常にうまく機能しており、他のチームの使用にも波及しています。

public enum Ok { OK; }

public class SideEffectCommand implements Command<Ok> {
    @Override
    public Ok execute(String... args) {
        ...
        return Ok.OK; // I typically static import Ok.OK;
}
于 2009-07-24T12:02:32.527 に答える
2

あなたの例で興味深いのは、パラメーター化された型の使用です。通常、あなたは持っているでしょう

interface Command<T> {
    public T execute(T someObject);
}

あなたの場合T、戻り値としてのみ持っています。それでも、この場合は Void を使用するのが良い解決策です。戻り値は ですnull

于 2009-07-24T12:06:35.077 に答える
1

その問題はそれほど一般的ではありません、それほどまれでもありません...私は、何も返さない Callables について、少し前にそれについての議論を見たことがあると思います。

これは良い解決策であり、オブジェクトや他のダミーのプレースホルダーを使用するよりもはるかに優れているという他のポスターに同意します。

于 2009-07-24T12:45:47.960 に答える
-1

次のようなインターフェイスを使用する代わりに、次のようにするとどうなりますか。

public interface Command<T> {
    T execute(String... args);
}

代わりに:

public interface Command<T> {
    void execute(String... args);
    T getResult();
    bool hasResult();
}

次に、発信者は次のことを行います。

public void doSomething(Command<?> cmd) {
    cmd.execute(args);
    if(cmd.hasResult()) {
        // ... do something with cmd.getResult() ...
    }
}

必要に応じて、Commandを拡張するインターフェイスVoidCommmandを作成することもできます。

これは私にとって最もクリーンな解決策のようです。

于 2009-08-11T22:48:45.677 に答える