คลาสนามธรรมในภาษาสวิฟท์


141

มีวิธีในการสร้างคลาสนามธรรมในภาษา Swift หรือนี่เป็นข้อ จำกัด เช่นเดียวกับ Objective-C หรือไม่? ฉันต้องการสร้างคลาสนามธรรมเปรียบได้กับสิ่งที่ Java กำหนดเป็นคลาสนามธรรม


คุณต้องการเรียนเต็มรูปแบบที่จะเป็นนามธรรมหรือเพียงวิธีการบางอย่างในมันได้หรือไม่ ดูคำตอบที่นี่สำหรับวิธีการและคุณสมบัติเดียว stackoverflow.com/a/39038828/2435872 ใน Java คุณสามารถเรียนนามธรรมที่ไม่มีวิธีนามธรรม คุณสมบัติพิเศษนั้นไม่ได้จัดทำโดย Swift
jboi

คำตอบ:


175

ไม่มีคลาสนามธรรมใน Swift (เหมือนกับ Objective-C) ทางออกที่ดีที่สุดของคุณคือการใช้โปรโตคอลซึ่งเป็นเหมือนส่วนต่อประสาน Java

ด้วย Swift 2.0 คุณสามารถเพิ่มวิธีการใช้งานและการใช้งานคุณสมบัติที่คำนวณได้โดยใช้ส่วนขยายโปรโตคอล ข้อ จำกัด ของคุณเท่านั้นที่คุณไม่สามารถให้สมาชิกตัวแปรหรือค่าคงที่และไม่มีการจัดส่งแบบไดนามิก

ตัวอย่างของเทคนิคนี้จะเป็น:

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

โปรดสังเกตว่านี่เป็นการให้ "abstract class" เช่นเดียวกับฟีเจอร์สำหรับ structs แต่คลาสสามารถใช้โปรโตคอลเดียวกันได้

โปรดสังเกตด้วยว่าทุกคลาสหรือโครงสร้างที่ใช้โพรโทคอลพนักงานจะต้องประกาศคุณสมบัติประจำปีประจำปีอีกครั้ง

สิ่งสำคัญที่สุดคือการแจ้งให้ทราบว่าไม่มีการจัดส่งแบบไดนามิก เมื่อlogSalaryมีการเรียกใช้งานอินสแตนซ์ที่จัดเก็บตามที่SoftwareEngineerเรียกใช้รุ่นที่ถูกแทนที่ของวิธีการ เมื่อlogSalaryถูกเรียกบนอินสแตนซ์หลังจากที่มันได้รับการโยนไปที่Employeeจะเรียกการดำเนินงานเดิม Software Engineer(มันไม่ได้แบบไดนามิกส่งไปยังรุ่นแทนที่แม้ว่าอินสแตนซ์เป็นจริง

สำหรับข้อมูลเพิ่มเติมตรวจสอบวิดีโอ WWDC ที่ยอดเยี่ยมเกี่ยวกับคุณสมบัตินั้น: การสร้างแอปที่ดีขึ้นด้วยประเภทค่าใน Swift


3
protocol Animal { var property : Int { get set } }. นอกจากนี้คุณยังสามารถออกจากชุดหากคุณไม่ต้องการให้ทรัพย์สินมี setter
drewag

3
ฉันคิดว่าวิดีโอ wwdcนี้มีความเกี่ยวข้องมากขึ้น
Mario Zannone

2
@MarioZannone วิดีโอนั้นเพิ่งระเบิดความคิดของฉันและทำให้ฉันตกหลุมรักกับสวิฟท์
Scott H

3
หากคุณเพียงแค่เพิ่มfunc logSalary()การประกาศโปรโตคอลพนักงานพิมพ์ตัวอย่างสำหรับการโทรทั้งoverridden logSalary()นี่คือใน Swift 3.1 ดังนั้นคุณจะได้รับประโยชน์ของ polymorphism วิธีการที่ถูกต้องเรียกว่าในทั้งสองกรณี
Mike Taverne

1
กฎเกี่ยวกับการจัดส่งแบบไดนามิกนี้ ... ถ้าวิธีการที่ถูกกำหนดให้เฉพาะในส่วนขยายแล้วก็ส่งแบบคงที่ ถ้ามันถูกกำหนดในโปรโตคอลที่คุณกำลังขยายมันจะถูกส่งแบบไดนามิก ไม่จำเป็นต้องใช้ Objective-C runtimes นี่คือพฤติกรรมที่รวดเร็วบริสุทธิ์
Mark A. Donohoe

47

โปรดทราบว่าคำตอบนี้มีเป้าหมายที่ Swift 2.0 ขึ้นไป

คุณสามารถบรรลุพฤติกรรมเดียวกันกับโปรโตคอลและส่วนขยายโปรโตคอล

ขั้นแรกให้คุณเขียนโปรโตคอลที่ทำหน้าที่เป็นส่วนต่อประสานสำหรับวิธีการทั้งหมดที่ต้องนำไปใช้ในทุกประเภทที่สอดคล้องกับมัน

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

จากนั้นคุณสามารถเพิ่มพฤติกรรมเริ่มต้นให้กับทุกประเภทที่สอดคล้องกับมัน

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

Drivableตอนนี้คุณสามารถสร้างรูปแบบใหม่โดยการใช้

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

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

ดังนั้นโดยทั่วไปคุณจะได้รับ:

  1. ตรวจสอบเวลาการคอมไพล์ที่รับประกันว่าทุกDrivableการใช้งานspeed
  2. คุณสามารถใช้พฤติกรรมเริ่มต้นสำหรับทุกประเภทที่สอดคล้องกับDrivable( accelerate)
  3. Drivable ไม่รับประกันอินสแตนซ์เนื่องจากเป็นเพียงโปรโตคอล

แบบจำลองนี้มีลักษณะเหมือนลักษณะความเป็นจริงมากขึ้นซึ่งหมายความว่าคุณสามารถปฏิบัติตามโปรโตคอลจำนวนมากและใช้งานการเริ่มต้นของพวกเขาใด ๆ ในขณะที่มี superclass นามธรรมคุณถูก จำกัด ชั้นลำดับชั้นที่เรียบง่าย


UICollectionViewDatasourceยังไม่มีความเป็นไปได้ที่จะขยายโปรโตคอลบางตัวอย่างเช่น ฉันต้องการลบต้นแบบสำเร็จรูปทั้งหมดและห่อหุ้มด้วยโพรโทคอล / ส่วนขยายแยกต่างหากจากนั้นใช้ซ้ำหลายคลาส ในความเป็นจริงรูปแบบแม่แบบจะเหมาะที่นี่ แต่ ...
ริชาร์ด Topchii

1
คุณไม่สามารถเขียนทับ "เร่งความเร็ว" ใน˚Car˚ หากคุณทำเช่นนั้นการนำไปใช้งานใน "Extentsion Driveable" ยังคงถูกเรียกใช้โดยไม่มีคำเตือนของคอมไพเลอร์ แตกต่างจากคลาสนามธรรมของ Java
Gerd Castan

@GerdCastan True ส่วนขยายโปรโตคอลไม่รองรับการจัดส่งแบบไดนามิก
IluTov

15

ฉันคิดว่านี่ใกล้เคียงที่สุดของ Java abstractหรือ C # abstract:

class AbstractClass {

    private init() {

    }
}

โปรดทราบว่าเพื่อให้privateตัวดัดแปลงทำงานคุณต้องกำหนดคลาสนี้ในไฟล์ Swift แยกต่างหาก

แก้ไข:ยังรหัสนี้ไม่อนุญาตให้ประกาศวิธีนามธรรมและบังคับให้ดำเนินการ


4
ถึงกระนั้นสิ่งนี้ไม่ได้บังคับให้คลาสย่อยแทนที่ฟังก์ชันในขณะที่ยังมีการใช้งานพื้นฐานของฟังก์ชันนั้นในคลาสพาเรนต์
Matthew Quiros

ใน C # หากคุณใช้ฟังก์ชั่นในคลาสฐานนามธรรมคุณจะไม่ถูกบังคับให้ใช้ในคลาสย่อย แต่ถึงกระนั้นรหัสนี้ไม่อนุญาตให้คุณประกาศวิธีนามธรรมเพื่อบังคับให้มีการแทนที่
Teejay

ให้บอกว่า ConcreteClass คลาสย่อยนั้น AbstractClass คุณยกตัวอย่าง ConcreteClass ได้อย่างไร
Javier Cadiz

2
ConcreteClass ควรมีคอนสตรัคสาธารณะ คุณอาจต้องการตัวสร้างที่ได้รับการป้องกันใน AbstractClass ยกเว้นว่าพวกเขาอยู่ในไฟล์เดียวกัน ตามที่ฉันจำได้ว่าโมดิฟายเออร์ที่ได้รับการป้องกันไม่มีใน Swift ดังนั้นทางออกคือประกาศ ConcreteClass ในไฟล์เดียวกัน
Teejay

13

วิธีที่ง่ายที่สุดคือใช้การเรียกไปยังfatalError("Not Implemented")เมธอด abstract (ไม่ใช่ตัวแปร) บนส่วนขยายโปรโตคอล

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

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

}

class MyConcreteClass: MyInterface {

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

}

MyConcreteClass().myMethod()

นี่คือคำตอบที่ดี ฉันไม่คิดว่ามันจะใช้งานได้ถ้าคุณโทรมา(MyConcreteClass() as MyInterface).myMethod()แต่ใช้ได้! ที่สำคัญคือรวมmyMethodอยู่ในการประกาศโปรโตคอล; มิฉะนั้นสายขัดข้อง
Mike Taverne

11

หลังจากที่ฉันพยายามมาหลายสัปดาห์ในที่สุดฉันก็รู้ว่าจะแปลคลาสนามธรรม Java / PHP เป็น Swift:

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());
    }
}

อย่างไรก็ตามฉันคิดว่า Apple ไม่ได้ใช้คลาสที่เป็นนามธรรมเพราะโดยทั่วไปจะใช้รูปแบบของผู้รับมอบสิทธิ์ + โพรโทคอลแทน ตัวอย่างเช่นรูปแบบเดียวกันข้างต้นจะทำได้ดีกว่าเช่นนี้:

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());
        }
    }

ฉันต้องการรูปแบบเช่นนี้เพราะฉันต้องการใช้วิธีการบางอย่างร่วมกันใน UITableViewController เช่น viewWillAppear เป็นต้นสิ่งนี้มีประโยชน์หรือไม่


1
+1 กำลังวางแผนที่จะทำวิธีเดียวกันกับที่คุณพูดถึงก่อน ตัวชี้ที่น่าสนใจสำหรับรูปแบบการมอบหมาย
Angad

นอกจากนี้ยังจะช่วยได้หากตัวอย่างของคุณทั้งสองอยู่ในกรณีใช้งานเดียวกัน GoldenSpoonChild เป็นชื่อที่สับสนเล็กน้อยโดยเฉพาะอย่างยิ่งเมื่อดูเหมือนว่าแม่จะขยายออกไป
Angad

@Angad รูปแบบของผู้รับมอบสิทธิ์เป็นกรณีการใช้งานแบบเดียวกัน แต่ไม่ใช่การแปล เป็นรูปแบบที่แตกต่างกันดังนั้นจึงต้องใช้มุมมองที่แตกต่างกัน
Josh Woodcock

8

มีวิธีการจำลองคลาสนามธรรมโดยใช้โปรโตคอล นี่คือตัวอย่าง:

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

อีกวิธีหนึ่งที่คุณสามารถใช้คลาสนามธรรมคือการบล็อก initializer ฉันทำแบบนี้แล้ว:

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
สิ่งนี้ไม่ได้ให้การรับประกันและ / หรือการตรวจสอบใด ๆ การระเบิดระหว่างรันไทม์เป็นวิธีที่ไม่ดีในการบังคับใช้กฎ มันจะดีกว่าถ้ามี init เป็นส่วนตัว
Морт

คลาสนามธรรมควรมีการสนับสนุนวิธีการแบบนามธรรม
Cristik

@ Cristik ฉันแสดงให้เห็นถึงความคิดหลักมันไม่ได้เป็นโซลูชั่นที่สมบูรณ์ วิธีนี้คุณสามารถไม่ชอบ 80% ของคำตอบเพราะพวกเขามีรายละเอียดไม่เพียงพอกับสถานการณ์ของคุณ
Alexey Yarmolovich

1
@AlexeyYarmolovich ที่บอกว่าฉันไม่ชอบ 80% ของคำตอบ? :) พูดเล่นกันผมแนะนำว่าตัวอย่างของคุณสามารถปรับปรุงได้ซึ่งจะช่วยให้ผู้อ่านคนอื่น ๆ และจะช่วยคุณด้วยการ upvotes
Cristik

0

ฉันพยายามทำให้เป็นWeatherนามธรรม แต่การใช้โปรโตคอลไม่เหมาะเนื่องจากฉันต้องเขียนinitวิธีเดียวกันซ้ำแล้วซ้ำอีก การขยายโพรโทคอลและการเขียนinitวิธีมีปัญหาโดยเฉพาะอย่างยิ่งเมื่อฉันใช้การNSObjectปฏิบัติตามNSCodingสอดคล้องกับ

ดังนั้นฉันจึงได้สิ่งนี้ขึ้นมาเพื่อความNSCodingสอดคล้อง:

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...
}        

สำหรับinit:

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

0

ย้ายการอ้างอิงทั้งหมดไปยังคุณสมบัตินามธรรมและเมธอดของคลาสฐานไปยังการใช้งานส่วนขยายโปรโตคอลซึ่งข้อ จำกัด ของตัวเองไปยังคลาสพื้นฐาน คุณจะสามารถเข้าถึงวิธีการและคุณสมบัติทั้งหมดของคลาสฐาน นอกจากนี้การตรวจสอบคอมไพเลอร์ใช้วิธีนามธรรมและคุณสมบัติในโปรโตคอลสำหรับคลาสที่ได้รับ

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

ด้วยข้อ จำกัด ที่ไม่มีการแจกจ่ายแบบไดนามิกคุณสามารถทำสิ่งนี้:

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()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.