Soyut veya sanal yöntemler kullanmalı mıyım?


11

Temel sınıfın saf bir arabirim sınıfı olmasını ve soyut veya sanal yöntem sınıfı tanımını kullanarak daha iyi bir yaklaşım olan aşağıdaki 2 örneği kullanmanın istenmediğini varsayarsak?

  • "Soyut" versiyonun avantajı, muhtemelen daha temiz görünmesi ve türetilmiş sınıfı umut verici bir uygulama yapmaya zorlamasıdır.

  • "Sanal" sürümün avantajı, diğer modüller tarafından kolayca çekilebilmesi ve soyut sürümün gerektirdiği gibi bir grup temel çerçeve eklemeden test için kullanılabilmesidir.

Özet Versiyon:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Sanal Sürüm:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Neden bir arayüzün istenmediğini varsayıyoruz?
Anthony Pegram

Kısmen bir sorun olmadan, birinin diğerinden daha iyi olduğunu söylemek zor.
Codism

@Anthony: Bu sınıfa da girecek yararlı işlevler olduğundan bir Arayüz istenmiyor.
Dunk

4
return ReturnType.NotImplemented? Ciddi anlamda? Uygulanmayan türü derleme zamanında reddedemezseniz (soyut yöntemler kullanabilirsiniz) en azından bir istisna atar .
Jan Hudec

3
@Dunk: İşte onlar vardır gerekli. İade değerleri olacaktır kontrolsüz gidin.
Jan Hudec

Yanıtlar:


15

Oyumu, eğer eşyalarını tüketiyor olsaydım, soyut yöntemler için olurdu. Bu "erken başarısız" ile birlikte gider. Tüm yöntemleri eklemek için beyan zamanında bir acı olabilir (herhangi bir iyi refactoring aracı bunu hızlı bir şekilde yapacaktır), ancak en azından sorunun hemen ne olduğunu biliyorum ve düzeltin. Neden aniden uygulanmayan bir istisna aldığımızı görmek için 6 ay ve 12 kişinin değişiklik değerinde hata ayıklamak yerine.


Beklenmedik bir şekilde NotImplemented hatasını alma konusunda iyi bir nokta. Bu, soyut tarafta bir artıdır, çünkü çalışma zamanı hatası yerine derleme zamanı alırsınız.
Dunk

3
+1 - Mirasçılar, erken başarısız olmanın yanı sıra, yeterli olduğunu düşündüklerini yapmak ve daha sonra çalışma zamanında başarısız olmak yerine, "Soyut Sınıfı Uygula" ile hangi yöntemleri uygulamak zorunda olduklarını hemen görebilirler.
Telastyn

Derleme zamanında başarısız olmanın listelemediğim bir artı olduğu ve yöntemleri hızlı bir şekilde otomatik olarak uygulamak için IDE kullanma başvurusu nedeniyle bu yanıtı kabul ettim.
Dunk

25

Sanal sürüm hataya açık ve anlamsal olarak yanlıştır.

Özet "bu yöntem burada uygulanmıyor. Bu sınıfın çalışması için onu uygulamalısın" diyor.

Virtual, "Varsayılan bir uygulamam var ancak ihtiyacınız olursa beni değiştirebilirsiniz" diyor

Nihai hedefiniz test edilebilirlikse, arayüzler normalde en iyi seçenektir. (bu sınıf bu sınıftan ziyade x yapar). Güzel çalışması için sınıflarınızı daha küçük bileşenlere ayırmanız gerekebilir.


3

Bu, sınıfınızın kullanımına bağlıdır.

Yöntemlerin makul “boş” bir uygulaması varsa, birçok yönteminiz vardır ve çoğu zaman bunlardan sadece birkaçını geçersiz kılarsınız, o zaman virtualyöntemleri kullanmak mantıklıdır. Örneğin ExpressionVisitorbu şekilde uygulanır.

Aksi takdirde, abstractyöntemleri kullanmanız gerektiğini düşünüyorum .

İdeal olarak, uygulanmayan yöntemlere sahip olmamalısınız, ancak bazı durumlarda bu en iyi yaklaşımdır. Ancak bunu yapmaya karar verirseniz, bu tür yöntemler NotImplementedExceptionözel bir değer döndürmemeli, atmalıdır .


"NotSupportedException" açık bir seçim gösterirken, "NotImplementedException" genellikle bir ihmal hatası gösterir. Bunun dışında katılıyorum.
Anthony Pegram

"X ile ilgili yükümlülükleri yerine getirmek için ne gerekiyorsa yap" şeklinde birçok yöntemin tanımlandığını belirtmek gerekir. X ile ilgili hiçbir öğesi olmayan bir nesneye böyle bir yöntem çağırmak anlamsız olabilir, ancak yine de iyi tanımlanmış olacaktır. Ayrıca, bir nesnenin X ile ilgili herhangi bir yükümlülüğü olabilir veya olmayabilir, koşulsuz olarak "X ile ilgili tüm yükümlülüklerinizi yerine getirin" demek koşulsuz olarak daha temiz ve daha etkilidir, öncelikle herhangi bir yükümlülüğü olup olmadığını sormak ve koşullu olarak bunları yerine getirmesini istemekten daha .
supercat

1

Temel sınıfınızın uyguladığı ayrı bir arabirime sahip olmayı yeniden düşünmenizi ve ardından soyut yaklaşımı izlemenizi öneririm.

Görüntüleme kodu şöyle:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Bunu yapmak şu sorunları çözer:

  1. AbstractVersion'dan türetilen nesneleri kullanan tüm kodlara sahip olmak artık IVersion arayüzünü almak için uygulanabiliyor.

  2. Ürününüzün 2. sürümü, mevcut müşteri kodunuzu kırmadan ek işlevsellik sağlamak için IVersion2 arabirimini uygulayabilir.

Örneğin.

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

Ayrıca, bu sınıfın etkili birim testini önleyen sabit kodlu bağımlılıklar içermesini önlemek için bağımlılık ters çevirme hakkında da okumaya değer.


Farklı sürümlerle başa çıkabilmek için oy verdim. Bununla birlikte, tasarımın normal bir parçası olarak arayüz sınıflarını etkili bir şekilde kullanmayı denedim ve denedim ve her zaman arayüz sınıflarının ya hiç / az değer sağlamadığını ve aslında hayatı kolaylaştırmak yerine kodu gizlediğini fark ettim. Bir arabirim sınıfı gibi, bir diğerinden miras alınan birden fazla sınıfım olması çok nadirdir ve paylaşılamayan adil bir ortaklık yoktur. Soyut sınıflar, arayüzlerin sağlamadığı yerleşik paylaşılabilir yönü elde ettikçe daha iyi çalışma eğilimindedir.
Dunk

Ve iyi sınıf isimleriyle miras kullanmak, sınıfları (ve dolayısıyla sistemi) kolayca anlamanın, zihinsel olarak bir bütün olarak birbirine bağlanması zor olan bir grup fonksiyonel arayüz sınıf isminden çok daha sezgisel bir yol sağlar. Ayrıca, arabirim sınıflarını kullanmak, bir ton ekstra sınıf oluşturma eğilimindedir, bu da sistemi başka bir şekilde anlamayı zorlaştırır.
Dunk

-2

Bağımlılık enjeksiyonu arayüzlere dayanır. Kısa bir örnek. Class Student, "IReporting" arabirimini (ReportAction yöntemiyle) uygulayan bir parametre gerektiren CreateStudent adlı bir işleve sahiptir. Bir öğrenci oluşturduktan sonra, beton sınıfı parametresinde ReportAction'ı çağırır. Sistem bir öğrenci oluşturduktan sonra e-posta gönderecek şekilde ayarlanmışsa, ReportAction uygulamasında bir e-posta gönderen somut bir sınıfta göndeririz veya ReportAction uygulamasında bir yazıcıya çıktı gönderen başka bir somut sınıfta gönderebiliriz. Kod yeniden kullanım için harika.


1
bu, önceki 5
cevapta
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.