Các chức năng trừu tượng trong Ngôn ngữ Swift


127

Tôi muốn tạo một chức năng trừu tượng trong ngôn ngữ swift. Có thể không?

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

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

Điều này khá gần với câu hỏi khác của bạn nhưng câu trả lời ở đây có vẻ tốt hơn một chút.
David Berry

Các câu hỏi tương tự nhau nhưng các giải pháp khác nhau rất nhiều vì một lớp trừu tượng có các trường hợp sử dụng khác với một hàm trừu tượng.
kev

Đúng, tại sao tôi không bỏ phiếu để đóng, nhưng câu trả lời sẽ không hữu ích ngoài "bạn không thể" Ở đây bạn có câu trả lời tốt nhất có sẵn :)
David Berry

Câu trả lời:


198

Không có khái niệm trừu tượng trong Swift (như Objective-C) nhưng bạn có thể làm điều này:

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

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

13
Hoặc bạn có thể sử dụng assert(false, "This method must be overriden by the subclass").
Erik

20
hoặcfatalError("This method must be overridden")
nathan

5
các xác nhận sẽ yêu cầu một câu lệnh return khi một phương thức có kiểu trả về. Tôi nghĩ fatalError () tốt hơn vì lý do này
André Fratelli

6
Ngoài ra còn có preconditionFailure("Bla bla bla")Swift sẽ thực thi trong các bản dựng phát hành và cũng loại bỏ sự cần thiết của câu lệnh return. EDIT: Chỉ cần phát hiện ra rằng phương pháp này về cơ bản là bằng fatalError()nhưng đó là cách phù hợp hơn (Tài liệu tốt hơn, được giới thiệu trong Swift cùng với precondition(), assert()assertionFailure(), đọc ở đây )
Kametrixom

2
Nếu hàm có kiểu trả về thì sao?
LoveMeow

36

Những gì bạn muốn không phải là một lớp cơ sở, mà là một giao thức.

protocol MyProtocol {
    func abstractFunction()
}

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

Nếu bạn không cung cấp trừu tượng trong lớp thì đó là một lỗi.

Nếu bạn vẫn cần lớp cơ sở cho các hành vi khác, bạn có thể làm điều này:

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

23
Điều này không hiệu quả lắm. Nếu BaseClass muốn gọi các hàm trống đó, thì nó cũng sẽ phải thực hiện giao thức, và sau đó thực hiện các hàm. Ngoài ra, lớp con vẫn không bị buộc phải thực hiện các hàm tại thời gian biên dịch và bạn cũng không bị buộc phải thực hiện giao thức.
bandejapaisa

5
Đó là quan điểm của tôi .... BaseClass không liên quan gì đến giao thức và để hoạt động như một lớp trừu tượng, cần phải có một cái gì đó để làm với nó. Để làm rõ những gì tôi đã viết "Nếu BaseClass muốn gọi các hàm trống đó, thì nó cũng sẽ phải thực hiện giao thức buộc bạn phải thực hiện các hàm - điều này dẫn đến Lớp con không bị buộc phải thực hiện các hàm trong thời gian biên dịch, bởi vì BaseClass đã hoàn thành nó ".
bandejapaisa

4
Điều đó thực sự phụ thuộc vào cách bạn định nghĩa "trừu tượng". Toàn bộ điểm của một hàm trừu tượng là bạn có thể đặt nó trên một lớp có thể làm những thứ bình thường. Ví dụ, lý do tôi muốn điều này là vì tôi cần xác định một thành viên tĩnh mà bạn dường như không thể làm với các giao thức. Vì vậy, thật khó cho tôi để thấy rằng điều này đáp ứng điểm hữu ích của một hàm trừu tượng.
Cún con

3
Tôi nghĩ rằng mối quan tâm với các bình luận được nêu lên ở trên là điều này không phù hợp với nguyên tắc Thay thế Liskov, trong đó nêu rõ "các đối tượng trong một chương trình nên có thể thay thế bằng các thể hiện của các kiểu con của chúng mà không làm thay đổi tính chính xác của chương trình đó." Nó giả định đây là một loại trừu tượng. Điều này đặc biệt quan trọng trong trường hợp mẫu Phương thức nhà máy trong đó lớp cơ sở chịu trách nhiệm tạo và lưu trữ các thuộc tính có nguồn gốc từ lớp phụ.
David James

2
Một lớp cơ sở trừu tượng và một giao thức không giống nhau.
Shoerob

29

Tôi chuyển một số lượng mã hợp lý từ một nền tảng hỗ trợ các lớp cơ sở trừu tượng sang Swift và chạy vào đây rất nhiều. Nếu điều bạn thực sự muốn là chức năng của một lớp cơ sở trừu tượng, thì điều đó có nghĩa là lớp này đóng vai trò là một triển khai của chức năng lớp dựa trên chia sẻ (nếu không nó sẽ chỉ là một giao diện / giao thức) VÀ nó định nghĩa các phương thức phải được thực hiện bởi các lớp dẫn xuất.

Để làm điều đó trong Swift, bạn sẽ cần một giao thức và lớp cơ sở.

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

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

Lưu ý rằng lớp cơ sở thực hiện (các) phương thức được chia sẻ, nhưng không thực hiện giao thức (vì nó không thực hiện tất cả các phương thức).

Sau đó, trong lớp dẫn xuất:

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

Lớp dẫn xuất kế thừa sharedFunction từ lớp cơ sở, giúp nó đáp ứng phần đó của giao thức và giao thức vẫn yêu cầu lớp dẫn xuất thực hiện trừu tượng.

Nhược điểm thực sự duy nhất của phương thức này là vì lớp cơ sở không thực hiện giao thức, nếu bạn có một phương thức lớp cơ sở cần truy cập vào một thuộc tính / phương thức giao thức, bạn sẽ phải ghi đè lên lớp dẫn xuất và từ đó gọi lớp cơ sở (thông qua siêu) đi qua selfđể lớp cơ sở có một thể hiện của giao thức để thực hiện công việc của nó.

Ví dụ: giả sử rằng sharedFunction cần thiết để gọi trừu tượng. Giao thức sẽ giữ nguyên và các lớp bây giờ sẽ như sau:

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

Bây giờ, hàm chia sẻ từ lớp dẫn xuất đang đáp ứng phần đó của giao thức, nhưng lớp dẫn xuất vẫn có thể chia sẻ logic lớp cơ sở theo cách đơn giản hợp lý.


4
Hiểu biết sâu sắc và có chiều sâu tốt về "lớp cơ sở không thực hiện giao thức" ... đó là loại điểm của một lớp trừu tượng. Tôi gặp khó khăn khi tính năng OO cơ bản này bị thiếu, nhưng một lần nữa, tôi đã lớn lên trên Java.
Dan Rosenstark

2
Tuy nhiên, việc triển khai không cho phép thực hiện liền mạch phương thức Chức năng Mẫu trong đó phương thức mẫu gọi một số lượng lớn các phương thức trừu tượng được triển khai trong các lớp con. Trong trường hợp này, bạn nên viết cả hai như các phương thức bình thường trong siêu lớp, như các phương thức trong giao thức và một lần nữa là các triển khai trong lớp con. Trong thực tế, bạn phải viết ba lần cùng một nội dung và chỉ dựa vào kiểm tra ghi đè để đảm bảo không mắc lỗi chính tả nào! Tôi thực sự hy vọng rằng Swift mở cửa cho các nhà phát triển, chức năng trừu tượng đầy đủ sẽ được giới thiệu.
Fabrizio Bartolomucci

1
Rất nhiều thời gian và mã có thể được lưu bằng cách giới thiệu từ khóa "được bảo vệ".
Shoerob

23

Đây có vẻ là cách "chính thức" để Apple xử lý các phương thức trừu tượng trong UIKit. Hãy xem UITableViewControllervà cách nó hoạt động UITableViewDelegate. Một trong những điều đầu tiên bạn làm là thêm một dòng : delegate = self. Vâng, đó chính xác là mẹo.

1. Đặt phương thức trừu tượng trong một giao thức
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Viết lớp cơ sở của bạ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. Viết lớp con (es).
/// 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!"}
}
Đây là, làm thế nào để bạn sử dụng tất cả
let x = ClassX()
x.doSomethingWithAbstractMethod()

Kiểm tra với Playground để xem đầu ra.

Một số lưu ý

  • Đầu tiên, rất nhiều câu trả lời đã được đưa ra. Tôi hy vọng ai đó tìm thấy tất cả các cách này.
  • Câu hỏi thực sự là, để tìm ra một mô hình thực hiện:
    • Một lớp gọi một phương thức, phải được thực hiện trong một trong các lớp con dẫn xuất của nó (ghi đè)
    • Trong trường hợp tốt nhất, nếu phương thức không bị ghi đè trong lớp con, sẽ gặp lỗi trong thời gian biên dịch
  • Vấn đề của các phương thức trừu tượng là, chúng là hỗn hợp của một định nghĩa giao diện và một phần của việc triển khai thực tế trong lớp cơ sở. Cả hai cùng một lúc. Vì swift là rất mới và được xác định rất rõ ràng, nó không có khái niệm như vậy mà là các khái niệm "ô uế" (chưa).
  • Đối với tôi (một anh chàng Java nghèo khổ), vấn đề này thỉnh thoảng phát triển. Tôi đã đọc qua tất cả các câu trả lời trong bài đăng này và lần này tôi nghĩ rằng tôi đã tìm thấy một mô hình, có vẻ khả thi - ít nhất là với tôi.
  • Cập nhật : Có vẻ như những người triển khai UIKit tại Apple sử dụng cùng một mẫu. UITableViewControllerthực hiện UITableViewDelegatenhưng vẫn cần phải được đăng ký như đại biểu bằng cách thiết lập rõ ràng delegatetài sản.
  • Tất cả điều này được thử nghiệm trên Playground of Xcode 7.3.1

Có thể không hoàn hảo, vì tôi gặp vấn đề khi ẩn giao diện của lớp khỏi các lớp khác, nhưng điều này là đủ những gì tôi cần để thực hiện Phương thức Factory cổ điển trong Swift.
Tomasz Nazarenko

Hừm. Tôi thích vẻ ngoài của câu trả lời này hơn rất nhiều, nhưng tôi cũng không chắc nó phù hợp. Tức là tôi có một ViewContoder có thể hiển thị thông tin cho một số loại đối tượng khác nhau, nhưng mục đích hiển thị thông tin đó là như nhau, với tất cả các phương thức giống nhau, nhưng kéo và kết hợp thông tin khác nhau dựa trên đối tượng . Vì vậy, tôi có một lớp ủy nhiệm chính cho VC này và một lớp con của đại biểu đó cho từng loại đối tượng. Tôi khởi tạo một đại biểu dựa trên đối tượng được truyền vào. Trong một ví dụ, mỗi đối tượng có một số ý kiến ​​liên quan được lưu trữ.
Jake T.

Vì vậy, tôi có một getCommentsphương pháp trên đại biểu phụ huynh. Có một số loại bình luận và tôi muốn những loại có liên quan đến từng đối tượng. Vì vậy, tôi muốn các lớp con không biên dịch khi tôi thực hiện delegate.getComments()nếu tôi không ghi đè phương thức đó. VC chỉ biết nó có một ParentDelegateđối tượng được gọi delegate, hoặc BaseClassXtrong ví dụ của bạn, nhưng BaseClassXkhông có phương thức trừu tượng hóa. Tôi cần VC để biết cụ thể rằng nó đang sử dụng SubclassedDelegate.
Jake T.

Tôi hy vọng tôi hiểu bạn đúng. Nếu không, bạn có phiền hỏi một câu hỏi mới nơi bạn có thể thêm một số mã không? Tôi nghĩ rằng bạn chỉ cần có một câu lệnh if trong kiểm tra ‚doS SomethingWith AbTHERMethod ', nếu đại biểu được đặt hay không.
jboi

Điều đáng nói là việc tự gán cho đại biểu sẽ tạo ra một chu kỳ giữ lại. Có lẽ tốt hơn là tuyên bố nó là yếu (thường là một ý tưởng tốt cho các đại biểu)
Enricoza

7

Một cách để làm điều này là sử dụng một bao đóng tùy chọn được xác định trong lớp cơ sở và trẻ có thể chọn thực hiện nó hay không.

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

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

Điều tôi thích về cách tiếp cận này là tôi không phải nhớ thực hiện một giao thức trong lớp kế thừa. Tôi có một lớp cơ sở cho ViewControllers của mình và đây là cách tôi thực thi chức năng tùy chọn, cụ thể của ViewContoder (ví dụ: ứng dụng đã hoạt động, v.v.) có thể được gọi bởi chức năng của lớp cơ sở.
ProgrammierTier

6

Chà, tôi biết rằng tôi đến trễ trò chơi và tôi có thể đang tận dụng những thay đổi đã xảy ra. Xin lỗi vì điều đó.

Trong mọi trường hợp, tôi muốn đóng góp câu trả lời của mình, vì tôi thích thực hiện kiểm tra và các giải pháp với fatalError(), là AFAIK, không thể kiểm tra và những giải pháp có ngoại lệ khó kiểm tra hơn nhiều.

Tôi sẽ đề nghị sử dụng một cách tiếp cận khéo léo hơn. Mục tiêu của bạn là xác định một sự trừu tượng có một số chi tiết phổ biến, nhưng điều đó không được xác định đầy đủ, tức là (các) phương thức trừu tượng. Sử dụng một giao thức xác định tất cả các phương thức dự kiến ​​trong trừu tượng hóa, cả hai phương thức được xác định và cả các phương thức không được xác định. Sau đó tạo một phần mở rộng giao thức thực hiện các phương thức được xác định trong trường hợp của bạn. Cuối cùng, bất kỳ lớp dẫn xuất nào cũng phải thực hiện giao thức, có nghĩa là tất cả các phương thức, nhưng các phương thức là một phần của phần mở rộng giao thức đã có triển khai.

Mở rộng ví dụ của bạn với một hàm cụ thể:

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

Lưu ý rằng bằng cách này, trình biên dịch lại là bạn của bạn. Nếu phương thức không bị "ghi đè", bạn sẽ gặp lỗi khi biên dịch, thay vì phương thức bạn sẽ gặp phải fatalError()hoặc ngoại lệ sẽ xảy ra trong thời gian chạy.


1
Imo câu trả lời tốt nhất.
Apfelsaft

1
Tuy nhiên, câu trả lời hay, nhưng sự trừu tượng cơ sở của bạn không có khả năng lưu trữ các thuộc tính
joehinkle11

5

Tôi hiểu những gì bạn đang làm bây giờ, tôi nghĩ rằng bạn nên sử dụng một giao thức tốt hơn

protocol BaseProtocol {
    func abstractFunction()
}

Sau đó, bạn chỉ cần tuân thủ giao thức:

class SubClass : BaseProtocol {

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

Nếu lớp của bạn cũng là một lớp con, các giao thức tuân theo Superclass:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

3

Sử dụng asserttừ khóa để thực thi các phương thức trừu tượng:

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

Tuy nhiên, như Steve Waddicor đã đề cập, có lẽ bạn muốn protocolthay thế.


Lợi ích của các phương thức trừu tượng là việc kiểm tra được thực hiện tại thời điểm biên dịch.
Fabrizio Bartolomucci

không sử dụng khẳng định, vì khi lưu trữ, bạn sẽ nhận được lỗi 'Mất trả lại trong một chức năng dự kiến ​​sẽ trả về'. Sử dụng fatalError ("Phương pháp này phải được ghi đè bởi lớp con")
Zaporozhchenko Oleksandr

2

Tôi hiểu câu hỏi và đang tìm kiếm giải pháp tương tự. Các giao thức không giống như các phương thức Trừu tượng.

Trong một giao thức bạn cần xác định rằng lớp của bạn tuân thủ giao thức đó, một phương thức trừu tượng có nghĩa là bạn phải ghi đè phương thức đó.

Nói cách khác, các giao thức là loại tùy chọn, bạn cần chỉ định lớp cơ sở và giao thức, nếu bạn không chỉ định giao thức, thì bạn không phải ghi đè các phương thức đó.

Một phương thức trừu tượng có nghĩa là bạn muốn có một lớp cơ sở nhưng cần phải thực hiện một hoặc hai phương thức của riêng bạn, không giống nhau.

Tôi cần hành vi tương tự, đó là lý do tại sao tôi đang tìm kiếm một giải pháp. Tôi đoán Swift đang thiếu tính năng như vậy.


1

Có một giải pháp khác cho vấn đề này, mặc dù vẫn còn một nhược điểm khi so sánh với đề xuất của @ jaumard; nó đòi hỏi một tuyên bố trở lại. Mặc dù tôi bỏ lỡ điểm cần thiết, bởi vì nó bao gồm việc ném trực tiếp một ngoại lệ:

class AbstractMethodException : NSException {

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

Và sau đó:

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

Bất cứ điều gì đến sau đó là không thể truy cập được, vì vậy tôi không hiểu tại sao buộc phải quay lại.


Ý bạn là AbstractMethodException().raise()sao
Joshcodes 23/2/2015

Ơ ... Có lẽ. Tôi không thể kiểm tra ngay bây giờ, nhưng nếu nó hoạt động theo cách đó, hơn là có
André Fratelli

1

Tôi không biết là nó sẽ hữu ích, nhưng tôi gặp vấn đề tương tự với phương pháp trừu tượng trong khi cố gắng xây dựng trò chơi SpritKit. Điều tôi muốn là một lớp Động vật trừu tượng có các phương thức như move (), run (), v.v., nhưng tên sprite (và các chức năng khác) nên được cung cấp bởi các lớp con. Vì vậy, cuối cùng tôi đã làm một cái gì đó như thế này (đã thử nghiệm cho Swift 2):

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"]
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.