Как реализовать "абстрактные" инициализаторы свойств в Swift - PullRequest
1 голос
/ 19 марта 2019

До сих пор я работал только над проектами Objectiv-C и сейчас начал свой первый проект Swift.

Я знаю, что Swift не поддерживает абстрактные классы, но я хотел бы знать, как лучше всего моделировать/ решить это в Swift:

// Abstract implementation
public abstract class MyClass {
    private SomeBaseClass someProperty; 

    MyClass() {
        initProperties();
    }

    abstract void initProperties();  // Init someProperty with some child class of SomeBaseClass.
}

// Objectiv-C
@implementation MyClass

- (id)init {
    self = [super init];

    if (self) {
        [self initProperties];     
    }

    return self;
}

- (void)initProperties {
    // Override in inherited classes
}


// Swift
class MyClass {
    // Should not be optional since MyClass should be required to have someProperty != nil
    let someProperty: SomeBaseClass;

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

    func initProperties() {
         // Cannot be empty since someProperty is non-optional and needs to be initialized
         // Cannot be empty since Swift does not support abstract methods
    }
}

Конечно, можно определить someProperty как необязательный SomeBaseClass?, но в этом случае каждый раз, когда свойство используется, оно должно быть проверено и развернуто.

Есть ли лучший способ решить эту проблему?

РЕДАКТИРОВАТЬ:

Я знаю, что Swift использует протоколы для создания абстракции, аналогичной абстрактным классам.Однако я не понимаю, как эта концепция может решить конкретную проблему / вопрос.

В других языках программирования абстрактный класс MyClass может использовать свойство someProperty во многих различных местах, оставляя бремя для инициализациисвойство со значением с его конкретными подклассами.

Хотя я читаю статью, связанную @MohamendS, и ответы на возможный повторный ответ, я не понимаю, как добиться того же, используя протоколы.

  • MyClass имеет только одну абстрактную функцию, в то время как все остальные функции реализованы.Таким образом, сам MyClass не может быть протоколом, поскольку протоколы не могут реализовывать функции (не так ли?)
  • MyClass может реализовывать только другой протокол, который определяет, что должен быть метод initProperties.Но в этом случае MyClass потребуется предоставить реализацию этого метода, которая возвращает нас к той же проблеме.

Я думаю, что я не вижу дерево для деревьев, но как можнопротоколы здесь помогают?

Ответы [ 2 ]

2 голосов
/ 19 марта 2019

Существует много возможных ответов на этот вопрос в зависимости от того, как используется MyClass. Как написано, конечно, нет никакой причины для someProperty быть в базовом классе вообще, и определенно нет причин для initProperties() метода (который должен быть в init).

Я понимаю, что это «просто пример», но он демонстрирует общую проблему создания ненужных иерархий. Есть способы написания этого кода с использованием полуабстрактного базового класса, но, как правило, этого следует избегать, поэтому первый вопрос - для чего вы его используете, и можем ли мы полностью избежать этой проблемы?

Чтобы ответить на заданный вопрос, вы, вероятно, начнете с установки по умолчанию SomeBaseClass, чтобы абстрактный класс мог просто присвоить someProperty = SomeBaseClass().

Если это невозможно, обычно вы используете тип !:

let someProperty: SomeBaseClass!

И вы реализуете initProperties() с помощью fatalError:

func initProperties() { fatalError("Implement in subclass") }

С другой стороны, может быть удобно реализовать someProperty как вычисляемую переменную и реализовать ее на основе некоторого другого свойства в подклассах

var someProperty: SomeBaseClass { fatalError() }

Но это действительно последнее средство. Каждый раз, когда вам приходится писать fatalError, вы, вероятно, ошибаетесь, и вам не нужен трюк, чтобы обойти это; вам нужно пересмотреть проблему.

Сначала вы должны подумать о том, как используется *1029*, используется , и подумать, может ли это быть типом значения. Отдельно вы должны подумать о том, может ли это быть протокол, соответствующий сценарию использования. Протоколы - это не просто абстрактные интерфейсы, которые скрывают реализации. Они представляют собой соответствующий тип для решения конкретной проблемы. Вот почему существует протокол Collection, который предоставляет доступ к десяткам алгоритмов для многочисленных, иначе не связанных типов, а не ArrayProtocol просто для того, чтобы скрыть реализацию Array. Не превращайте MyClass в MyClassProtocol. Спросите, какие алгоритмы хотят использовать такие типы, как этот.

Когда вы создаете взаимосвязанные иерархии типов (подклассы чего-то, что требует подклассов чего-то другого), вы часто решаете проблему в неправильном направлении. Вы должны переосмыслить, можете ли вы решить проблему так, чтобы различные части SomeBaseClass фактически были частью MyClass (часто это упрощает SomeBaseClass; например, это чистые данные, а не логика).

Здесь нет одного правильного ответа. Это зависит от природы MyClass, поэтому мы не можем обсуждать это в абстрактных терминах. Как абстрактные классы, решение абстрактных задач часто ведет вас по неверным путям. Часто лучше начать с конкретных типов, а затем найти их сходства и извлечь их.

Даже с учетом вышесказанного, стоит показать, как простой, наивный протокол будет выглядеть здесь. (Возможно, это даже правильный протокол.)

public protocol MyProtocol {
    var someProperty: SomeBaseClass { get }
}

Вот и все. Это все, что вы на самом деле выражаете в своем текущем абстрактном классе (и на самом деле неясно, является ли someProperty публичным; если он приватный, этот протокол будет пустым).

Реализующая структура будет выглядеть так:

struct MyStruct: MyProtocol {
    var someProperty: SomeBaseClass
}

Или, если вы хотите ссылочный тип, вы можете использовать последний класс:

final class MyClass: MyProtocol {
    var someProperty: SomeBaseClass
    init() {
        someProperty = ...
    }
}

Или, если вы хотите наследовать, вы можете использовать не финальный класс:

class MyClass: MyProtocol {
    var someProperty: SomeBaseClass
    init() {
        someProperty = ...
    }
}
2 голосов
/ 19 марта 2019

Концепция абстракции в Swift используется с protocols, я предлагаю прочитать эту статью, чтобы узнать больше, и вот пример

 protocol Abstraction {
    var foo: String { get }
    func fee()
    init(with foo: String)
}

class A: Abstraction {

    required init(with foo: String) {
        self.foo = foo 
    }
    var foo: String = ""

    func fee() {

      }
}

Edit: по вашему мнению, это

протоколы не могут реализовывать функции

Вы не можете, но то, что вы можете сделать, это расширить эти протоколы, используя расширение, и дать им начальную реализацию, поэтому вам не нужно реализовывать их в классе, и вы можете, когда захотите, проверить код ниже

    class A: Abstraction {

    required init(with foo: String) {
        self.foo = foo
    }
    var foo: String = ""

    //you don't have to implement func foo anymore as its extended in the extension
}

extension Abstraction {
    func fee() {
        print("ok")
    }
}

let a = A(with: "foo")
a.fee() // this will trigger the extension implementation,

Теперь, чтобы использовать init внутри тела extension, чтобы вам не приходилось вводить их в каждом подтверждении, проверьте код ниже

protocol Abstraction {
    var foo: String { get set }
    func fee()
    init()
    init(with foo: String)
}


class A: Abstraction {
    required init() { }
    var foo: String = ""

    //you don't have to implement func foo anymore as its extended in the extension 
   // you don't have to implement the custom init anymore too 

}

extension Abstraction {
    init(with foo: String) {
        self.init()
        self.foo = foo
    }
    func fee() {
        print("ok")
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...