Swift Dilinde soyut işlevler


127

Hızlı bir dilde soyut bir işlev oluşturmak istiyorum. Mümkün mü?

class BaseClass {
    func abstractFunction() {
        // How do I force this function to be overridden?
    }
}

class SubClass : BaseClass {
    override func abstractFunction() {
        // Override
    }
}

Bu, diğer sorunuza oldukça yakın ancak buradaki cevap biraz daha iyi görünüyor.
David Berry

Sorular benzerdir ancak çözümler çok farklıdır çünkü soyut bir sınıf, soyut bir işlevden farklı kullanım durumlarına sahiptir.
kev

Evet, neden ben de kapatmak için oy vermedi, ama cevaplar yararlı ötesinde olmayacak "sen edemez" Burada mevcut en iyi cevabı var :)
David Berry

Yanıtlar:


198

Swift'de soyut kavramı yoktur (Objective-C gibi) ama bunu yapabilirsiniz:

class BaseClass {
    func abstractFunction() {
        preconditionFailure("This method must be overridden") 
    } 
}

class SubClass : BaseClass {
     override func abstractFunction() {
         // Override
     } 
}

13
Alternatif olarak kullanabilirsiniz assert(false, "This method must be overriden by the subclass").
Erik

20
veyafatalError("This method must be overridden")
nathan

5
Bir yöntemin dönüş türü olduğunda, iddialar bir dönüş ifadesi gerektirecektir. Sanırım fatalError () bu nedenle daha iyi
André Fratelli

6
Ayrıca preconditionFailure("Bla bla bla"), sürüm yapılarında uygulanacak ve ayrıca bir dönüş ifadesine olan ihtiyacı ortadan kaldıracak Swift'de var. DÜZENLEME: Sadece bu yöntem temelde eşit olduğunu öğrendim fatalError()ama daha uygun bir yoldur (Daha İyi dokümantasyon, birlikte Swift tanıtılan precondition(), assert()ve assertionFailure(), okumak burada )
Kametrixom

2
ya işlevin bir dönüş türü varsa?
LoveMeow

36

İstediğin şey temel sınıf değil, protokoldür.

protocol MyProtocol {
    func abstractFunction()
}

class MyClass : MyProtocol {
    func abstractFunction() {
    }
}

Sınıfınızda abstractFunction sağlamazsanız bu bir hatadır.

Diğer davranışlar için hala temel sınıfa ihtiyacınız varsa, bunu yapabilirsiniz:

class MyClass : BaseClass, MyProtocol {
    func abstractFunction() {
    }
}

23
Bu pek işe yaramıyor. BaseClass bu boş işlevleri çağırmak isterse, protokolü de uygulaması ve ardından işlevleri gerçekleştirmesi gerekir. Ayrıca alt sınıf, işlevleri derleme zamanında uygulamaya zorlanmaz ve siz de protokolü uygulamaya zorlamazsınız.
bandejapaisa

5
Demek istediğim bu .... BaseClass'ın protokolle hiçbir ilgisi yok ve soyut bir sınıf gibi davranması için onunla bir ilgisi olmalı. Yazdığım şeyi açıklığa kavuşturmak için, "BaseClass bu boş işlevleri çağırmak istiyorsa, o zaman sizi işlevleri uygulamaya zorlayacak protokolü uygulamak zorunda kalacak - bu da Alt Sınıfın işlevleri derleme zamanında uygulamaya zorlanmamasına yol açacaktır. Çünkü BaseClass bunu zaten yaptı ".
bandejapaisa

4
Bu gerçekten "soyut" u nasıl tanımladığınıza bağlı. Soyut bir işlevin tüm amacı, onu normal sınıf şeyler yapabilen bir sınıfa koyabilmenizdir. Örneğin, bunu istememin nedeni, sizin protokollerle yapamayacağınız bir statik üye tanımlamam gerekmesidir. Bu yüzden bunun soyut bir fonksiyonun yararlı noktasını karşıladığını görmek benim için zor.
Yavru

3
Sanırım yukarıdaki yorumlarla ilgili endişenin, "bir programdaki nesnelerin, o programın doğruluğunu değiştirmeden kendi alt türlerinin örnekleriyle değiştirilebilmesi gerektiğini" belirten Liskov İkame ilkesine uymaması olduğunu düşünüyorum. Bunun soyut bir tür olduğu varsayılıyor. Bu, özellikle alt sınıftan kaynaklanan özelliklerin yaratılması ve depolanmasından temel sınıfın sorumlu olduğu Fabrika Yöntemi modeli durumunda önemlidir.
David James

2
Soyut bir temel sınıf ve bir protokol aynı şey değildir.
Shoerob

29

Soyut temel sınıfları destekleyen bir platformdan Swift'e makul miktarda kod aktarıyorum ve buna çokça giriyorum. Gerçekten istediğiniz şey soyut bir temel sınıfın işlevselliğiyse, bu, bu sınıfın hem paylaşılan tabanlı sınıf işlevselliğinin bir uygulaması (aksi halde yalnızca bir arabirim / protokol olacaktır) hem de uygulayıcı tarafından uygulanması gereken yöntemleri tanımladığı anlamına gelir. türetilmiş sınıflar.

Bunu Swift'de yapmak için bir protokole ve bir temel sınıfa ihtiyacınız olacak.

protocol Thing
{
    func sharedFunction()
    func abstractFunction()
}

class BaseThing
{
    func sharedFunction()
    {
        println("All classes share this implementation")
    }
}

Temel sınıfın paylaşılan yöntemleri uyguladığını, ancak protokolü uygulamadığını unutmayın (çünkü tüm yöntemleri uygulamaz).

Sonra türetilmiş sınıfta:

class DerivedThing : BaseThing, Thing 
{
    func abstractFunction() 
    {
        println("Derived classes implement this");
    }
}

Türetilmiş sınıf, sharedFunction'ı temel sınıftan miras alır ve protokolün bu bölümünü tatmin etmesine yardımcı olur ve protokol hala türetilmiş sınıfın abstractFunction uygulamasını gerektirir.

Bu yöntemin tek gerçek dezavantajı, temel sınıf protokolü uygulamadığından, bir protokol özelliğine / yöntemine erişime ihtiyaç duyan bir temel sınıf yönteminiz varsa, türetilmiş sınıftaki bunu geçersiz kılmanız ve buradan çağrı yapmanız gerekmesidir. temel sınıf (süper aracılığıyla) geçişi, selfböylece temel sınıf, işini yapmak için protokolün bir örneğine sahip olur.

Örneğin, sharedFunction'ın abstractFunction'ı çağırması gerektiğini varsayalım. Protokol aynı kalacak ve sınıflar artık şöyle görünecekti:

class BaseThing
{
    func sharedFunction(thing: Thing)
    {
        println("All classes share this implementation")
        thing.abstractFunction()
    }
}

class DerivedThing : BaseThing, Thing 
{
    func sharedFunction()
    {
        super.sharedFunction(self)
    }

    func abstractFunction() 
    {
        println("Derived classes implement this");
    }
}

Artık türetilmiş sınıftan paylaşılan işlev, protokolün bu bölümünü karşılamaktadır, ancak türetilmiş sınıf yine de temel sınıf mantığını makul ölçüde basit bir şekilde paylaşabilir.


4
"Temel sınıf protokolü uygulamaz" üzerine büyük bir anlayış ve iyi bir derinlik ... soyut bir sınıfın noktasıdır. Bu temel OO özelliğinin eksik olması beni şaşırttı, ancak yine, Java konusunda yetiştirildim.
Dan Rosenstark

2
Yine de uygulama, şablon yönteminin alt sınıflarda uygulanan çok sayıda soyut yöntemi çağırdığı Template Function yönteminin sorunsuz bir şekilde uygulanmasına izin vermez. Bu durumda, onları hem süper sınıfta normal yöntemler olarak, hem protokoldeki yöntemler olarak hem de alt sınıfta uygulamalar olarak yazmalısınız. Pratikte aynı şeyi üç kez yazmanız ve feci bir yazım hatası yapmadığınızdan emin olmak için yalnızca geçersiz kılma kontrolüne güvenmeniz gerekir! Umarım şimdi Swift geliştiricilere açık, tam teşekküllü soyut fonksiyonun tanıtılması.
Fabrizio Bartolomucci

1
"Korumalı" anahtar kelime eklenerek çok fazla zaman ve kod tasarrufu sağlanabilir.
Shoerob

23

Bu, Apple'ın UIKit'te soyut yöntemleri ele alma şeklinin "resmi" yolu gibi görünüyor. Şuna UITableViewControllerve nasıl çalıştığına bir göz atın UITableViewDelegate. Yaptığınız ilk şeylerden biri bir satır eklemektir:delegate = self . İşte hile de bu.

1. Soyut yöntemi bir protokole koyun
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Temel sınıfınızı yazın
/// It takes an implementation of the protocol as property (same like the delegate in UITableViewController does)
/// And does not implement the protocol as it does not implement the abstract methods. It has the abstract methods available in the `delegate`
class BaseClassX {
    var delegate: AbstractMethodsForClassX!

    func doSomethingWithAbstractMethod() -> String {
        return delegate.abstractMethod() + " - And I believe it"
    }
}
3. Alt Sınıf (lar) ı yazın.
/// First and only additional thing you have to do, you must set the "delegate" property
class ClassX: BaseClassX, AbstractMethodsForClassX {
    override init() {
        super.init()
        delegate = self
    }

    func abstractMethod() -> String {return "Yes, this works!"}
}
İşte tüm bunları nasıl kullanacaksın
let x = ClassX()
x.doSomethingWithAbstractMethod()

Çıkışı görmek için Playground ile kontrol edin.

Bazı Açıklamalar

  • İlk olarak, birçok cevap zaten verildi. Umarım birisi buna kadar her şeyi bulur.
  • Asıl soru, uygulayan bir model bulmaktı:
    • Bir sınıf, türetilmiş alt sınıflarından birinde uygulanması gereken bir yöntemi çağırır (geçersiz kılınır)
    • En iyi durumda, yöntem alt sınıfta geçersiz kılınmamışsa, derleme süresi sırasında bir hata alın
  • Soyut yöntemlerle ilgili olan şey, bir arayüz tanımının bir karışımı ve temel sınıftaki gerçek bir uygulamanın parçası olmalarıdır. İkisi de aynı anda. Swift çok yeni ve çok temiz tanımlandığı için, böyle bir rahatlığı değil, "kirli" kavramları (henüz) var.
  • Bana göre (zavallı eski bir Java adamı), bu sorun zaman zaman gelişir. Bu gönderideki tüm cevapları okudum ve bu sefer, en azından bana uygun görünen bir model bulduğumu düşünüyorum.
  • Güncelleme : Görünüşe göre Apple'daki UIKit uygulayıcıları aynı kalıbı kullanıyor. UITableViewControlleruygular, UITableViewDelegateancak yine de delegateözelliği açıkça ayarlayarak temsilci olarak kaydedici olması gerekir .
  • Bunların hepsi Playground of Xcode 7.3.1'de test edildi

Belki mükemmel değil, çünkü sınıfın arayüzünü diğer sınıflardan gizlemekte sorun yaşıyorum, ancak Swift'de klasik Fabrika Yöntemini uygulamak için ihtiyacım olan şey bu.
Tomasz Nazarenko

Hmmm. Bu cevabın görünüşünü daha çok beğeniyorum ama uygun olduğundan da emin değilim. Yani, bir avuç farklı nesne türü için bilgi görüntüleyebilen bir ViewController'ım var, ancak bu bilgileri aynı yöntemlerle göstermenin amacı aynı, ancak bilgileri nesneye göre farklı şekilde çekip bir araya getiriyor. . Bu nedenle, bu VC için bir üst temsilci sınıfım ve her nesne türü için bu temsilcinin bir alt sınıfım var. Verilen nesneye göre bir delege oluşturuyorum. Bir örnekte, her nesnenin depolanan bazı ilgili yorumları vardır.
Jake T.

Bu yüzden getCommentsebeveyn delege hakkında bir yöntemim var . Bir avuç yorum türü var ve her bir nesneyle alakalı olanları istiyorum. Bu nedenle, delegate.getComments()bu yöntemi geçersiz kılmazsam, alt sınıfların yaptığımda derlenmemesini istiyorum . VC , sizin örneğinizde veya sizin örneğinizde bir ParentDelegatenesnesi olduğunu bilir , ancak soyutlanmış yönteme sahip değildir. VC'nin özellikle . delegateBaseClassXBaseClassXSubclassedDelegate
Jake T.

Umarım seni doğru anlarım. Değilse, biraz kod ekleyebileceğiniz yeni bir soru sorabilir misiniz? Temsilci atanıp ayarlanmadığına bakılmaksızın, sadece doSomethingWithAbstractMethod kontrolünde bir if-ifadesine sahip olmanız gerektiğini düşünüyorum.
jboi

Delegeye kendini atamanın bir tutma döngüsü yarattığından bahsetmeye değer. Belki onu zayıf olarak ilan etmek daha iyidir (bu genellikle delegeler için iyi bir fikirdir)
Enricoza

7

Bunu yapmanın bir yolu, temel sınıfta tanımlanan isteğe bağlı bir kapatma kullanmaktır ve çocuklar bunu uygulamayı ya da uygulamamayı seçebilirler.

class BaseClass {
    var abstractClosure?:(()->())?
    func someFunc()
    {
        if let abstractClosure=abstractClosure
        {
            abstractClosure()
        }
    } 
}

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}

Bu yaklaşımla ilgili sevdiğim şey, miras alan sınıfta bir protokol uygulamayı hatırlamak zorunda olmamam. ViewControllers'ım için bir temel sınıfım var ve bu, temel sınıf işlevselliği tarafından çağrılabilecek ViewController'a özgü, isteğe bağlı işlevselliği (örn. Uygulama etkin hale geldi, vb.) Bu şekilde uyguluyorum.
ProgrammierTier

6

Pekala, oyuna geç kaldığımı ve meydana gelen değişikliklerden yararlanıyor olabileceğimi biliyorum. Bunun için üzgünüm.

Her durumda, cevabıma katkıda bulunmak isterim, çünkü test yapmayı ve çözümleri ile fatalError() is, AFAIK, test edilemez ve istisnaları olanların test edilmesi çok daha zor.

Daha hızlı bir yaklaşım kullanmanızı öneririm . Amacınız, bazı ortak ayrıntıları olan, ancak tam olarak tanımlanmamış bir soyutlama, yani soyut yöntem (ler) tanımlamaktır. Soyutlamada hem tanımlanan hem de tanımlanmayan tüm beklenen yöntemleri tanımlayan bir protokol kullanın. Ardından, durumunuzda tanımlanan yöntemleri uygulayan bir protokol uzantısı oluşturun. Son olarak, türetilmiş herhangi bir sınıf, protokolü uygulamalıdır; bu, tüm yöntemler anlamına gelir, ancak protokol uzantısının parçası olanların uygulamalarına zaten sahiptir.

Örneğinizi tek bir somut işlevle genişletmek:

protocol BaseAbstraction {
    func abstractFunction() {
        // How do I force this function to be overridden?
    }
}

extension BaseAbstraction {
    func definedFunction() {
        print("Hello")
}

class SubClass : BaseAbstraction {
    func abstractFunction() {
        // No need to "Override". Just implement.
    }
}

Bunu yaparak derleyicinin yine arkadaşınız olduğuna dikkat edin. Yöntem "geçersiz kılınmamış" ise, fatalError()çalışma zamanında meydana gelebilecek istisnalar veya karşılaşabileceğiniz istisnalar yerine derleme zamanında bir hata alırsınız .


1
En iyi cevabı Imo.
Apfelsaft

1
İyi cevap, ancak temel soyutlamanız mülkleri depolamak için yeterli değil
joehinkle11

5

Şu an ne yaptığını anlıyorum, bence bir protokol kullansan daha iyi olur

protocol BaseProtocol {
    func abstractFunction()
}

Ardından, sadece protokole uyarsınız:

class SubClass : BaseProtocol {

    func abstractFunction() {
        // Override
        println("Override")
    }
}

Sınıfınız aynı zamanda bir alt sınıfsa, protokoller Süper Sınıfı izler:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

3

assertSoyut yöntemleri zorlamak için anahtar kelimeyi kullanma :

class Abstract
{
    func doWork()
    {
        assert(false, "This method must be overriden by the subclass")
    }
}

class Concrete : Abstract
{
    override func doWork()
    {
        println("Did some work!")
    }
}

let abstract = Abstract()
let concrete = Concrete()

abstract.doWork()    // fails
concrete.doWork()    // OK

Ancak, Steve Waddicor'un bahsettiği gibi, muhtemelen protocolonun yerine bir tane istiyorsunuz .


Soyut yöntemlerin yararı, kontrollerin derleme zamanında yapılmasıdır.
Fabrizio Bartolomucci

assert kullanmayın, çünkü arşivleme sırasında 'Dönmesi beklenen bir işlevde eksik dönüş' hatası alacaksınız. FatalError kullanın ("Bu yöntem alt sınıf tarafından geçersiz kılınmalıdır")
Zaporozhchenko Oleksandr

2

Soruyu anlıyorum ve aynı çözümü arıyordum. Protokoller Soyut yöntemlerle aynı değildir.

Bir protokolde, sınıfınızın bu tür bir protokole uygun olduğunu belirtmeniz gerekir; soyut bir yöntem, bu yöntemi geçersiz kılmanız gerektiği anlamına gelir.

Başka bir deyişle, protokoller isteğe bağlı bir türdür, temel sınıfı ve protokolü belirtmeniz gerekir, protokolü belirtmezseniz, bu tür yöntemleri geçersiz kılmanız gerekmez.

Soyut bir yöntem, bir temel sınıf istediğiniz, ancak aynı olmayan kendi yönteminizi veya iki yönteminizi uygulamanız gerektiği anlamına gelir.

Aynı davranışa ihtiyacım var, bu yüzden bir çözüm arıyordum. Sanırım Swift böyle bir özelliği eksik.


1

Bu sorunun başka bir alternatifi daha var, ancak @ jaumard'ın önerisiyle karşılaştırıldığında hala bir dezavantajı var; bir dönüş ifadesi gerektirir. Bunu talep etme noktasını kaçırmama rağmen, çünkü doğrudan bir istisna atmaktan ibarettir:

class AbstractMethodException : NSException {

    init() {
        super.init(
            name: "Called an abstract method",
            reason: "All abstract methods must be overriden by subclasses",
            userInfo: nil
        );
    }
}

Ve sonra:

class BaseClass {
    func abstractFunction() {
        AbstractMethodException.raise();
    }
}

Bundan sonra gelen her şeye ulaşılamaz, bu yüzden neden geri dönüşü zorladığını anlamıyorum.


Şunu musunuz AbstractMethodException().raise()?
Joshcodes

Err ... Muhtemelen. Şu anda test edemiyorum, ancak bu şekilde çalışıyorsa, evet yerine
André Fratelli

1

Faydalı olacak mı bilmiyorum ama SpritKit oyununu oluşturmaya çalışırken soyutlanmış yöntemle benzer bir sorun yaşadım. İstediğim şey, move (), run () vb. Gibi yöntemlere sahip soyut bir Animal sınıfıdır, ancak hareketli grafik adları (ve diğer işlevler) sınıf çocukları tarafından sağlanmalıdır. Bu yüzden şöyle bir şey yaptım (Swift 2 için test edildi):

import SpriteKit

// --- Functions that must be implemented by child of Animal
public protocol InheritedAnimal
{
    func walkSpriteNames() -> [String]
    func runSpriteNames() -> [String]
}


// --- Abstract animal
public class Animal: SKNode
{
    private let inheritedAnimal: InheritedAnimal

    public init(inheritedAnimal: InheritedAnimal)
    {
        self.inheritedAnimal = inheritedAnimal
        super.init()
    }

    public required init?(coder aDecoder: NSCoder)
    {
        fatalError("NSCoding not supported")
    }

    public func walk()
    {
        let sprites = inheritedAnimal.walkSpriteNames()
        // create animation with walking sprites...
    }

    public func run()
    {
        let sprites = inheritedAnimal.runSpriteNames()
        // create animation with running sprites
    }
}


// --- Sheep
public class SheepAnimal: Animal
{
    public required init?(coder aDecoder: NSCoder)
    {
        fatalError("NSCoding not supported")
    }

    public required init()
    {
        super.init(inheritedAnimal: InheritedAnimalImpl())
    }

    private class InheritedAnimalImpl: InheritedAnimal
    {
        init() {}

        func walkSpriteNames() -> [String]
        {
            return ["sheep_step_01", "sheep_step_02", "sheep_step_03", "sheep_step_04"]
        }

        func runSpriteNames() -> [String]
        {
            return ["sheep_run_01", "sheep_run_02"]
        }
    }
}
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.