Swift Dilinde soyut dersler


141

Swift dilinde soyut bir sınıf yaratmanın bir yolu var mı, yoksa bu Objective-C gibi bir sınırlama mı? Java'nın soyut bir sınıf olarak tanımladığı şeyle karşılaştırılabilir soyut bir sınıf oluşturmak istiyorum.


Soyut olmak için tam sınıfa mı yoksa sadece bazı yöntemlere mi ihtiyacınız var? Tek yöntemler ve özellikler için buradaki cevaba bakınız. stackoverflow.com/a/39038828/2435872 . Java'da, soyut yöntemlerin hiçbirine sahip olmayan soyut sınıfları kullanabilirsiniz. Bu özellik Swift tarafından sağlanmaz.
jboi

Yanıtlar:


175

Swift'te soyut sınıflar yoktur (tıpkı Objective-C gibi). En iyi seçeneğiniz, Java Arayüzü gibi bir Protokol kullanmak olacaktır .

Swift 2.0 ile, protokol uzantılarını kullanarak yöntem uygulamaları ve hesaplanan özellik uygulamaları ekleyebilirsiniz. Tek kısıtlamanız üye değişkenleri veya sabitleri sağlayamamanız ve dinamik bir gönderme olmamasıdır .

Bu tekniğin bir örneği:

protocol Employee {
    var annualSalary: Int {get}
}

extension Employee {
    var biweeklySalary: Int {
        return self.annualSalary / 26
    }

    func logSalary() {
        print("$\(self.annualSalary) per year or $\(self.biweeklySalary) biweekly")
    }
}

struct SoftwareEngineer: Employee {
    var annualSalary: Int

    func logSalary() {
        print("overridden")
    }
}

let sarah = SoftwareEngineer(annualSalary: 100000)
sarah.logSalary() // prints: overridden
(sarah as Employee).logSalary() // prints: $100000 per year or $3846 biweekly

Bunun, yapılar için bile "soyut sınıf" gibi özellikler sunduğuna dikkat edin, ancak sınıflar da aynı protokolü uygulayabilir.

Ayrıca, Çalışan protokolünü uygulayan her sınıfın veya yapının tekrar yıllıkSalary özelliğini bildirmesi gerekeceğine dikkat edin.

En önemlisi, dinamik bir gönderim olmadığına dikkat edin . Ne zaman logSalarybir şekilde depolanan örnek olarak adlandırılır SoftwareEngineerbu yöntemin geçersiz kılınan sürümünü çağırır. Ne zaman logSalarybir karşı döküldükten sonra örneğinde denir Employee, bu örnek aslında bir halde değil dinamik geçersiz kılınan versiyona göndermez (orijinal uygulanmasını çağırır Software Engineer.

Daha fazla bilgi için, bu özellikle ilgili harika WWDC videosuna bakın: Swift'te Değer Türleriyle Daha İyi Uygulamalar Oluşturma


3
protocol Animal { var property : Int { get set } }. Mülkün bir ayarlayıcıya sahip olmasını istemiyorsanız seti de bırakabilirsiniz
drewag

3
Bence bu wwdc videosu daha da alakalı
Mario Zannone

2
@MarioZannone bu video sadece aklımı patladı ve Swift'e aşık olmamı sağladı.
Scott H

3
Yalnızca func logSalary()Çalışan protokolü bildirimine eklerseniz , overriddenher iki çağrı için de örnek yazdırılır logSalary(). Bu Swift 3.1'de. Böylece polimorfizmden faydalanırsınız. Her iki durumda da doğru yöntem çağrılır.
Mike Taverne

1
Dinamik dağıtım ile ilgili kural şudur ... yöntem yalnızca uzantıda tanımlanmışsa , statik olarak gönderilir. Genişlettiğiniz protokolde de tanımlanmışsa, dinamik olarak gönderilir. Objective-C çalışma zamanlarına gerek yok. Bu saf Swift davranışıdır.
Mark A. Donohoe

47

Bu cevabın Swift 2.0 ve üzerini hedeflediğini unutmayın

Protokoller ve protokol uzantıları ile aynı davranışı elde edebilirsiniz.

İlk olarak, ona uyan tüm tiplerde uygulanması gereken tüm yöntemler için arayüz görevi gören bir protokol yazarsınız.

protocol Drivable {
    var speed: Float { get set }
}

Ardından, ona uyan tüm türlere varsayılan davranış ekleyebilirsiniz

extension Drivable {
    func accelerate(by: Float) {
        speed += by
    }
}

Artık uygulayarak yeni türler oluşturabilirsiniz Drivable.

struct Car: Drivable {
    var speed: Float = 0.0
    init() {}
}

let c = Car()
c.accelerate(10)

Temel olarak şunu elde edersiniz:

  1. Tüm denetimlerin Drivableuygulanmasını garanti eden zaman denetimlerini derleyinspeed
  2. Drivable( accelerate) İle uyumlu tüm türler için varsayılan davranışı uygulayabilirsiniz
  3. Drivable sadece bir protokol olduğu için somutlaştırılmaması garanti edilir

Bu model aslında çok daha fazla özellik gibi davranır, yani birden çok protokole uyup herhangi birisinin varsayılan uygulamalarını gerçekleştirebilirsiniz, oysa soyut bir süper sınıfla basit bir sınıf hiyerarşisiyle sınırlısınız.


Yine de, örneğin, bazı protokolleri genişletme olasılığı her zaman yoktur UICollectionViewDatasource. Tüm boilerplate kaldırmak ve ayrı protokol / uzantısı içine kapsüllemek ve daha sonra birden çok sınıf tarafından yeniden kullanmak istiyorum. Aslında, şablon deseni burada mükemmel olurdu, ama ...
Richard Topchii

1
˚Car˚ içindeki ˚accelerate˚ üzerine yazamazsınız. Bunu yaparsanız, ˚extentsion Driveable 'uygulamasında derleyici uyarısı olmadan uygulama çağrılır. Java soyut sınıfından çok farklı
Gerd Castan

@GerdCastan Doğru, protokol uzantıları dinamik dağıtımı desteklemez.
IluTov

15

Bu Java abstractveya C # 's en yakın olduğunu düşünüyorum abstract:

class AbstractClass {

    private init() {

    }
}

privateDeğiştiricilerin çalışması için bu sınıfı ayrı bir Swift dosyasında tanımlamanız gerektiğini unutmayın.

EDIT: Yine de, bu kod soyut bir yöntem bildirmek için izin vermez ve böylece uygulanmasını zorlar.


4
Yine de, bu değil zorlamak da üst sınıfta bu işlevin bir taban uygulaması yerken bir işlevi geçersiz kılmak için bir alt sınıf.
Matthew Quiros

C # 'da, soyut bir temel sınıfta bir işlev uygularsanız, onu alt sınıflarına uygulamak zorunda kalmazsınız. Yine de, bu kod geçersiz kılmayı zorlamak için soyut bir yöntem bildirmenize izin vermez.
Teejay

Diyelim ki ConcreteClass, AbstractClass'ın alt sınıfı. ConcreteClass'ı nasıl başlatırsınız?
Javier Cadiz

2
ConcreteClass bir kamu kurucuya sahip olmalıdır. Aynı dosyada olmadıkları sürece, AbstractClass'ta korumalı bir kurucuya ihtiyacınız vardır. Hatırladığım kadarıyla, Swift'te korumalı erişim değiştirici yok. Dolayısıyla çözüm ConcreteClass'ı aynı dosyada ilan etmektir.
Teejay

13

En basit yol, fatalError("Not Implemented")protokol uzantısındaki soyut yönteme (değişken değil) bir çağrı kullanmaktır .

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

    func myMethod() -> String {
        fatalError("Not Implemented")
    }

}

class MyConcreteClass: MyInterface {

    func myMethod() -> String {
        return "The output"
    }

}

MyConcreteClass().myMethod()

Bu harika bir cevap. Arasan işe yarayacağını sanmıyordum (MyConcreteClass() as MyInterface).myMethod()ama işe yarıyor ! Anahtar myMethodprotokol bildirgesine dahil etmektir; aksi takdirde çağrı kilitlenir.
Mike Taverne

11

Birkaç hafta uğraştıktan sonra nihayet bir Java / PHP soyut sınıfını Swift'e nasıl çevireceğimizi anladım:

public class AbstractClass: NSObject {

    internal override init(){}

    public func getFoodToEat()->String
    {
        if(self._iAmHungry())
        {
            return self._myFavoriteFood();
        }else{
            return "";
        }
    }

    private func _myFavoriteFood()->String
    {
        return "Sandwich";
    }

    internal func _iAmHungry()->Bool
    {
        fatalError(__FUNCTION__ + "Must be overridden");
        return false;
    }
}

public class ConcreteClass: AbstractClass, IConcreteClass {

    private var _hungry: Bool = false;

    public override init() {
        super.init();
    }

    public func starve()->Void
    {
        self._hungry = true;
    }

    public override func _iAmHungry()->Bool
    {
        return self._hungry;
    }
}

public protocol IConcreteClass
{
    func _iAmHungry()->Bool;
}

class ConcreteClassTest: XCTestCase {

    func testExample() {

        var concreteClass: ConcreteClass = ConcreteClass();

        XCTAssertEqual("", concreteClass.getFoodToEat());

        concreteClass.starve();

        XCTAssertEqual("Sandwich", concreteClass.getFoodToEat());
    }
}

Ancak Apple'ın soyut sınıfları uygulamadığını düşünüyorum çünkü genellikle delege + protokol modelini kullanıyor. Örneğin, yukarıdaki aynı model şu şekilde daha iyi yapılabilir:

import UIKit

    public class GoldenSpoonChild
    {
        private var delegate: IStomach!;

        internal init(){}

        internal func setup(delegate: IStomach)
        {
            self.delegate = delegate;
        }

        public func getFoodToEat()->String
        {
            if(self.delegate.iAmHungry())
            {
                return self._myFavoriteFood();
            }else{
                return "";
            }
        }

        private func _myFavoriteFood()->String
        {
            return "Sandwich";
        }
    }

    public class Mother: GoldenSpoonChild, IStomach
    {

        private var _hungry: Bool = false;

        public override init()
        {
            super.init();
            super.setup(self);
        }

        public func makeFamilyHungry()->Void
        {
            self._hungry = true;
        }

        public func iAmHungry()->Bool
        {
            return self._hungry;
        }
    }

    protocol IStomach
    {
        func iAmHungry()->Bool;
    }

    class DelegateTest: XCTestCase {

        func testGetFood() {

            var concreteClass: Mother = Mother();

            XCTAssertEqual("", concreteClass.getFoodToEat());

            concreteClass.makeFamilyHungry();

            XCTAssertEqual("Sandwich", concreteClass.getFoodToEat());
        }
    }

UWableAppConler gibi UITableViewController bazı yöntemleri yaygınlaştırmak istedim çünkü bu tür desen gerekli. Bu yardımcı oldu mu?


1
+1 Önce bahsettiğinizle aynı yaklaşımı yapmayı planlıyordum; heyeti ilginç işaretçi.
Angad

Ayrıca, her iki örneğiniz de aynı kullanım durumundaysa yardımcı olacaktır. GoldenSpoonChild, özellikle Annenin onu genişlettiği görülüyorsa, biraz kafa karıştırıcı bir isim.
Angad

@Angad Delege deseni aynı kullanım örneğidir, ancak bir çeviri değildir; farklı bir örüntüdür, bu yüzden farklı bir bakış açısı almalıdır.
Josh Woodcock

8

Protokolleri kullanarak soyut sınıfları simüle etmenin bir yolu vardır. Bu bir örnektir:

protocol MyProtocol {
   func doIt()
}

class BaseClass {
    weak var myDelegate: MyProtocol?

    init() {
        ...
    }

    func myFunc() {
        ...
        self.myDelegate?.doIt()
        ...
    }
}

class ChildClass: BaseClass, MyProtocol {
    override init(){
        super.init()
        self.myDelegate = self
    }

    func doIt() {
        // Custom implementation
    }
}

1

Soyut sınıfı nasıl uygulayabileceğinizin bir yolu da başlatıcıyı engellemektir. Bu şekilde yaptım:

class Element:CALayer { // IT'S ABSTRACT CLASS

    override init(){ 
        super.init()
        if self.dynamicType === Element.self {
        fatalError("Element is abstract class, do not try to create instance of this class")
        }
    }
}

4
Bu herhangi bir garanti ve / veya kontrol sağlamaz. Çalışma zamanı sırasında havaya uçurmak kuralları zorlamanın kötü bir yoludur. İnitin özel olması daha iyidir.
Морт

Soyut sınıflar da soyut yöntemleri desteklemelidir.
Cristik

@Cristik Ana fikri gösterdim, tam bir çözüm değil. Bu şekilde, yanıtların% 80'ini beğenmeyebilirsiniz, çünkü durumunuz için yeterince ayrıntılı değildirler
Alexey Yarmolovich

1
@AlexeyYarmolovich cevapların% 80'ini beğenmediğimi kim söyledi? :) Şaka bir yana, örneğinizin geliştirilebileceğini öne sürüyordum, bu diğer okuyuculara yardımcı olacak ve upvotes alarak size yardımcı olacaktır.
Cristik

0

WeatherSoyut bir sınıf yapmaya çalışıyordum , ama aynı inityöntemleri tekrar tekrar yazmak zorunda kaldığım için protokolleri kullanmak ideal değildi . Protokolün genişletilmesi ve bir inityöntem yazmanın sorunları vardı, özellikle de NSObjectuyum sağladığımdan beri NSCoding.

Bu yüzden NSCodinguygunluk için bunu buldum :

required init?(coder aDecoder: NSCoder) {
    guard type(of: self) != Weather.self else {
        fatalError("<Weather> This is an abstract class. Use a subclass of `Weather`.")
    }
    // Initialize...
}        

Gelince init:

fileprivate init(param: Any...) {
    // Initialize
}

0

Temel sınıfın soyut özelliklerine ve yöntemlerine yönelik tüm başvuruları, temel sınıf için Öz sınırlama olan protokol uzantısı uygulamasına taşıyın. Base sınıfının tüm yöntemlerine ve özelliklerine erişeceksiniz. Ayrıca derleyici sınıfları için protokolde soyut yöntem ve özelliklerin uygulanmasını kontrol

protocol Commom:class{
  var tableView:UITableView {get};
  func update();
}

class Base{
   var total:Int = 0;
}

extension Common where Self:Base{
   func update(){
     total += 1;
     tableView.reloadData();
   }
} 

class Derived:Base,Common{
  var tableView:UITableView{
    return owner.tableView;
  }
}

0

Dinamik gönderim sınırlaması olmadan, böyle bir şey yapabilirsiniz:

import Foundation

protocol foo {

    static var instance: foo? { get }
    func prt()

}

extension foo {

    func prt() {
        if Thread.callStackSymbols.count > 30 {
            print("super")
        } else {
            Self.instance?.prt()
        }
    }

}

class foo1 : foo {

    static var instance : foo? = nil

    init() {
        foo1.instance = self
    }

    func prt() {
        print("foo1")
    }

}

class foo2 : foo {

    static var instance : foo? = nil

    init() {
        foo2.instance = self
    }

    func prt() {
        print("foo2")
    }

}

class foo3 : foo {

    static var instance : foo? = nil

    init() {
        foo3.instance = self
    }

}

var f1 : foo = foo1()
f1.prt()
var f2 : foo = foo2()
f2.prt()
var f3 : foo = foo3()
f3.prt()
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.