“Etrafında Yürüt” deyimi nedir?


Yanıtlar:


147

Temelde, kaynak tahsisi ve temizleme gibi her zaman gerekli olan şeyleri yapmak için bir yöntem yazdığınız ve arayanın "kaynakla ne yapmak istediğimizi" aktarmasını sağlayan kalıptır. Örneğin:

public interface InputStreamAction
{
    void useStream(InputStream stream) throws IOException;
}

// Somewhere else    

public void executeWithFile(String filename, InputStreamAction action)
    throws IOException
{
    InputStream stream = new FileInputStream(filename);
    try {
        action.useStream(stream);
    } finally {
        stream.close();
    }
}

// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
    public void useStream(InputStream stream) throws IOException
    {
        // Code to use the stream goes here
    }
});

// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));

// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);

Arama kodunun açık / temizleme tarafı hakkında endişelenmesine gerek yoktur - ilgilenir executeWithFile.

Bu Java'da açıkçası acı vericiydi, çünkü Java 8 lambda ifadeleriyle başlayarak diğer birçok dilde (örn. C # lambda ifadeleri veya Groovy) olduğu gibi kapanışlar çok garipti ve bu özel durum Java 7'den try-with-resourcesve AutoClosableakışlardan beri ele alınmaktadır .

"Tahsis etme ve temizleme" verilen tipik örnek olmasına rağmen, işlem işleme, günlüğe kaydetme, bazı kodları daha fazla ayrıcalıkla yürütme vb. Gibi birçok olası örnek vardır. Temel olarak şablon yöntemi kalıbı gibi ama kalıtım olmadan.


4
Bu deterministiktir. Java'daki finalizörler deterministik olarak çağrılmaz. Ayrıca son paragrafta söylediğim gibi, sadece kaynak tahsisi ve temizleme için kullanılmaz. Yeni bir nesne oluşturmanız gerekmeyebilir. Genellikle "başlatma ve parçalara ayırma" dır, ancak bu kaynak tahsisi olmayabilir.
Jon Skeet

3
Yani bazı işler yapmak için bir işlev işaretçisi geçirdiğiniz bir işlevin olduğu C gibi?
Paul Tomblin

3
Ayrıca, Jon, Java'daki kapanışlara atıfta bulunuyorsunuz - ki hala sahip değil (kaçırmadım). Tanımladığınız şey, aynı şey olmayan anonim iç sınıflardır. Gerçek kapanış desteği (önerildiği gibi - bloguma bakın) sözdizimini önemli ölçüde basitleştirecektir.
philsquared

8
@Phil: Bence bu bir derece meselesi. Java anonim iç sınıfları, çevreleyen ortamlara sınırlı bir anlamda erişebilir - bu nedenle "tam" kapaklar olmasa da, "sınırlı" kapaklar olduğunu söyleyebilirim. Her ne kadar kontrol (devam) rağmen, Java düzgün kapanışlarını görmek istiyorum
Jon Skeet

4
Java 7 kaynakla deneme ekledi ve Java 8 lambdas ekledi. Bunun eski bir soru / cevap olduğunu biliyorum ama beş buçuk yıl sonra bu soruya bakan herkes için bunu belirtmek istedim. Bu dil araçlarının her ikisi de, bu modelin düzeltmek için icat edildiği sorunu çözmeye yardımcı olacaktır.

45

Execute Around deyimi, kendinizi böyle bir şey yapmak zorunda bulduğunuzda kullanılır:

//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...

//... and so on.

Gerçek görevlerinizin her zaman "etrafında" yürütülen bu gereksiz kodun tümünü tekrarlamaktan kaçınmak için, bu görevle otomatik olarak ilgilenen bir sınıf oluşturacaksınız:

//pseudo-code:
class DoTask()
{
    do(task T)
    {
        // .. chunk of prep code
        // execute task T
        // .. chunk of cleanup code
    }
};

DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)

Bu deyim, tüm karmaşık gereksiz kodları tek bir yere taşır ve ana programınızı çok daha okunabilir (ve bakımı kolay!)

Bir göz atın bu yazı , bir C # örneğin ve bu yazıyı bir C ++ örneğin.


7

Bir Yöntem Çevresinde Yürütme Kurulum ve / veya söküm kodunu gerçekleştirmek ve aradaki kodunuzu çalıştırabilir bir yöntem, rasgele kod geçmesi yerdir.

Java bunu yapmayı tercih ettiğim dil değil. Bir kapanış (veya lambda ifadesi) argüman olarak geçmek daha şık. Nesneler muhtemelen kapanışlara eşdeğer olsa da .

Bana öyle geliyor ki Etrafında Yürütme Yöntemi, yöntemi her çağırdığınızda geçici olarak değiştirebileceğiniz bir tür Denetimi Tersine Çevirme (Bağımlılık Enjeksiyonu) gibidir.

Ancak aynı zamanda Kontrol Kuplajının bir örneği olarak da yorumlanabilir (bir yönteme argümanıyla ne yapacağını söylemek, bu durumda tam anlamıyla).


7

Burada bir Java etiketiniz olduğunu görüyorum, bu yüzden model platforma özgü olmasa da Java'yı örnek olarak kullanacağım.

Fikir, bazen kodu çalıştırmadan önce ve kodu çalıştırdıktan sonra her zaman aynı boilerplate'i içeren bir kodunuz olmasıdır. İyi bir örnek JDBC'dir. Asıl sorguyu çalıştırmadan ve sonuç kümesini işlemeden önce her zaman bir bağlantı alır ve bir deyim (veya hazırlanmış deyim) oluşturursunuz ve daha sonra deyim ve bağlantıyı kapatarak her zaman aynı ortak plaka temizliğini yaparsınız.

Execute-around fikri, kazan plakası kodunu çarpanlara ayırmanın daha iyi olmasıdır. Bu size bazı yazımlar kazandırır, ancak nedeni daha derindir. Burada tekrar etmeyin (KURU) ilkesi - kodu bir konuma ayırıyorsunuz, böylece bir hata varsa veya değiştirmeniz gerekiyorsa veya sadece anlamak istiyorsanız, hepsi tek bir yerde.

Bu tür faktoring-out ile biraz zor olan şey, hem "önce" hem de "sonra" parçalarının görmesi gereken referanslara sahip olmanızdır. JDBC örneğinde bu, Bağlantı ve (Hazır) ifadesini içerir. Yani işlemek için esas olarak kazan kod plaka ile hedef kodu "sarmak".

Java ile ilgili bazı yaygın durumlara aşina olabilirsiniz. Bunlardan biri sunucu filtresi. Başka bir tavsiye etrafında AOP olduğunu. Üçüncüsü, Bahar'daki çeşitli xxxTemplate sınıflarıdır. Her durumda, içine "ilginç" kodunuzun (JDBC sorgusu ve sonuç kümesi işleme diyelim) enjekte edildiği bazı sarmalayıcı nesneniz vardır. Wrapper nesnesi "before" parçasını yapar, ilginç kodu çağırır ve sonra "after" parçasını yapar.


7

Ayrıca , bu yapıyı birçok programlama dilinde araştıran ve bazı ilginç araştırma fikirleri sunan Code Sandwiches'e de bakın . Birinin neden kullanabileceği sorusu ile ilgili olarak, yukarıdaki makale bazı somut örnekler sunmaktadır:

Bu tür durumlar, bir program paylaşılan kaynakları işlediğinde ortaya çıkar. Kilitler, soketler, dosyalar veya veritabanı bağlantıları için API'ler, önceden edinmiş olduğu bir kaynağı açıkça kapatmak veya serbest bırakmak için bir program gerektirebilir. Çöp toplama özelliği olmayan bir dilde, programcı kullanmadan önce bellek ayırmaktan ve kullanımdan sonra serbest bırakmaktan sorumludur. Genel olarak, çeşitli programlama görevleri bir programın değişiklik yapmasını, bu değişiklik bağlamında çalışmasını ve değişikliği geri almasını gerektirir. Bu tür durumlara sandviç sandviç diyoruz.

Ve sonra:

Kod sandviçler birçok programlama durumunda ortaya çıkar. Birkaç yaygın örnek, kilitler, dosya tanımlayıcılar veya soket bağlantıları gibi az kaynakların edinilmesi ve serbest bırakılması ile ilgilidir. Daha genel durumlarda, program durumundaki herhangi geçici bir değişiklik için bir kod sandviçi gerekebilir. Örneğin, GUI tabanlı bir program kullanıcı girişlerini geçici olarak yoksayabilir veya bir OS çekirdeği donanım kesintilerini geçici olarak devre dışı bırakabilir. Bu durumlarda önceki durumun geri yüklenmemesi ciddi hatalara neden olur.

Neden kağıt üzerinde durmamaktadır değil bu deyim kullanmak, ancak deyim dili düzeyinde yardımı olmadan yanlış almak kolay neden açıklamak vermez:

Arızalı kodlu sandviçler en sık istisnalar ve bunların ilişkili görünmez kontrol akışı varlığında ortaya çıkar. Gerçekten de, kod sandviçlerini yönetmek için özel dil özellikleri özellikle istisnaları destekleyen dillerde ortaya çıkar.

Ancak, kusurlu kod sandviçlerinin tek nedeni istisnalar değildir. Gövde kodunda her değişiklik yapıldığında , sonraki kodu atlayan yeni kontrol yolları ortaya çıkabilir . En basit durumda, bir bakımcının yeni bir kusuru tanıtmak returniçin bir sandviçin gövdesine sadece bir ifade eklemesi gerekir , bu da sessiz hatalara yol açabilir. Tüm vücut kodu büyük ve bir önceki ve sonra yaygın olarak ayrılır, bu hatalar görsel tespit etmek zor olabilir.


İyi bir nokta, azurefrag. Cevabımı gözden geçirip genişlettim, böylece gerçekten kendi başına kendi kendine yeten bir cevap olacak. Bunu önerdiğiniz için teşekkürler.
Ben Liblit

4

Dört yaşında bir çocuğa yaptığım gibi açıklamaya çalışacağım:

örnek 1

Noel Baba şehre geliyor. Elfleri, arkasından istediklerini kodlar ve bir şeyleri değiştirmedikçe biraz tekrarlanır:

  1. Ambalaj kağıdı alın
  2. Süper Nintendo alın .
  3. Sar.

Veya bu:

  1. Ambalaj kağıdı alın
  2. Barbie bebek alın .
  3. Sar.

.... ad nauseam bir milyon farklı hediye ile bir milyon kez: farklı tek şey adım 2 olduğunu fark edin. İkinci adım farklı olan tek şey ise, o zaman Santa neden kodu çoğaltıyor, yani neden adımları çoğaltıyor? 1 ve 3 bir milyon kez mi? Bir milyon hediye, 1 ve 3 numaralı adımları gereksiz yere milyonlarca kez tekrarladığı anlamına gelir.

Etrafında yürüt bu sorunu çözmeye yardımcı olur. ve kodu ortadan kaldırmaya yardımcı olur. Adım 1 ve 3 temel olarak sabittir ve adım 2'nin değişen tek parça olmasını sağlar.

Örnek 2

Hala alamıyorsanız, işte başka bir örnek: bir sandwhich düşünün: dışarıdaki ekmek her zaman aynıdır, ancak içeride ne varsa seçtiğiniz sandviç türüne (.eg jambon, peynir, reçel, fıstık ezmesi vb.). Ekmek her zaman dışarıdadır ve yarattığınız her kum türü için milyarlarca kez tekrarlamanıza gerek yoktur.

Şimdi yukarıdaki açıklamaları okursanız, belki de anlaşılmasını daha kolay bulacaksınız. Umarım bu açıklama size yardımcı olmuştur.


+ hayal gücü için: D
Efendim. Kirpi

3

Bu bana strateji tasarım modelini hatırlatıyor . İşaret ettiğim bağlantının desen için Java kodu içerdiğine dikkat edin.

Açıkçası, başlatma ve temizleme kodu yaparak ve sadece bir strateji geçirerek "Etrafında Yürüt" işlemini gerçekleştirebilir, bu da her zaman başlatma ve temizleme koduna sarılacaktır.

Kod tekrarını azaltmak için kullanılan herhangi bir teknikte olduğu gibi, ihtiyacınız olan en az 2 vaka, belki de 3 (a la YAGNI prensibi) olana kadar kullanmamalısınız. Kod tekrarının kaldırılmasının bakımı azalttığını unutmayın (daha az kod kopyası, her kopyada düzeltmeleri kopyalamak için daha az zaman harcanması anlamına gelir), aynı zamanda bakımı da arttırır (daha fazla toplam kod). Böylece, bu hile maliyeti daha fazla kod eklemektir.

Bu tür bir teknik, sadece başlatma ve temizleme işleminden daha fazlası için yararlıdır. İşlevlerinizi çağırmayı kolaylaştırmak istediğinizde de iyidir (örn. "Sonraki" ve "önceki" düğmelerinin, ne yapılacağına karar vermek için devasa durum ifadelerine ihtiyaç duymaması için bir sihirbazda kullanabilirsiniz. sonraki / önceki sayfa.


0

Harika deyimler istiyorsanız, işte burada:

//-- the target class
class Resource { 
    def open () { // sensitive operation }
    def close () { // sensitive operation }
    //-- target method
    def doWork() { println "working";} }

//-- the execute around code
def static use (closure) {
    def res = new Resource();
    try { 
        res.open();
        closure(res)
    } finally {
        res.close();
    }
}

//-- using the code
Resource.use { res -> res.doWork(); }

Benim açık başarısız olursa (bir reentrant kilidi alma diyelim) yakın denir (eşleşen açık başarısız olmasına rağmen bir reentrant kilidi serbest bırakın diyelim).
Tom Hawtin - tackline
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.