Diğer geliştiricileri çalışmalarını tamamladıktan sonra yöntemi çağırmaya zorlama


34

Java 7'deki bir kütüphanede diğer sınıflara hizmet veren bir sınıfa sahibim. Bu hizmet sınıfının bir örneğini oluşturduktan sonra, bunun bir yöntemi birkaç kez çağrılabilir (hadi yöntem olarak adlandıralım doWork()). Bu yüzden hizmet sınıfının çalışması ne zaman biter bilmiyorum.

Sorun, servis sınıfının ağır nesneler kullanması ve onları serbest bırakmasıdır. Bu kısmı bir yöntemde belirledim (diyelim release()), ancak diğer geliştiricilerin bu yöntemi kullanacağı garanti edilmez.

Servis sınıfının görevini tamamladıktan sonra diğer geliştiricileri bu yöntemi çağırmaya zorlamanın bir yolu var mı? Tabii ki bunu belgeleyebilirim ama onları zorlamak istiyorum.

Not: release()Yöntemdeki yöntemi doWork()çağıramıyorum, çünkü doWork() daha sonra çağrıldığında bu nesnelere ihtiyaç duyuyor.


46
Yaptığınız şey bir geçici bağlantı biçimidir ve genellikle bir kod kokusu olarak kabul edilir. Bunun yerine daha iyi bir tasarım ortaya çıkarmak için kendinizi zorlamak isteyebilirsiniz.
Frank

1
@Frank kabul ediyorum, altta yatan katmanın tasarımını değiştirmek bir seçenek ise o zaman bu en iyi bahis olurdu. Ama bunun bir başkasının hizmetinin etrafındaki bir sarıcı olduğundan şüpheleniyorum.
Dar Brett,

Hizmetinizin hangi tür ağır nesnelere ihtiyacı var? Hizmet sınıfı bu kaynakları kendiliğinden otomatik olarak yönetemiyorsa (geçici bağlantıya gerek kalmadan), o zaman belki bu kaynaklar başka bir yerde yönetilmeli ve Hizmete enjekte edilmelidir. Servis sınıfı için birim testleri yazdınız mı?
GELMEKTEDİR

18
Bunu istemek çok "Java" değil. Java, çöp toplama özelliğine sahip bir dildir ve şimdi, geliştiricilerin "temizlemesini" gerektiren bir tür anlaşmayla karşılaşıyorsunuz.
Pieter B

22
@ PieterB neden? AutoCloseablebu amaç için yapılmıştır.
BgrWorker

Yanıtlar:


48

Pragmatik çözüm, sınıfı yapmak ve geri döndürmez olarak bir yöntem AutoCloseablesunmaktır finalize()(eğer uygunsa ... aşağıya bakınız!). Ardından, kaynak kullanarak denemek veya close()açıkça arama yapmak için sınıfınızın kullanıcılarına güvenin.

Tabii ki bunu belgeleyebilirim ama onları zorlamak istiyorum.

Ne yazık ki, programcıyı doğru olanı yapmaya zorlamak için Java'da 1 yolu yoktur . Yapmayı umabileceğiniz en iyi şey, statik kod çözümleyicide yanlış kullanımı tespit etmektir.


Kesinleştiriciler konusunda. Bu Java özelliği çok az iyi kullanım durumlarına sahiptir. Toplayıcılık için kesinleştiricilere güvenirseniz, toparlanmanın gerçekleşmesinin çok uzun zaman alabileceği sorunuyla karşı karşıya kalırsınız. Sonlandırıcı yalnızca GC nesneye artık erişilemez olduğuna karar verdikten sonra çalıştırılacaktır . JVM tam bir koleksiyon yapana kadar bu olmayabilir .

Bu nedenle, çözmek istediğiniz sorun erken çıkarılması gereken kaynakları geri almak ise , sonlandırma kullanarak tekneyi kaçırdınız.

Ve yukarıda söylediklerime sahip değilseniz, sonlandırıcıları üretim kodunda kullanmak hiçbir zaman uygun değildir ve asla bunlara güvenmemeniz gerekir!


1 - Yollar var. Hizmet nesnelerini kullanıcı kodundan "gizlemeye" hazırsanız veya kullanım ömrünü sıkıca kontrol ederseniz (örneğin, https://softwareengineering.stackexchange.com/a/345100/172 ) o zaman kullanıcı kodunu çağırmanız gerekmezrelease() . Ancak, API daha karmaşık, kısıtlı ... ve "çirkin" IMO olur. Ayrıca, bu programcıyı doğru olanı yapmaya zorlamaz . Programcının yanlış şeyi yapma yeteneğini ortadan kaldırıyor!


1
Sonlandırıcılar çok kötü bir ders veriyor - eğer becerirseniz, sizin için düzelteceğim. Netty'nin yaklaşımını tercih ederim -> (ByteBuffer) fabrikasına rastgele% X bytebuffers olasılığı ile bu arabelleğin alındığı yeri basacak bir sonlandırıcıya sahip rasgele oluşturma talimatı veren bir yapılandırma parametresi tercih ediyorum. Bu yüzden üretimde bu değer% 0 olarak ayarlanabilir - yani genel gider yok ve test sırasında ve ürün
devri

11
ve nesne örneği çöp toplanıyor olsa bile, sonlandırıcıların asla çalıştırılmasının garanti edilmediğini unutmayın!
17'de

3
Bir Otomatik Kapanma kapalı değilse Eclipse'in bir derleyici uyarısı gönderdiğini belirtmek önemlidir. Diğer Java IDE'nin de böyle yapmasını bekliyorum.
meriton - grevde

1
@jwenting Nesne GC'dken hangi durumda kaçmazlar? Bunu belirten veya açıklayan herhangi bir kaynak bulamadım.
Cedric Reichenbach

3
@Voo Yorumumu yanlış anladınız. İki uygulamanız var -> DefaultResourceve FinalizableResourceilkini genişleten ve sonlandırıcı ekleyen. Üretimde DefaultResourcekesinleştiriciye sahip olmayan ve hiç bir ek yükü olmayan kullanın . Geliştirme ve test sırasında uygulamayı FinalizableResourcebir sonlandırıcıya sahip olan ve dolayısıyla ek yükü ekleyecek şekilde yapılandırırsınız . Geliştirme ve test sırasında sorun değil, ancak sızıntıları tespit etmenize ve üretime ulaşmadan önce onları düzeltmenize yardımcı olabilir. Eğer bir sızıntı PRD'ye ulaşırsa, fabrikayı FinalizableResourcesızıntıyı tespit etmek için% X oluşturacak şekilde yapılandırabilirsiniz
Svetlin Zarev

55

Bu, AutoCloseablearabirim için kullanım durumu ve Java 7'de sunulan try-with-resourcesifadeye benziyor.

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

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

o zaman tüketicileriniz onu kullanabilir

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

ve releasekodlarının bir istisna atıp atmadığına bakılmaksızın açık bir çağrı yapmak için endişelenmenize gerek yoktur .


Ek cevap

Eğer gerçekten aradığınız şey, kimsenin işlevinizi kullanmayı unutamayacağından emin olmaksa, birkaç şey yapmanız gerekir.

  1. Tüm yapıcıların MyServiceözel olduğundan emin olun .
  2. Kullandıktan MyServicesonra temizlenmesini sağlamak için kullanılacak tek bir giriş noktası tanımlayın .

Gibi bir şey yapardın

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)
        }
    }
}

O zaman olarak kullanırdın

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

Java-8 lambdas ve fonksiyonel arayüzler (burada olmadan çirkin olduğu için başlangıçta bu yolu tavsiye etmedi MyServiceConsumerolur Consumer<MyService>) ve oldukça da tüketiciler üzerinde heybetli. Ama sadece istediğin, aranması release()gereken şeyse işe yarayacak.


1
Bu cevap muhtemelen benimkinden daha iyi. Çöp Toplayıcı devreye girmeden önce yolumda bir gecikme var.
Dar Brett

1
Müvekkillerin kullanmayacağından try-with-resourcesve sadece ihmal etmeyeceğinden ve tryböylece closearamayı kaçırdığından nasıl emin olabilirsiniz ?
Frank

@ walpen Bu taraftan da aynı sorun var. Kullanıcıyı kullanmaya nasıl zorlayabilirim try-with-resources?
hasanghaforian

1
@ Frank / @hasanghaforian, Evet, bu şekilde çağrılmaya zorlanmaz. Alışkanlık kazancı var: Java geliştiricileri .close(), sınıfın uygulandığı zaman gerçekten çağrıldığından emin olmanız gerektiğini biliyorlar AutoCloseable.
55'te walpen

1
Bir sonlandırıcıyı usingdeterministik sonlandırma için bir blok ile eşleştiremez misiniz ? En azından C # 'da bu, geliştiricilerin deterministik sonlandırmaya ihtiyaç duyan kaynakları kullanırken alışkanlık edinmeleri için oldukça açık bir kalıp haline gelir. Kolay ve alışkanlık yaratan bir şey, birini bir şeyler yapmaya zorlayamayacağınız bir sonraki en iyi şeydir. stackoverflow.com/tr/2016311/84206
AaronLS

52

Evet yapabilirsin

Onları serbest bırakmaları için kesinlikle zorlayabilir ve yeni Serviceörnekleri yaratmalarını imkansız kılarak, unutmak imkansız olan% 100'dür .

Daha önce böyle bir şey yaptılarsa:

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

O zaman değiştirin, böylece bu şekilde erişebilsinler.

// 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();
      } 
    } 
  }

Uyarılar:

  • Bu sahte kodu göz önünde bulundurun, Java yazdığımdan bu yana yıllar geçti.
  • Bugünlerde, belki de AutoCloseablebahsi geçen birinin ara yüzü dahil olmak üzere, daha zarif çeşitler olabilir ; ancak verilen örnek kutunun dışına çıkmalı ve şıklık dışında (ki bu kendi içinde iyi bir amaçtır) değiştirmek için önemli bir sebep olmamalıdır. Bunun AutoCloseable, özel uygulamanızın içinde kullanabileceğinizi söylemek niyetinde olduğunu unutmayın ; Çözümümün kullanıcılarınızın kullanımına göre yararı AutoCloseable, yine unutulamamasıdır.
  • Örneğin new Serviceçağrıya argümanları eklemek için gerektiği gibi kesmeniz gerekir .
  • Yorumlarda da belirtildiği gibi, böyle bir yapının (a yaratma ve yok etme sürecini alma Service) arayanın elinde olup olmadığı sorusu bu cevabın kapsamı dışındadır. Ancak buna kesinlikle ihtiyacınız olduğuna karar verirseniz, bunu nasıl yapabilirsiniz.
  • Bir yorum yapan, istisnaların ele alınmasıyla ilgili şu bilgileri verdi: Google Kitaplar

2
Olumsuz oyla ilgili yorumumdan memnunum, bu yüzden cevabı geliştirebilirim.
AnoE

2
Oy vermedim, ancak bu tasarımın değerinden daha fazla sorun çıkarması muhtemel. Çok fazla karmaşıklık ekler ve arayanlara büyük bir yük getirir. Geliştiriciler, bir sınıf uygun arayüzü uyguladıklarında bunları kullanmanın ikinci nitelikte olduğu kaynaklarla denemeli stil sınıfları hakkında yeterince bilgi sahibi olmalıdır, bu nedenle genellikle yeterince iyidir.
jpmc26

30
@ jpmc26, tuhaf bir şekilde, bu yaklaşımın , kaynak yaşam döngüsü hakkında düşünmelerini engelleyerek , arayanlar üzerindeki karmaşıklığı ve yükü azalttığını hissediyorum . Bunun dışında; OP, "kullanıcıları zorlamalı mıyım ..." değil, "kullanıcıları nasıl zorlarım" diye sormadı. Yeterince önemliyse, bu çok iyi çalışan bir çözümdür, yapısal olarak basittir (lambda / procs / kapama olan diğer dillere bile aktarılabilir). Ben bunu yargılamak için SE demokrasisine bırakacağım; OP zaten zaten karar vermişti. ;)
AnoE

14
Yine, @ jpmc26, bu yaklaşımın oradaki herhangi bir tek kullanımlık durum için doğru olup olmadığını tartışmakla ilgilenmiyorum. OP böyle bir şeyi nasıl uygulayacağını sorar ve bu cevap böyle bir şeyi nasıl uygulayacağını söyler . İlk başta yapmanın uygun olup olmadığına karar vermek bizim için değildir. Gösterilen teknik bir alet, daha fazla bir şey değil, daha az bir şey değil ve alet çantası olanlarında faydalı olduğuna inanıyorum.
AnoE

11
Bu doğru cevap imho, aslında ortadaki delik kalıbı
jk.

11

Komut düzenini kullanmayı deneyebilirsiniz .

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();
    }

}

Bu size kaynak edinimi ve salıverilmesinde tam kontrol sağlar. Arayan kişi yalnızca Servisinize görevler verir ve siz de kaynakları almaktan ve temizlemekten siz sorumlusunuz.

Yalnız dikkat edilmesi gereken bir şey var. Arayan hala bunu yapabilir:

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

Fakat bu problemi ortaya çıktığında çözebilirsiniz. Performans sorunları olduğunda ve bazı arayanların bunu yaptığını öğrendiğinizde, onlara doğru yolu gösterin ve sorunu çözün:

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

Bunu, diğer görevlere bağlı görevler için vaatler uygulayarak keyfi olarak karmaşık hale getirebilirsiniz, ancak daha sonra işleri serbest bırakmak da daha karmaşık hale gelir. Bu sadece bir başlangıç ​​noktasıdır.

zaman uyumsuz

Yorumlarda da belirtildiği gibi, yukarıdakiler asenkron isteklerle gerçekten çalışmaz. Bu doğru. Ancak bu, Java 8'de CompleteableFuture kullanımıyla , özellikle CompleteableFuture#supplyAsyncbireysel geleceklerini oluşturmak ve tüm görevler tamamlandıktan CompleteableFuture#allOfsonra kaynakların serbest bırakılmasını sağlamak için kolayca çözülebilir . Alternatif olarak, kendi Futures / Promises uygulamalarını uygulamak için her zaman Threads veya ExecutorServices'i kullanabilirsiniz.


1
Katılımcılar neden bunun kötü olduğunu düşündükleri hakkında bir yorumda bulunursa, bu endişeleri doğrudan ele alabilir ya da en azından gelecek için başka bir bakış açısına sahip olabilirim. Teşekkürler!
Polygnome

+1 oy ver. Bu bir yaklaşım olabilir, ancak hizmet zaman uyumsuz ve bir sonraki hizmeti istemeden önce tüketicinin ilk sonucu alması gerekiyor.
hasanghaforian

1
Bu sadece bir barebones şablonu. JS bu vaadiyle çözer, Java ile geleceklerle benzer şeyler yapabilir ve tüm işler bittiğinde toplanıp toplanıp toplanacak bir koleksiyoncu ile yapabilirsiniz. Orada uyumsuzluk ve hatta bağımlılık almak kesinlikle mümkün, ama aynı zamanda işleri çok zorlaştırıyor.
Polygnome

3

Yapabilirseniz, kullanıcının kaynağı oluşturmasına izin vermeyin. Kullanıcının, kaynağı oluşturacak olan yöntemine ziyaretçi nesnesini geçmesine, onu ziyaretçi nesnesinin yöntemine geçirmesine ve daha sonra serbest bırakmasına izin ver.


Cevabınız için teşekkür ederim, ama afedersiniz, tüketiciye kaynakları iletmenin ve ardından hizmet istediğinde tekrar almanın daha iyi olduğu anlamına mı geliyorsunuz? O zaman sorun devam edecek: Tüketici bu kaynakları serbest bırakmayı unutabilir.
hasanghaforian

Bir kaynağı kontrol etmek istiyorsanız, bırakmayın, tamamen başka birinin yürütme akışına geçirmeyin. Bunu yapmanın bir yolu, kullanıcının ziyaretçi nesnesinin önceden tanımlanmış bir yöntemini çalıştırmaktır. AutoCloseableperde arkasında çok benzer bir şey yapar. Başka bir açıda, bağımlılık enjeksiyonuna benzer .
9000

1

Benim fikrim Polygnome'unki ile benzerdi.

Sınıfınızdaki "do_something ()" yöntemlerine özel bir komut listesine komut eklemeniz yeterlidir. Ardından, işi gerçekten yapan ve "release ()" adında bir "commit ()" yöntemi kullanın.

Böylece, kullanıcı asla bir çağrı yapmazsa (), iş hiç bitmez. Onlar yaparsa, kaynaklar serbest bırakılır.

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();
  }

}

Daha sonra foo.doSomething.doSomethineElse (). Commit ();


Bu aynı çözümdür, ancak arayan kişiden görev listesini korumasını istemek yerine, dahili olarak korumanızı sağlarsınız. Çekirdek kavramı tam anlamıyla aynı. Ancak bu, Java için şimdiye kadar gördüğüm kodlama kurallarına uymuyor ve okunması oldukça zor (döngü gövdesi, döngü başlığı ile aynı satırda, başlangıçta _).
Polygnome

Evet, komut düzenidir. Düşündüğüm şeyin mümkün olduğu kadar özlü olması için bir kod yazdım. Bu günlerde çoğunlukla C # yazarım, burada özel değişkenler sık ​​sık _ ile eklenmişlerdir.
Sava B.

-1

Bahsedilenlerin bir diğer alternatifi, kapsüllenen kaynaklarınızı, çöp toplayıcısının kaynakları elle serbest bırakmadan otomatik olarak temizlemesini sağlayacak şekilde yönetmek için "akıllı referansların" kullanılmasıdır. Yararları elbette uygulamanıza bağlıdır. Bu harika blog yazısında bu konuyla ilgili daha fazla bilgi edinin : https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references


Bu ilginç bir fikir, ancak zayıf referansları kullanmanın OP'nin sorununu nasıl çözdüğünü göremiyorum. Hizmet sınıfı başka bir doWork () isteği alıp almayacağını bilmiyor. Ağır nesnelere atıfta bulunursa ve başka bir doWork () çağrısı alırsa, başarısız olur.
Jay Elston

-4

Diğer herhangi bir Sınıf Odaklı dil için onu yıkıcıya koyarım derdim, ama bu bir seçenek olmadığından sonlandırma yöntemini geçersiz kılabileceğinizi düşünüyorum. Bu durumda, örnek kapsam dışına çıktığında ve çöp toplandığında, serbest bırakılacaktır.

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

Java'ya pek aşina değilim, ancak finalize () hedefinin amacı bu olduğunu düşünüyorum.

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


7
Bu Stephen'ın cevabında söylediği nedenin sorununa kötü bir çözümdür -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
Ve o zaman bile
finalizör

3
Finalizers herkesin bildiği güvenilmez: onlar garantisi yoktur çalıştırırsanız onlar, onlar hiç çalışmayabilir, çalışabilir ınlanırlar. Josh Bloch gibi Java mimarları bile finalizatörlerin korkunç bir fikir olduğunu söylüyor (referans: Etkili Java (2nd Edition) ).

Bu tür sorunlar beni deterministik yıkım ve RAII ile C ++ 'ı özlüyor ...
Mark K Cowan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.