他の開発者に作業の完了後にメソッドを呼び出すように強制する


34

Java 7のライブラリには、他のクラスにサービスを提供するクラスがあります。このサービスクラスのインスタンスを作成した後、その1つのメソッドを複数回呼び出すことができます(doWork()メソッドと呼びましょう)。したがって、サービスクラスの作業がいつ完了するかはわかりません。

問題は、サービスクラスが重いオブジェクトを使用し、それらを解放する必要があることです。この部分をメソッドに設定します(呼び出しましょうrelease())が、他の開発者がこのメソッドを使用することは保証されません。

サービスクラスのタスクを完了した後、他の開発者にこのメソッドを強制的に呼び出す方法はありますか?もちろんそれを文書化することはできますが、私はそれらを強制したいと思います。

注:release()メソッドでメソッドを呼び出すことはできません 。これは、次に呼び出されるときにこれらのオブジェクトが必要doWork()になるためdoWork()です。


46
あなたがしていることは、一時的な結合の形であり、通常はコードのにおいと見なされます。代わりに、より良いデザインを思い付くように強制することもできます。
フランク

1
@Frank基本的なレイヤーのデザインを変更することがオプションである場合、それが最善策であることに同意します。しかし、私はこれが誰かのサービスのラッパーであると疑っています。
ダーブレット

サービスに必要な重いオブジェクトはどのようなものですか?サービスクラスがそれらのリソースを自動的に(一時的な結合を必要とせずに)自動的に管理できない場合は、おそらくそれらのリソースを他の場所で管理し、サービスに注入する必要があります。Serviceクラスの単体テストを作成しましたか?
から来る

18
それを要求するのは非常に「非Java」です。Javaはガベージコレクションを備えた言語であり、開発者に「クリーンアップ」を要求するある種の仕掛けを思い付くようになりました。
ピーターB

22
@PieterBなぜ?AutoCloseableこの正確な目的のために作られました。
BgrWorker

回答:


48

実用的な解決策は、クラスを作成し、バックストップとしてメソッドをAutoCloseable提供するfinalize()ことです(適切な場合は、以下を参照してください!)。次に、クラスのユーザーがtry-with-resourceを使用するか、close()明示的に呼び出すようにします。

もちろんそれを文書化することはできますが、私はそれらを強制したいと思います。

残念ながら、Javaにはプログラマに正しいことを強制する方法1はありません。期待できる最善の方法は、静的コードアナライザーで誤った使用法を見つけることです。


ファイナライザーのトピックについて。このJava機能には、適切なユースケースはほとんどありませ。ファイナライザを使用して片付けを行うと、片付けが完了するまでに非常に長い時間がかかるという問題が発生します。ファイナライザは、オブジェクトが到達不能であるとGCが判断した後にのみ実行さます。JVMが完全なコレクションを実行するまで、それは起こらないかもしれません。

したがって、解決しようとしている問題が、早期にリリースする必要があるリソースを回収することである場合、ファイナライズを使用してボートを見逃しています。

そして、私が上で言っていることを理解していない場合に備えて... 本番コードでファイナライザを使用することはほとんど適切ではなく、それらに頼るべきではありません


1-方法があります。ユーザーコードからサービスオブジェクトを「非表示」にするか、ライフサイクルを厳密に制御する準備ができている場合(例:https : //softwareengineering.stackexchange.com/a/345100/172)、ユーザーコードを呼び出す必要ありませんrelease()。ただし、APIはより複雑になり、制限され、...および「ugい」IMOになります。また、これはプログラマーに正しいことを強いることではありません。プログラマーが間違ったことをする能力を取り除いています!


1
ファイナライザーは非常に悪い教訓を教えてくれます-あなたがそれをねじ込んだ場合、私はあなたのためにそれを修正します。nettyのアプローチ->(ByteBuffer)ファクトリーに、このバッファーが取得された場所を出力するファイナライザーを使用して、X%バイトバッファーの確率でランダムに作成するよう指示する構成パラメーター(まだリリースされていない場合)を好みます。だから、本番では、この値が0%に設定することができます-すなわちオーバーヘッドなし、および製品をリリースする前にリークを検出するために、50%または100%のような高い値にテスト&DEV中
Svetlin Zarev

11
オブジェクトインスタンスがガベージコレクションされている場合でも、ファイナライザが実行されることは保証されていません。
-jwenting

3
AutoCloseableが閉じられていない場合、Eclipseはコンパイラー警告を発することに注意してください。他のJava IDEもそうすることを期待しています。
メリトン-ストライキで

1
@jwentingオブジェクトがGCされたときに実行されないのはどのような場合ですか?それを説明または説明するリソースが見つかりませんでした。
セドリックライヘンバッハ

3
@Vooあなたは私のコメントを誤解した。あなたは、2つの実装を持っている- > DefaultResourceFinalizableResourceその最初のものを拡張し、ファイナライザを追加します。本番環境DefaultResourceでは、ファイナライザーのないオーバーヘッドを使用します。開発およびテスト中に、使用するアプリを構成しFinalizableResource、ファイナライザーを使用するため、オーバーヘッドが追加されます。開発中およびテスト中は問題になりませんが、実稼働に到達する前にリークを特定して修正することができます。ただし、リークがPRDに到達した場合、リークを特定するためにX%FinalizableResourceを作成するようにファクトリを構成できます
スヴェトリン

55

これは、AutoCloseableインターフェースのユースケースとJava 7で導入されたtry-with-resourcesステートメントのようです。

public class MyService implements AutoCloseable {
    public void doWork() {
        // ...
    }

    @Override
    public void close() {
        // release resources
    }
}

消費者は次のように使用できます

public class MyConsumer {
    public void foo() {
        try (MyService myService = new MyService()) {
            //...
            myService.doWork()
            //...
            myService.doWork()
            //...
        }
    }
}

releaseコードが例外をスローするかどうかに関係なく、明示的な呼び出しを心配する必要はありません。


追加の回答

あなたが本当に探しているのがあなたの関数を使うことを誰も忘れられないようにすることであるなら、あなたはいくつかのことをしなければなりません

  1. のすべてのコンストラクターMyServiceがプライベートであることを確認してください。
  2. MyService後でクリーンアップされることを保証する、使用する単一のエントリポイントを定義します。

あなたは次のようなことをします

public interface MyServiceConsumer {
    void accept(MyService value);
}

public class MyService implements AutoCloseable {
    private MyService() {
    }


    public static void useMyService(MyServiceConsumer consumer) {
        try (MyService myService = new MyService()) {
            consumer.accept(myService)
        }
    }
}

その後、次のように使用します

public class MyConsumer {
    public void foo() {
        MyService.useMyService(new MyServiceConsumer() {
            public void accept(MyService myService) {
                myService.doWork()
            }
        });
    }
}

このパスは、Java-8ラムダと機能的なインターフェース(にMyServiceConsumerなるConsumer<MyService>)がなく恐ろしく、消費者にかなりの負担をかけるため、もともとお勧めしませんでした。しかし、release()必要なのは、それを呼び出す必要がある場合だけです。


1
この答えはおそらく私の答えよりも優れています。私の方法は、ガベージコレクタのキックの前に遅延を持っている。
ダルブレット

1
どのようにクライアントが使用することを保証するんtry-with-resourcesだけ省略していないtryため、不足している、closeコールを?
フランク

@walpenこの方法には同じ問題があります。ユーザーに強制的に使用させるにはどうすればよいtry-with-resourcesですか?
-hasanghaforian

1
@Frank / @hasanghaforian、はい、この方法では強制的に近くに呼び出されることはありません。Java開発者.close()は、クラスがを実装するときに呼び出されることを本当に確認する必要があることを知っていますAutoCloseable
ウォルペン

1
using確定的なファイナライズのために、ファイナライザとブロックをペアにできませんでしたか?少なくともC#では、これは開発者が確定的なファイナライズを必要とするリソースを活用するときに使用する習慣を身に付けるための非常に明確なパターンになります。誰かに何かを強制することができない場合、簡単で習慣的なものが次善の策です。stackoverflow.com/a/2016311/84206
AaronLS

52

はい、できます

絶対に強制的にリリースさせて、新しいものを作成できないようにすることで、忘れることが100%不可能になるようにすることができます。 Serviceインスタンスます。

以前にこのようなことをした場合:

Service s = s.new();
try {
  s.doWork("something");
  if (...) s.doWork("something else");
  ...
}
finally {
  s.release(); // oops: easy to forget
}

次に、このようにアクセスする必要があるように変更します。

// Their code

Service.runWithRelease(new ServiceConsumer() {
  void run(Service s) {
    s.doWork("something");
    if (...) s.doWork("something else");
    ...
  }  // yay! no s.release() required.
}

// Your code

interface ServiceConsumer {
  void run(Service s);
}

class Service {

   private Service() { ... }      // now: private
   private void release() { ... } // now: private
   public void doWork() { ... }   // unchanged

   public static void runWithRelease(ServiceConsumer consumer) {
      Service s = new Service();
      try {
        consumer.run(s);
      }
      finally {
        s.release();
      } 
    } 
  }

警告:

  • この擬似コードを検討してください。Javaを書いてからずっと経ちます。
  • 最近では、AutoCloseable誰かが言及したインターフェイスを含む、よりエレガントなバリアントが存在する可能性があります。ただし、指定された例はそのまま使用でき、エレガントさ(それ自体が素晴らしい目標です)を除き、変更する大きな理由はないはずです。これはAutoCloseable、プライベート実装内で使用できると言っていることに注意してください。ユーザーに使用してもらうことに対する私のソリューションの利点は、AutoCloseable忘れられないということです。
  • new Service呼び出しに引数を挿入するなど、必要に応じて肉付けする必要があります。
  • コメントで述べたように、このような構成体(つまり、 Service)が呼び出し側の手にある、この回答の範囲外です。ただし、これが絶対に必要だと判断した場合は、これを行うことができます。
  • 1人のコメント提供者が例外処理に関する次の情報を提供しました:Googleブックス

2
投票に対するコメントに満足しているので、答えを改善できます。
-AnoE

2
私はダウン投票しませんでしたが、この設計は価値があるよりも厄介である可能性があります。それはかなりの複雑さを追加し、発信者に大きな負担を課します。開発者はtry-with-resourcesスタイルのクラスに十分に精通している必要があります。これは、クラスが適切なインターフェイスを実装するときはいつでも使用することです。
jpmc26

30
@ jpmc26、おもしろいことに、このアプローチ発信者がリソースのライフサイクルについてまったく考えないようにすることで、発信者の複雑さと負担を軽減すると感じています。それはさておき; OPは「ユーザーを強制する必要がありますか?」ではなく、「ユーザーを強制するにはどうすればよいか」を尋ねませんでした。そして、それが十分に重要な場合、これは非常にうまく機能する1つのソリューションであり、構造的に単純であり(クロージャ/実行可能ファイルでラップするだけです)、ラムダ/プロシージャ/クロージャを持つ他の言語にも転送可能です。さて、私は判断するためにSE民主主義に任せます。とにかく、OPは既に決定しています。;)
AnoE

14
繰り返しになりますが、@ jpmc26、このアプローチが単一のユースケースに対して正しいかどうかを議論することにはあまり興味がありません。OP はそのようなことを実施する方法を求め、この答えはそのようなことを実施する方法を教えます。そもそもそうすることが適切であるかどうかを判断することはできません。示されているテクニックはツールであり、それ以上でもそれ以下でもありません。
-AnoE

11
これは、基本的に穴・イン・ザ・ミドルパターンで、正解の私見である
JK。

11

コマンドパターンを使用してみてください。

class MyServiceManager {

    public void execute(MyServiceTask tasks...) {
        // set up everyting
        MyService service = this.acquireService();
        // execute the submitted tasks
        foreach (Task task : tasks)
            task.executeWith(service);
        // cleanup yourself
        service.releaseResources();
    }

}

これにより、リソースの取得と解放を完全に制御できます。呼び出し元はタスクをサービスに送信するだけで、ユーザー自身がリソースを取得してクリーンアップする責任があります。

ただし、キャッチがあります。呼び出し元はこれを行うことができます:

MyServiceTask t1 = // some task
manager.execute(t1);
MyServiceTask t2 = // some task
manager.execute(t2);

ただし、この問題が発生した場合は対処できます。パフォーマンスの問題があり、一部の呼び出し元がこれを実行していることがわかった場合は、単に適切な方法を示して問題を解決します。

MyServiceTask t1 = // some task
MyServiceTask t2 = // some task
manager.execute(t1, t2);

他のタスクに依存するタスクのプロミスを実装することにより、この意的に複雑にすることができますが、その後、ものをリリースすることもより複雑になります。これは出発点にすぎません。

非同期

コメントで指摘されているように、上記は非同期リクエストでは実際には機能しません。そのとおりです。しかし、これは、Java 8ではCompleteableFutureを使用して簡単に解決できます。特にCompleteableFuture#supplyAsync、個々の先物を作成し、すべてのタスクが完了したらCompleteableFuture#allOfリソースのリリースを実行します。あるいは、常にThreadsまたはExecutorServicesを使用して、Futures / Promisesの独自の実装をロールバックできます。


1
ダウンボッターがなぜこれが悪いと思うかコメントを残してくれれば、それらの懸念を直接解決するか、少なくとも将来の別の視点を得ることができれば幸いです。ありがとう!
ポリグノーム

+1の投票。これはアプローチかもしれませんが、サービスは非同期であり、コンシューマは次のサービスを要求する前に最初の結果を取得する必要があります。
-hasanghaforian

1
これは単なる基本テンプレートです。JSはこれを約束で解決します。Javaでもfutureとすべてのタスクが完了してからクリーンアップされるときに呼び出されるコレクターで同様のことを行うことができます。非同期や依存関係を取得することは確かに可能ですが、事態を複雑にします。
ポリグノーム

3

可能であれば、ユーザーによるリソースの作成を許可しないでください。ユーザーにビジターオブジェクトをメソッドに渡させます。メソッドはリソースを作成し、ビジターオブジェクトのメソッドに渡し、その後解放します。


返信いただきありがとうございますが、すみません、消費者にリソースを渡し、サービスを要求したときに再びそれを取得する方が良いという意味ですか その後、問題は残ります。消費者はそれらのリソースを解放するのを忘れるかもしれません。
-hasanghaforian

リソースを制御したい場合は、手放さないでください。他の人の実行フローに完全に渡さないでください。それを行う1つの方法は、ユーザーの訪問者オブジェクトの事前定義されたメソッドを実行することです。AutoCloseable舞台裏で非常に似たようなことをします。別の角度では、依存性注入に似ています。
9000

1

私の考えは、ポリグノームに似ていました。

クラスの「do_something()」メソッドを使用して、プライベートコマンドリストにコマンドを追加するだけで済みます。次に、実際に作業を行い、「release()」を呼び出す「commit()」メソッドを用意します。

したがって、ユーザーがcommit()を呼び出さない場合、作業は行われません。存在する場合、リソースは解放されます。

public class Foo
{
  private ArrayList<ICommand> _commands;

  public Foo doSomething(int arg) { _commands.Add(new DoSomethingCommand(arg)); return this; }
  public Foo doSomethingElse() { _commands.Add(new DoSomethingElseCommand()); return this; }

  public void commit() { 
     for(ICommand c : _commands) c.doWork();
     release();
     _commands.clear();
  }

}

foo.doSomething.doSomethineElse()。commit();のように使用できます。


これは同じ解決策ですが、呼び出し元にタスクリストを維持するように依頼する代わりに、内部的に維持します。コアコンセプトは文字通り同じです。しかし、これは私が今まで見たJavaのコーディング規約に従っておらず、実際には非常に読みにくいです(ループヘッダーと同じ行のループ本体、_の先頭)。
ポリノーム

はい、それはコマンドパターンです。考えていることをできるだけ簡潔にするために、いくつかのコードを書き留めました。私は最近、プライベート変数の前に_が付いているC#をほとんど書いています。
サヴァB.

-1

言及されたものに代わる別の方法は、「スマートリファレンス」を使用して、手動でリソースを解放せずにガベージコレクターが自動的にクリーンアップできるようにカプセル化されたリソースを管理することです。もちろん、その有用性は実装によって異なります。この素晴らしいブログ記事でこのトピックの詳細を読んでくださいhttps : //community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references


これは興味深いアイデアですが、弱参照を使用するとOPの問題がどのように解決されるかわかりません。サービスクラスは、別のdoWork()要求を取得するかどうかを知りません。重いオブジェクトへの参照を解放してから、別のdoWork()呼び出しを取得すると、失敗します。
ジェイエルストン

-4

他のクラス指向言語の場合は、デストラクタに入れますが、それはオプションではないので、finalizeメソッドをオーバーライドできると思います。そのようにして、インスタンスが範囲外になり、ガベージコレクションされると、インスタンスは解放されます。

@Override
public void finalize() {
    this.release();
}

私はJavaにあまり詳しくありませんが、これがfinalize()の目的であると思います。

http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize()


7
これは、スティーブンが彼の答えで言う理由のために問題の悪い解決策ですIf you rely on finalizers to tidy up, you run into the problem that it can take a very long time for the tidy-up to happen. The finalizer will only be run after the GC decides that the object is no longer reachable. That may not happen until the JVM does a full collection.
--Daenyth

4
そして、それでもファイナライザは実際には実行されない場合があります
...-

3
ファイナライザは信頼性が低いことで有名です。実行される場合、実行される保証がない場合、実行される可能性があります。Josh BlochなどのJavaアーキテクトでさえ、ファイナライザはひどいアイデアだと言います(参照:Effective Java(2nd Edition))。

問題のこれらの種類は...私はその決定論的破壊とRAIIとC ++を欠場作る
マーク・K・コーワン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.