Soyut sınıflar / yöntemler eski midir?


37

Bir sürü soyut sınıf / yöntem yaratırdım. Sonra arayüzleri kullanmaya başladım.

Şimdi, arayüzlerin soyut sınıfları modası geçmiş kılmadığından emin değilim.

Tamamen soyut bir sınıfa mı ihtiyacınız var? Bunun yerine bir arayüz oluşturun. İçinde bazı uygulamalar olan soyut bir sınıfa mı ihtiyacınız var? Bir arayüz oluşturun, bir sınıf oluşturun. Sınıfı devralın, arayüzü uygulayın. Ek bir fayda, bazı sınıfların üst sınıfa ihtiyaç duymayabileceği, ancak sadece arayüzü uygulayacağıdır.

Peki soyut sınıflar / yöntemler eski midir?


Programlama seçim diliniz arayüzleri desteklemiyorsa ne olur? C ++ için böyle olduğunu hatırlıyor gibiyim.
Bernard,

7
@Bernard: C ++ 'da, soyut bir sınıf isimden başka bir arayüzdür. 'Saf' arayüzlerden daha fazlasını da yapabilmeleri bir dezavantaj değildir.
gbjbaanb

@gbjbaanb: Sanırım. Bunları arayüz olarak kullanmayı hatırlamıyorum, daha ziyade varsayılan uygulamaları sağlamak için kullanıyorum.
Bernard

Arabirimler, nesne referanslarının "para birimidir". Genel olarak konuşursak onlar polimorfik davranışın temelidir. Soyut sınıflar, deadalnix'in mükemmel bir şekilde açıkladığı farklı bir amaca hizmet eder.
jiggy

4
Bu, "artık modası geçmiş araçlarımız var mı kullanılmıyor?" Demek gibi bir şey değil mi? Evet, çoğu zaman bir araba kullanırsın. Ama hiç bir arabadan başka bir şeye ihtiyacınız olup olmadığına bakılmaksızın, "taşıma modları kullanmaya ihtiyacım yok" demek doğru olmaz. Arayüz, herhangi bir uygulama olmadan soyut bir sınıfla aynıdır ve özel bir adı yoktur, değil mi?
Jack V.

Yanıtlar:


111

Hayır.

Arayüzler varsayılan uygulama sağlayamaz, soyut sınıflar ve metotlar sağlayabilir. Bu, özellikle çoğu durumda kod çoğaltılmasını önlemek için kullanışlıdır.

Bu aynı zamanda sıralı kuplajı azaltmanın gerçekten güzel bir yoludur. Soyut yöntem / sınıflar olmadan, şablon yöntem desenini uygulayamazsınız. Bu wikipedia makalesine bakmanı öneriyorum: http://en.wikipedia.org/wiki/Template_method_pattern


3
@deadalnix Şablon yöntemi model oldukça tehlikeli olabilir. Kolayca birleştiğinde kodla kolayca sona erebilir ve şablonlu soyut sınıfı "sadece bir tane daha" kullanmak üzere genişletmek için kodu kırmaya başlayabilirsiniz.
quant_dev

9
Bu bir anti-kalıp gibi görünüyor. Bir arayüze karşı kompozisyon ile tam olarak aynı şeyi elde edebilirsiniz. Aynı şey bazı soyut yöntemlerle kısmi sınıflar için de geçerlidir. Daha sonra, müşteri kodunu alt sınıflamaya ve geçersiz kılmaya zorlamak yerine, uygulamanın enjekte edilmesini sağlamalısınız . Bakınız: en.wikipedia.org/wiki/Composition_over_inheritance#Benefits
back2dos

4
Bu, AT ALL'a sıralı bağlamanın nasıl azaltılacağını açıklamaz. Bu hedefe ulaşmak için çeşitli yolların mevcut olduğuna katılıyorum, ancak lütfen konuştuğunuz soruna kör bir şekilde bazı ilkeleri çağırmak yerine cevap verin. Bunun neden gerçek sorunla ilgili olduğunu açıklayamazsanız, kargo kültü programlaması yaparak sona ereceksiniz.
deadalnix

3
@deadalnix: Ardışık kuplajın şablon kalıbıyla en iyi şekilde azaltıldığını söylemek , kargo kültü programlamasıdır (durum / strateji / fabrika modelini kullanarak da çalışabilir), her zaman azaltılması gerektiği varsayımıyla. Sıralı bağlanma çoğu zaman bir şey üzerinde çok ince taneli kontrolü açığa çıkarmanın sadece bir sonucudur, bir işlemden başka bir şey ifade etmez. Bu yine de, kompozisyonu kullanarak sıralı bağlamaya sahip olmayan bir sargı yazamayacağım anlamına gelmez. Aslında, bu çok daha kolay hale getirir.
back2dos

4
Java artık arayüzler için varsayılan uygulamalara sahiptir. Bu cevabı değiştirir mi?
raptortech97 20:14

15

Soyut bir yöntem var, böylece onu temel sınıfınızdan arayabilir, ancak türetilmiş bir sınıfta uygulayabilirsiniz. Yani temel sınıfınız bilir:

public void DoTask()
{
    doSetup();
    DoWork();
    doCleanup();
}

protected abstract void DoWork();

Kurulum ve temizleme faaliyetleri hakkında türetilmiş bir sınıf olmadan orta kalıpta bir delik açmanın oldukça iyi bir yolu. Soyut yöntemlerle olmadan, türetilmiş sınıf uygulamak güvenmek zorunda kalacak DoTaskve aramaya hatırlamak base.DoSetup()ve base.DoCleanup()her zaman.

Düzenle

Ayrıca, yukarıda açıkladığım şekliyle ismini bilmeden Şablon Metodu Örüntüsüne bir bağlantı gönderdiği için deadalnix'e teşekkürler . :)


2
+1, cevabınızı deadalnix'e tercih ediyorum. Bir şablon yöntemini delegeleri kullanarak daha basit bir şekilde uygulayabileceğinizi unutmayın (C # içinde):public void DoTask(Action doWork)
Joh

1
@Joh - true, ancak API'nize bağlı olarak mutlaka güzel olmaz. Eğer baz sınıfın ise Fruitve türetilmiş sınıf Apple, aramak istediğiniz myApple.Eat()değil myApple.Eat((a) => howToEatApple(a)). Ayrıca Applearamak zorunda değilsiniz base.Eat(() => this.howToEatMe()). Sadece soyut bir yöntemi geçersiz kılmak daha temiz olduğunu düşünüyorum.
Scott Whitlock

1
@Scott Whitlock: Elmaları ve meyveleri kullanma şekliniz, genel olarak delegelere miras almayı tercih edip etmeme konusunda karar vermek için gerçeklikten biraz kopuktur. Açıkçası, cevabı "bağlıdır", bu yüzden tartışmalar için çok yer bırakıyor ... Her neyse, şablon yineleme sırasında, örneğin yinelenen kodu kaldırırken, çok fazla ortaya çıktığını görüyorum. Bu gibi durumlarda, genellikle tür hiyerarşisi ile uğraşmak istemem ve kalıtımdan uzak durmayı tercih ederim. Bu tür bir ameliyat lambda ile yapmak daha kolaydır.
Joh,

Bu soru, Şablon Yöntemi modelinin basit bir uygulamasından daha fazlasını gösterir - kamusal / kamusal olmayan yön C ++ topluluğunda Sanal Arayüz Olmayan desen olarak bilinir . Genel sanal olmayan bir işleve sahip olmak sanal olanı sarmakla - başlangıçta eski hiçbir şey yapmasa da ikincisini çağırmakla birlikte - sonradan ihtiyaç duyulabilecek kurulum / temizleme, günlüğe kaydetme, profil oluşturma, güvenlik kontrolleri vb. İçin bir özelleştirme noktası bırakırsınız.
Tony,

10

Hayır, onlar eski değil.

Aslında, Soyut Sınıflar / Yöntemler ve Arayüzler arasında belirsiz fakat temel bir fark vardır.

bunlardan birinin kullanılması gereken sınıflar kümesinin paylaştıkları ortak bir davranışları varsa (ilgili sınıflar, yani), Soyut sınıflar / yöntemler için gidin.

Örnek: memur, Memur, Yönetmen -Tüm bu sınıfların hepsinde ortak olarak CalculateSalary () vardır, soyut temel sınıfları kullanın.

Sınıflarınızın aralarında ortak bir şey yoksa (İlişkili olmayan sınıflar, seçilen bağlamda) aralarında ama uygulamada oldukça farklı bir eylem varsa, Arayüz'e gidin.

Örnek: İnek, tezgah, araba, teleskopla ilgili olmayan sınıflar ancak Sıralanabilir onları bir dizide sıralamak için orada olabilir.

Polimorfik bir perspektiften yaklaşıldığında bu fark genellikle göz ardı edilir. Ancak şahsen, yukarıda açıklanan nedenden dolayı birinin diğerinden daha uygun olduğu durumlar olduğunu hissediyorum.


6

Diğer iyi cevaplara ek olarak, arayüzler ve kimsenin özel olarak bahsetmediği soyut sınıflar arasında temel bir fark vardır, yani arayüzlerin çok daha az güvenilir olması ve bu nedenle soyut sınıflardan çok daha büyük bir test yükü getirmesi . Örneğin, bu C # kodunu göz önünde bulundurun:

public abstract class Frobber
{
    private Frobber() {}
    public abstract void Frob(Frotz frotz);
    private class GreenFrobber : Frobber
    { ... }
    private class RedFrobber : Frobber
    { ... }
    public static Frobber GetFrobber(bool b) { ... } // return a green or red frobber
}

public sealed class Frotz
{
    public void Frobbit(Frobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Test etmem gereken yalnızca iki kod yolu olduğunu garanti ediyorum. Frobbit'in yazarı, frobberın kırmızı veya yeşil olduğu gerçeğine güvenebilir.

Öyleyse deriz ki:

public interface IFrobber
{
    void Frob(Frotz frotz);
}
public class GreenFrobber : IFrobber
{ ... }
public class RedFrobber : Frobber
{ ... }

public sealed class Frotz
{
    public void Frobbit(IFrobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Şimdi buradaki Frob'a olan çağrının etkileri hakkında hiçbir şey bilmiyorum . Frobbit'teki tüm kodların, IFrobber'in olası uygulamalarına karşı sağlam olmasına , hatta beceriksiz (kötü) veya aktif olarak bana veya kullanıcılarına (çok daha kötü) karşı olan kişilerin uygulamalarına karşı emin olmam gerekiyor .

Soyut sınıflar tüm bu sorunlardan kaçınmanıza izin verir; onları kullan!


1
Bahsettiğiniz problem, sınıfları açık / kapalı prensibini ihlal ettikleri bir noktaya bükmek yerine, cebirsel veri tipleri kullanarak çözülmelidir.
back2dos

1
İlk önce, bunun iyi ya da ahlaki olduğunu söylemedim . Onu ağzıma sen soktun. İkincisi (asıl amacım): Eksik bir dil özelliği için kötü bir bahaneden başka bir şey değil. Son olarak, soyut sınıfları olmayan bir çözüm var: pastebin.com/DxEh8Qfz . Bunun aksine, yaklaşımınız iç içe ve soyut sınıfları kullanır, güvenliği gerektiren tüm kodları birlikte büyük bir çamur topuna atar. RedFrobber veya GreenFrobber'ın uygulamak istediğiniz kısıtlamalara bağlanması için iyi bir neden yoktur. Kuplajı arttırır ve birçok kararda faydası olmadan kilitlenir.
back2dos

1
Soyut yöntemlerin modası geçmiş olduğunu söylemek kuşkusuz yanlıştır, ancak diğer yandan ara yüzlerde tercih edilmeleri gerektiğini iddia etmek yanıltıcıdır. Onlar sadece arayüzlerden farklı problemleri çözmek için bir araçtır.
Groo

2
"temel bir fark var [...] arayüzler soyut sınıflardan çok daha az güvenilir [...]." Kabul etmiyorum, kodunuzda göstermiş olduğunuz fark, arayüzler ve soyut sınıflar arasında önemli bir farklılık göstermek için hiçbir nedeninin olmadığını düşündüğüm erişim kısıtlamalarına dayanıyor. Öyle olabilir C #, ama soru dille agnostik.
Joh

1
@ back2dos: Sadece Eric'in çözümünün aslında bir cebirsel veri türü kullandığını belirtmek isterim: soyut bir sınıf, bir toplam türüdür. Soyut bir yöntem çağırmak, bir dizi değişken üzerinde örüntü eşleştirme ile aynıdır.
Rodrick Chapman

4

Sen kendin söyledin:

İçinde bazı uygulamalar olan soyut bir sınıfa mı ihtiyacınız var? Bir arayüz oluşturun, bir sınıf oluşturun. Sınıfı devralmak, arayüzü uygulamak

Bu, 'soyut sınıfa miras' ile karşılaştırıldığında çok fazla iş geliyor. 'Saf' bir bakış açısından koda yaklaşarak kendiniz için çalışabilirsiniz, ancak iş yüküme eklemeye pratik bir fayda sağlamaya çalışmaksızın zaten yapacak kadar işim olduğunu biliyorum.


Eğer sınıf, arayüzü (mirasız olması gerektiği gibi) arayüzü devralmazsa, bazı dillerde yönlendirme fonksiyonlarını yazmanız gerektiğini söylemeyiniz.
Sjoerd

Bahsettiğiniz ek "Çok iş", bir arayüz oluşturduğunuzu ve sınıfları uygulamanızı sağladığını gösteriyor - bu gerçekten çok iş gibi görünüyor mu? Bunun ötesinde, elbette, arayüz size arayüzü soyut bir sınıfla çalışmayan yeni bir hiyerarşiden uygulama yeteneği verir.
Bill K

4

@Deadnix yazısında yorumda bulunduğum gibi: Kısmi uygulamalar, kalıp deseni onları biçimlendirmiş olmasına rağmen, bir kalıp karşıtıdır.

Şablon şablonunun bu wikipedia örneği için temiz bir çözüm :

interface Game {
    void initialize(int playersCount);
    void makePlay(int player);
    boolean done();
    void finished();
    void printWinner();
}
class GameRunner {
    public void playOneGame(int playersCount, Game game) {
        game.initialize(playersCount);
        int j = 0;
        for (int i = 0; !game.finished(); i++)
             game.makePlay(i % playersCount);
        game.printWinner();
    }
} 
class Monopoly implements Game {
     //... implementation
}

Bu çözüm daha iyidir, çünkü kalıtım yerine kompozisyon kullanır . Şablon kalıbı, Tekel kurallarının uygulanması ile oyunların nasıl yürütüleceği konusunda bir bağımlılık ortaya koymaktadır. Ancak bunlar tamamen farklı sorumluluklardır ve onları birleştirmek için iyi bir sebep yoktur.


+1. Bir not olarak: "Kısmi uygulamalar, kalıp modelin onları resmileştirmesine rağmen bir anti-kalıptır". Vikipedi üzerindeki açıklama, deseni temiz bir şekilde tanımlar, sadece kod örneği "yanlıştır" (gerekmediğinde kalıtım kullandığı ve yukarıda gösterildiği gibi daha basit bir alternatif olduğu anlamına gelir). Başka bir deyişle, kalıbın kendisinin suçlama olduğunu düşünmüyorum, yalnızca insanların onu uygulama eğiliminde olduğunu düşünüyorum.
Joh

2

Hayır. Önerilen alternatifiniz bile soyut sınıfların kullanımını içerir. Ayrıca, dili belirtmediğiniz için, hemen devam edeceğim ve genel kodun yine de kırılganlık kalıtımından daha iyi bir seçenek olduğunu söyleyeceğim. Soyut sınıfların arayüzlere göre önemli avantajları vardır.


-1. "Genel kod vs kalıtım" sorusu ile ne ilgisi var anlamıyorum. Neden “soyut sınıfların arayüzler üzerinde önemli avantajlara sahip olduğunu” açıklamalısınız.
Joh

1

Soyut sınıflar arayüz değildir. Bunlar somutlaştırılamayan sınıflar.

Tamamen soyut bir sınıfa mı ihtiyacınız var? Bunun yerine bir arayüz oluşturun. İçinde bazı uygulamalar olan soyut bir sınıfa mı ihtiyacınız var? Bir arayüz oluşturun, bir sınıf oluşturun. Sınıfı devralın, arayüzü uygulayın. Ek bir fayda, bazı sınıfların üst sınıfa ihtiyaç duymayabileceği, ancak sadece arayüzü uygulayacağıdır.

Ama sonra soyut olmayan işe yaramaz bir sınıfa sahip olacaktınız. Temel sınıftaki işlevselliği doldurmak için soyut yöntemler gerekir.

Örneğin, bu sınıfa verilen

public abstract class Frobber {
    public abstract void Frob();

    public abstract boolean IsFrobbingNeeded { get; }

    public void FrobUntilFinished() {
        while (IsFrobbingNeeded) {
            Frob();
        }
    }
}

Bu temel işlevselliği, ne Frob()de ne olmayan bir sınıfta nasıl uygularsınız IsFrobbingNeeded?


1
Bir arayüz de başlatılamaz.
Sjoerd

@Sjoerd: Ancak paylaşılan uygulamayla bir temel sınıfa ihtiyacınız var; bu bir arayüz olamaz.
yapılandırıcı,

1

Soyut sınıfların temel rol oynadığı bir servlet çerçevesi yaratıcısıyım. Daha fazlasını söyleyeyim,% 50 durumlarda bir yöntemin geçersiz kılınması gerektiğinde yarı derleyici yöntemlere ihtiyacım var ve derleyiciden bu yöntem hakkında uyarılmadığını görmek istiyorum. Ek açıklama ekleme sorununu çözerim. Sorunuza geri dönersek, iki farklı kullanım soyut sınıf ve arayüz durumu vardır ve şu ana kadar kimse eski değildir.


0

Arayüzlerin saplantılı olduklarını sanmıyorum, fakat strateji paterni tarafından sapmış olabilirler.

Soyut bir sınıfın birincil kullanımı, uygulamanın bir bölümünü ertelemektir; "Sınıfın uygulanmasının bu kısmı farklı olabilir" demek.

Ne yazık ki, soyut bir sınıf müşteriyi bunu miras yoluyla yapmaya zorlar . Oysaki strateji kalıbı, aynı sonucu herhangi bir kalıtım olmadan elde etmenize izin verecektir. Müşteri, her zaman kendi tanımını yapmak yerine sınıfınıza örnekler verebilir ve sınıfın "uygulaması" (davranışı) dinamik olarak değişebilir. Strateji modeli ek bir artıya sahiptir; bu davranış sadece tasarım zamanında değil aynı zamanda çalışma zamanında da değiştirilebilir ve ayrıca ilgili türler arasında önemli ölçüde daha az birleşme vardır.


0

Genelde, saf arayüzlerle ilgili, çok sayıda kalıtımda kullanılan ABC'ler bile, ABC'lerden çok daha uzak, çok daha fazla bakım sorunuyla karşı karşıya kaldım. YMMV - bilmiyorum, belki ekibimiz onları yetersiz kullandı.

Bununla birlikte, eğer gerçek bir dünya analojisi kullanırsak, işlevsellik ve durumdan tamamen yoksun olan saf arayüzler için ne kadar faydası var? USB'yi örnek olarak kullanırsam, bu oldukça kararlı bir arayüzdür (bence şimdi USB 3.2'deyiz, ancak geriye dönük uyumluluk da var).

Yine de vatansız bir arayüz değil. İşlevsellikten yoksun değil. Saf bir arayüzden çok soyut bir temel sınıf gibidir. Çok özel fonksiyonel ve devlet gereklilikleri olan somut bir sınıfa daha yakındır; sadece soyutlama, limana takılan tek ikame parçadır.

Aksi taktirde, standart bir form faktörü ve bilgisayarınızda her deliğin bir şey yapması için kendi donanımı olana kadar kendi başına bir şey yapamayan daha gevşek fonksiyonel gereksinimleri olan bilgisayarınızda sadece bir "delik" olacaktır. çok daha zayıf bir standart haline gelir ve bir "delikten" ve ne yapması gerektiğine dair bir spesifikasyondan başka bir şey olmaz, ama nasıl yapılacağına dair merkezi bir hüküm yoktur. Bu arada, tüm donanım üreticileri işlevsellik eklemek ve bu “deliğe” belirtmek için kendi yollarını bulmaya çalıştıktan sonra bunu yapmanın 200 farklı yolunu bulabiliriz.

Ve bu noktada başkalarına göre farklı problemler ortaya çıkaran bazı üreticilerimiz olabilir. Şartnameyi güncellememiz gerekirse, güncellenmesi ve test edilmesi gereken şartnamelerin tamamen farklı yöntemlerle ele alınması için 200 farklı somut USB port uygulaması olabilir. Bazı üreticiler kendi aralarında paylaştığı fiili standart uygulamalar geliştirebilir (bu arayüzü uygulayan analog temel sınıfınız), ancak hepsi değil. Bazı sürümler diğerlerinden daha yavaş olabilir. Bazıları daha iyi verime sahip olabilir, ancak daha kötü gecikme süresi veya bunun tersi olabilir. Bazıları diğerlerinden daha fazla pil gücü kullanabilir. Bazıları sönebilir ve USB bağlantı noktalarıyla çalışması gereken tüm donanımlarla çalışmayabilir. Bazıları, kullanıcılarına radyasyon zehirlenmesi verme eğiliminde olan bir nükleer reaktörün çalışmasına bağlanabilir.

Ve şahsen saf arayüzlerle bulduğum şey buydu. Bir anakartın form faktörünü bir CPU vakasına karşı modellemek gibi, mantıklı oldukları bazı durumlar olabilir. Form faktörü analojileri, aslında, analog "delik" ile olduğu gibi, hemen hemen hiç vatansızdır ve işlevsellikten yoksundur. Ancak çoğu zaman takımların yakın, hatta her durumda bir şekilde daha üstün olduğunu düşünmelerini büyük bir hata olarak görüyorum.

Aksine, takımınız o kadar devasa olmadıkça iki seçenek olsa, ABC'ler tarafından arayüzlerden ziyade daha fazla vakanın daha iyi çözüleceğini düşünüyorum. korumak, sağlamak, sürdürmek. İçinde bulunduğum eski bir takımda, aslında ABC'lerin ve çoklu kalıtımın sağlanması için ve özellikle yukarıda açıklanan bakım sorunlarına cevap olarak kodlama standardını gevşetmek için çok mücadele etmek zorunda kaldım.

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.