Zaman uyumsuz işlevleri gösteren bir arabirim sızdıran bir soyutlama mı?


13

Bağımlılık Enjeksiyon Prensipleri, Uygulamaları ve Desenler kitabını okuyorum ve kitapta iyi tarif edilen sızdıran soyutlama kavramını okudum.

Bu gün bağımlılık enjeksiyon kullanarak bir C # kod tabanı refactoring böylece engelleme yerine async çağrıları kullanılır. Bunu yaparken kod tabanımdaki soyutlamaları temsil eden ve zaman uyumsuz çağrıların kullanılabilmesi için yeniden tasarlanması gereken bazı arayüzleri düşünüyorum.

Örnek olarak, uygulama kullanıcıları için bir havuzu temsil eden aşağıdaki arayüzü düşünün:

public interface IUserRepository 
{
  Task<IEnumerable<User>> GetAllAsync();
}

Kitap tanımına göre, sızdıran bir soyutlama, akılda belirli bir uygulama ile tasarlanmış bir soyutlamadır, böylece bazı uygulama detayları soyutlamanın kendisinden "sızar".

Sorum şu: IUserRepository gibi asenkron düşünülerek tasarlanmış bir arayüzü Sızdıran Soyutlama örneği olarak düşünebilir miyiz?

Tabii ki tüm olası uygulamaların eşzamansızlık ile bir ilgisi yoktur: sadece işlem dışı uygulamalar (SQL uygulaması gibi) yapar, ancak bir bellek deposunda eşzamanlılık gerekmez (aslında arayüzün bir bellek versiyonunun uygulanması muhtemelen daha fazladır arayüz zaman uyumsuz yöntemler ortaya çıkarsa zor olabilir, örneğin yöntem uygulamalarında muhtemelen Task.CompletedTask veya Task.FromResult (kullanıcılar) gibi bir şey döndürmeniz gerekir).

Bunun hakkında ne düşünüyorsun ?


@Neil muhtemelen anladım. Görev veya Görev <T> döndüren yöntemleri ortaya çıkaran bir arabirim, sızdıran bir soyutlama değildir, yalnızca görevleri içeren imzalı bir sözleşmedir. Görev veya Görev <T> döndüren bir yöntem, zaman uyumsuz bir uygulama olması anlamına gelmez (örneğin, Task.CompletedTask kullanarak tamamlanmış bir görev oluşturursam zaman uyumsuz bir uygulama yapmıyorum). Bunun tersine, C # içindeki zaman uyumsuz uygulama, bir zaman uyumsuz yönteminin dönüş türünün Görev veya Görev <T> türünde olmasını gerektirir. Başka bir deyişle, arayüzümün tek "sızdıran" yönü adlarındaki asenkron sonekidir
Enrico Massone

@Neil aslında tüm zaman uyumsuz yöntemlerin "Zaman uyumsuz" ile biten bir isim olması gerektiğini belirten bir adlandırma kılavuzu vardır. Ancak bu, bir Görev veya Görev <T> döndüren bir yöntemin Async sonekiyle adlandırılması gerektiği anlamına gelmez, çünkü hiçbir zaman uyumsuz çağrı kullanılarak uygulanabilir.
Enrico Massone

6
Bir yöntemin 'zaman uyumsuzluğunun' a döndürdüğü gerçeğiyle belirtildiğini iddia ediyorum Task. Zaman uyumsuzluk yöntemlerini zaman uyumsuzluk kelimesi ile sonlandırma yönergeleri, aksi takdirde aynı API çağrıları (dönüş türüne göre C # cant dağıtımı) arasında ayrım yapmaktı. Şirketimizde hepsini bir araya getirdik.
richzilla

Yöntemin asenkron doğasının neden soyutlamanın bir parçası olduğunu açıklayan bir takım cevaplar ve yorumlar vardır. Daha ilginç bir soru, bir dilin veya programlama API'sının bir yöntemin işlevselliğini yürütülme biçiminden, artık Görev dönüş değerlerine veya zaman uyumsuzluk işaretleyicilerine ihtiyaç duymadığımız noktaya nasıl ayırabileceğidir. İşlevsel programlama insanları bunu daha iyi anladılar. F # ve diğer dillerde asenkron yöntemlerin nasıl tanımlandığını düşünün.
Frank Hileman

2
:-) -> "Fonksiyonel programlama insanları" ha. Async senkronize olmaktan daha fazla sızıntı yapmaz, sadece varsayılan olarak senkronizasyon kodu yazmaya alışkın olduğumuz için öyle görünüyor. Hepimiz varsayılan olarak zaman uyumsuzluk kodlamışsak, senkronize bir işlev sızıntılı görünebilir.
StarTrekRedneck

Yanıtlar:


8

Tabii ki, sızdıran soyutlamaların yasasını çağırabilir , ancak bu özellikle ilginç değildir, çünkü tüm soyutlamaların sızdıran bir durumdur. Bu kavrayışa karşı ve tartışılabilir, ancak soyutlama ile ne kastettiğimizi ve sızıntı ile ne kastettiğimizi anlamazsak bu işe yaramaz . Bu nedenle, önce bu terimlerin her birini nasıl gördüğümü anlatmaya çalışacağım:

Soyutlamalar

En sevdiğim soyutlama tanımı Robert C. Martin'in APPP'sinden türetildi :

"Bir soyutlama, temelin yükseltilmesi ve ilgisizliğin ortadan kaldırılmasıdır."

Dolayısıyla, arayüzler kendi başlarına soyutlama değildir . Onlar sadece önemli olan yüzeye çıkarırlar ve gerisini gizlerlerse soyutlamalardır.

sızdıran

Bağımlılık Enjeksiyonu Prensipleri, Desenler ve Uygulamaları kitabı , Bağımlılık Enjeksiyonu (DI) bağlamında sızdıran soyutlama terimini tanımlar . Bu bağlamda polimorfizm ve SOLID ilkeleri büyük rol oynamaktadır.

Gönderen Bağımlılık Inversion İlke (DIP) aşağıda belirtildiği tekrar APPP alıntı yaparak şöyle ki:

"istemciler [...] soyut arayüzlere sahipler"

Bunun anlamı, istemcilerin (çağrı kodu) gereksinim duydukları soyutlamaları tanımlamaları ve sonra da bu soyutlamayı uygulamanızdır.

Bir sızdıran soyutlama , bana göre, her nasılsa müşteri etmediğini bazı işlevleri dahil ederek DIP ihlal eden bir soyutlamadır gerek .

Senkron bağımlılıklar

Bir iş mantığı parçası uygulayan bir istemci, DI'yi genellikle veritabanları gibi belirli uygulama ayrıntılarından ayırmak için kullanır.

Bir restoran rezervasyonu isteğini işleyen bir etki alanı nesnesini düşünün:

public class MaîtreD : IMaîtreD
{
    public MaîtreD(int capacity, IReservationsRepository repository)
    {
        Capacity = capacity;
        Repository = repository;
    }

    public int Capacity { get; }
    public IReservationsRepository Repository { get; }

    public int? TryAccept(Reservation reservation)
    {
        var reservations = Repository.ReadReservations(reservation.Date);
        int reservedSeats = reservations.Sum(r => r.Quantity);

        if (Capacity < reservedSeats + reservation.Quantity)
            return null;

        reservation.IsAccepted = true;
        return Repository.Create(reservation);
    }
}

Burada, IReservationsRepositorybağımlılık sadece müşteri, MaîtreDsınıf tarafından belirlenir :

public interface IReservationsRepository
{
    Reservation[] ReadReservations(DateTimeOffset date);
    int Create(Reservation reservation);
}

MaîtreDSınıfın eşzamansız olması gerekmediğinden bu arabirim tamamen eşzamanlıdır .

Asenkron bağımlılıklar

Arayüzü kolayca eşzamansız olacak şekilde değiştirebilirsiniz:

public interface IReservationsRepository
{
    Task<Reservation[]> ReadReservations(DateTimeOffset date);
    Task<int> Create(Reservation reservation);
}

MaîtreDSınıf Ancak gelmez gerek şimdi DIP ihlal edilmektedir, bu yöntemlerin asenkron olmak. Bunu sızdıran bir soyutlama olarak görüyorum, çünkü bir uygulama detayı müşteriyi değişmeye zorluyor. TryAcceptYöntem şimdi de asenkron olmak zorundadır:

public async Task<int?> TryAccept(Reservation reservation)
{
    var reservations =
        await Repository.ReadReservations(reservation.Date);
    int reservedSeats = reservations.Sum(r => r.Quantity);

    if (Capacity < reservedSeats + reservation.Quantity)
        return null;

    reservation.IsAccepted = true;
    return await Repository.Create(reservation);
}

Etki alanı mantığının eşzamansız olması için temel bir gerekçe yoktur, ancak uygulamanın eşzamansızlığını desteklemek için bu artık gereklidir.

Daha iyi seçenekler

NDC Sydney 2018'de bu konu hakkında bir konuşma yaptım . İçinde, sızıntı yapmayan bir alternatif de belirtiyorum. Bu konuşmayı 2019'da da birkaç konferansta vereceğim, ancak şimdi Async enjeksiyonunun yeni başlığıyla yeniden markalaştım .

Konuşmaya eşlik edecek bir dizi blog yayını da yayınlamayı planlıyorum. Bu makaleler zaten yazıldı ve makale kuyruğumda oturuyor, yayınlanmasını bekliyor, bu yüzden bizi izlemeye devam edin.


Bana göre bu bir niyet meselesi. Eğer soyutlamam sanki bir şekilde davranmalı gibi görünüyorsa, ama bazı detaylar ya da kısıtlamalar sunulduğu gibi soyutlamayı bozarsa, bu sızdıran bir soyutlamadır. Ama bu durumda, size açıkça operasyonun eşzamansız olduğunu sunuyorum - soyutlamaya çalıştığım şey bu değil. Zihnimde bir SQL veritabanı olduğu gerçeğini soyutlamaya çalıştığım (akıllıca ya da değil) örneğinizden farklıdır ve hala bir bağlantı dizgisini açığa çıkarıyorum. Belki de anlambilim / perspektif meselesidir.
Ant P

Yani bir soyutlamanın asla "kendiliğinden" sızdırmadığını söyleyebiliriz, bunun yerine belirli bir uygulamanın bazı detaylarının maruz kalan üyelerden sızması ve tüketicinin soyutlama şeklini tatmin etmek için uygulamasını değiştirmesini kısıtlaması sızıntılı bir durumdur. .
Enrico Massone

2
İlginç bir şekilde, açıklamanızda vurguladığınız nokta, tüm bağımlılık enjeksiyon hikayesinin en yanlış anlaşılmış noktalarından biridir. Bazen geliştiriciler bağımlılık tersine çevirme prensibini unutur ve önce soyutlamayı tasarlamaya çalışır ve daha sonra soyutlamanın kendisi ile başa çıkmak için tüketici tasarımını uyarlarlar. Bunun yerine, işlem ters sırada yapılmalıdır.
Enrico Massone

11

Hiç de sızdıran bir soyutlama değil.

Eşzamansız olmak, bir işlev tanımında temel bir değişikliktir - çağrı geri döndüğünde görev tamamlanmadığı anlamına gelir, ancak aynı zamanda program akışınızın uzun bir gecikmeyle değil, hemen devam edeceği anlamına gelir. Aynı görevi yapan bir eşzamansız ve eşzamanlı işlev aslında farklı işlevlerdir. Eşzamansız olmak bir uygulama detayı değildir . Bir işlev tanımının bir parçasıdır.

Eğer fonksiyon fonksiyonun eşzamansız hale getirilme şeklini ortaya çıkarırsa, bu sızdırabilir. Nasıl uygulandığını umursamalısınız (yapmamalısınız / yapmamalısınız).


5

asyncBir yöntemin özelliği, belirli bir özen ve kullanım gerekli olduğunu belirten bir etikettir. Bu nedenle, dünyaya sızması gerekiyor . Eşzamansız işlemlerin düzgün bir şekilde oluşturulması son derece zordur, bu nedenle API kullanıcısına bilgi vermek önemlidir.

Bunun yerine, kitaplığınız kendi içindeki tüm eşzamansız etkinlikleri düzgün bir şekilde asyncyönetiyorsa, API'dan "dışarı sızmasına" izin vermeyebilirsiniz.

Yazılımda dört zorluk derecesi vardır: veri, kontrol, alan ve zaman. Eşzamansız işlemler dört boyutu da kapsamaktadır, bu nedenle en fazla dikkat gerektirir.


Ben sizin görüşünüze katılıyorum, ama "sızıntı" kötü bir şey anlamına geliyor, yani "sızdıran soyutlama" terimi - soyutlamada istenmeyen bir şey. Eşzamansız vs eşitleme durumunda, hiçbir şey sızdırmaz.
StarTrekRedneck

2

sızdıran bir soyutlama, belirli bir uygulama göz önünde bulundurularak tasarlanmış bir soyutlamadır, böylece bazı uygulama ayrıntıları soyutlamanın kendisinden "sızar".

Pek değil. Bir soyutlama, daha karmaşık bir somut şey veya problemin bazı unsurlarını göz ardı eden kavramsal bir şeydir (şeyi / problemi daha basit, izlenebilir veya başka bir fayda nedeniyle yapmak). Bu nedenle, gerçek şeyden / problemden mutlaka farklıdır ve bu nedenle bazı vakaların alt kümelerinde sızıntı olacaktır (yani, tüm soyutlamalar sızıntılıdır, tek soru ne ölçüde - yani, hangi durumlarda soyutlamadır) bizim için yararlı, uygulanabilirlik alanı nedir).

Bununla birlikte, yazılım soyutlamaları söz konusu olduğunda, bazen (veya belki de çoğu zaman yeterli mi?) Görmezden gelmeyi seçtiğimiz ayrıntılar aslında göz ardı edilemez çünkü yazılımın bizim için önemli olan bazı yönlerini (performans, sürdürülebilirlik, ...) etkiler. . Bu nedenle, sızdıran bir soyutlama, belirli ayrıntıları göz ardı etmek için tasarlanan bir soyutlamadır (bunu yapmak mümkün ve yararlı olduğu varsayımıyla), ancak daha sonra bu detayların bazılarının pratikte önemli olduğu ortaya çıktı (göz ardı edilemez, bu yüzden "sızdırmak").

Dolayısıyla, bir uygulamanın detayını ortaya koyan bir arayüz kendiliğinden sızıntı yapmaz (ya da daha ziyade, tek başına görüntülenen bir arayüz kendi içinde bir sızdıran soyutlama değildir); bunun yerine, sızıntı arayüzü uygulayan koda (arayüz tarafından temsil edilen soyutlamayı gerçekten destekleyebiliyor mu) ve ayrıca müşteri kodu tarafından yapılan varsayımlara da (ifade edileni tamamlayan kavramsal bir soyutlamaya karşılık gelir) ancak kodun içinde ifade edilemez (örneğin, dilin özellikleri yeterince ifade edici değildir, bu yüzden dokümanda vb. tanımlayabiliriz)).


2

Aşağıdaki örnekleri düşünün:

Bu, adı geri gelmeden önce ayarlayan bir yöntemdir:

public void SetName(string name)
{
    _dataLayer.SetName(name);
}

Bu, adı ayarlayan bir yöntemdir. Arayan, döndürülen görev tamamlanana kadar adın ayarlandığını varsayamaz ( IsCompleted= true):

public Task SetName(string name)
{
    return _dataLayer.SetNameAsync(name);
}

Bu, adı ayarlayan bir yöntemdir. Arayan, döndürülen görev tamamlanana kadar adın ayarlandığını varsayamaz ( IsCompleted= true):

public async Task SetName(string name)
{
    await _dataLayer.SetNameAsync(name);
}

S: Hangisi diğer ikisine ait değil?

C: Async yöntemi, tek başına duran yöntem değildir. Tek başına duran, void döndüren yöntemdir.

Bana göre, buradaki "sızıntı" asyncanahtar kelime değildir ; yöntemin bir Görev döndürmesi gerçeğidir. Ve bu bir sızıntı değil; bu prototipin ve soyutlamanın bir parçası. Görevi döndüren bir zaman uyumsuz yöntem, bir görevi döndüren zaman uyumlu bir yöntem tarafından verilen sözün aynısını verir.

Yani hayır, asynckendiliğinden sızan bir soyutlama oluşturduğunu düşünmüyorum . Ancak, arabirimi (soyutlama) değiştirerek "sızan" bir Görevi döndürmek için prototipi değiştirmeniz gerekebilir. Ve bu soyutlamanın bir parçası olduğu için, tanımı gereği bir sızıntı değildir.


0

Bu sızan soyutlama ve eğer yalnızca, değil bir zaman uyumsuz çağrı oluşturmak için tüm uygulayan sınıfları niyetinde. Örneğin, desteklediğiniz her veritabanı türü için bir tane olmak üzere birden çok uygulama oluşturabilirsiniz ve bu, programınız boyunca kullanılan tam uygulamayı bilmeniz gerekmediğini varsayarak mükemmel olur.

Eşzamansız bir uygulamayı kesinlikle uygulayamasanız da, adın olması gerektiği anlamına gelir. Koşullar değişirse ve herhangi bir nedenden dolayı eşzamanlı bir çağrı olabilirse, bir isim değişikliğini düşünmeniz çok iyi olabilir, bu yüzden tavsiyem bunu ancak bunun çok muhtemel olacağını düşünmüyorsanız geleceği.


0

İşte karşıt bir bakış açısı.

Çünkü biz sadece yerine isteyen başladı dönen Fooiçin gitmedi . Verilen, bazen etkileşimde bulunuyoruz, ancak çoğu gerçek dünya kodunda onu görmezden geliyoruz ve sadece .Task<Foo>TaskFooTaskFoo

Dahası, uygulama zaman uyumsuz olsa da olmasa da, zaman uyumsuz davranışı desteklemek için arayüzler tanımlarız.

Aslında, geri dönen bir arabirim Task<Foo>, uygulamanın, gerçekten de öyle olup olmadığına bakılmaksızın, belki de umursasanız da olmasanız bile, eşzamansız olduğunu söyler. Eğer bir soyutlama bize onun uygulanması hakkında bilmemiz gerekenden daha fazlasını anlatıyorsa, sızıntılıdır.

Uygulamamız asenkron değilse, onu asenkron olarak değiştiririz ve ardından soyutlamayı ve onu kullanan her şeyi değiştirmeliyiz, bu çok sızdıran bir soyutlamadır.

Bu bir yargı değil. Diğerlerinin işaret ettiği gibi, tüm soyutlamalar sızıntı yapar. Bunun daha büyük bir etkisi vardır, çünkü kodumuzda async / bir dalgalanma etkisi gerektirir, çünkü bunun sonunda bir yerde aslında asenkron olan bir şey olabilir .

Bu bir şikayet gibi mi geliyor? Niyetim bu değil, ama bence bu doğru bir gözlem.

İlgili bir nokta, "bir arayüz bir soyutlama değildir" iddiasıdır. Mark Seeman'ın kısaca söylediği şey biraz suistimal edildi.

"Soyutlama" tanımı .NET'de bile "arayüz" değildir. Soyutlamalar başka şekillerde de olabilir. Bir arayüz zayıf bir soyutlama olabilir veya uygulanmasını o kadar yakından yansıtabilir ki, bir anlamda hiç de soyutlama değildir.

Ama kesinlikle soyutlamalar yaratmak için arayüzler kullanıyoruz. Yani "arayüzler soyutlama değildir" diye atmak bir soru arayüzlerden bahseder ve soyutlamalar aydınlatıcı değildir.


-2

GetAllAsync()aslında zaman uyumsuz? Yani "async" in adında olduğundan emin olabilirim, ama bu kaldırılabilir. Tekrar soruyorum ... Task<IEnumerable<User>>Eşzamanlı olarak çözülen bir işlev uygulamak imkansız mı ?

Net Tasktürünün özelliklerini bilmiyorum , ancak işlevi eşzamanlı olarak uygulamak imkansızsa, o zaman sızıntılı bir soyutlama olduğundan (bu şekilde) emin olun. Ben do bir olsaydı biliyoruz IObservablebir Görev yerine, bu olabilir fonksiyon bilir dışında eşzamanlı veya zaman uyumsuz bir şey öylesine uyugulanması ve bu nedenle söz konusu gerçeği sızıntı değil.


Task<T> zaman uyumsuz demektir . Görev nesnesini hemen alırsınız, ancak kullanıcı sırasını beklemek zorunda kalabilir
Caleth

Mayıs o zaman uyumsuz ille olduğu anlamına gelmez beklemek zorunda. Shall zaman uyumsuz anlamına gelecektir bekleyin. Muhtemelen, altta yatan görev zaten yürütülmüşse, beklemek zorunda değilsiniz .
Daniel T.
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.