Czy istnieje sposób na stworzenie abstrakcyjnej klasy w Swift Language, czy jest to ograniczenie, tak jak Objective-C? Chciałbym stworzyć klasę abstrakcyjną porównywalną z tym, co Java definiuje jako klasę abstrakcyjną.
Czy istnieje sposób na stworzenie abstrakcyjnej klasy w Swift Language, czy jest to ograniczenie, tak jak Objective-C? Chciałbym stworzyć klasę abstrakcyjną porównywalną z tym, co Java definiuje jako klasę abstrakcyjną.
Odpowiedzi:
W Swift nie ma abstrakcyjnych klas (podobnie jak Objective-C). Najlepszym rozwiązaniem będzie użycie protokołu , który jest podobny do interfejsu Java.
Dzięki Swift 2.0 możesz następnie dodawać implementacje metod i implementacje obliczonych właściwości przy użyciu rozszerzeń protokołów. Jedynymi ograniczeniami jest to, że nie możesz podać zmiennych składowych ani stałych i nie ma dynamicznego wysyłania .
Przykładem tej techniki byłoby:
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
Zauważ, że zapewnia to funkcje podobne do "klas abstrakcyjnych" nawet dla struktur, ale klasy mogą również implementować ten sam protokół.
Zauważ również, że każda klasa lub struktura, która implementuje protokół Employee, będzie musiała ponownie zadeklarować właściwość AnnualSalary.
Co najważniejsze, zauważ, że nie ma dynamicznej wysyłki . Gdy logSalary
jest wywoływana dla wystąpienia, które jest przechowywane jako a SoftwareEngineer
, wywołuje zastąpioną wersję metody. Gdy logSalary
jest wywoływana na instancji po jej rzutowaniu na plik Employee
, wywołuje oryginalną implementację (nie jest dynamicznie wysyłana do nadpisanej wersji, mimo że instancja jest faktycznie plikiem Software Engineer
.
Aby uzyskać więcej informacji, obejrzyj świetny film WWDC dotyczący tej funkcji: Tworzenie lepszych aplikacji z typami wartości w języku Swift
protocol Animal { var property : Int { get set } }
. Możesz również pominąć set, jeśli nie chcesz, aby obiekt miał setera
func logSalary()
do deklaracji protokołu pracownika, przykład zostanie wydrukowany overridden
dla obu wywołań funkcji logSalary()
. To jest w Swift 3.1. W ten sposób uzyskujesz korzyści z polimorfizmu. W obu przypadkach wywoływana jest właściwa metoda.
Zwróć uwagę, że ta odpowiedź jest skierowana do Swift 2.0 i nowszych wersji
To samo zachowanie można osiągnąć dzięki protokołom i rozszerzeniom protokołów.
Najpierw piszesz protokół, który działa jako interfejs dla wszystkich metod, które muszą być zaimplementowane we wszystkich typach, które są z nim zgodne.
protocol Drivable {
var speed: Float { get set }
}
Następnie możesz dodać domyślne zachowanie do wszystkich typów, które są z nim zgodne
extension Drivable {
func accelerate(by: Float) {
speed += by
}
}
Możesz teraz tworzyć nowe typy, implementując Drivable
.
struct Car: Drivable {
var speed: Float = 0.0
init() {}
}
let c = Car()
c.accelerate(10)
W zasadzie otrzymujesz:
Drivable
są implementowanespeed
Drivable
( accelerate
)Drivable
jest gwarantowane, że nie zostanie utworzony, ponieważ jest to tylko protokółTen model w rzeczywistości zachowuje się znacznie bardziej jak cechy, co oznacza, że możesz dostosować się do wielu protokołów i przyjąć domyślne implementacje dowolnego z nich, podczas gdy w przypadku abstrakcyjnej nadklasy ogranicza się do prostej hierarchii klas.
UICollectionViewDatasource
. Chciałbym usunąć cały standardowy szablon i zamknąć go w osobnym protokole / rozszerzeniu, a następnie ponownie wykorzystać przez wiele klas. W rzeczywistości wzór szablonu byłby tutaj idealny, ale ...
Myślę, że jest to najbliższe Java abstract
lub C # abstract
:
class AbstractClass {
private init() {
}
}
Zauważ, że aby private
modyfikatory działały, musisz zdefiniować tę klasę w osobnym pliku Swift.
EDYCJA: Mimo to ten kod nie pozwala na zadeklarowanie metody abstrakcyjnej i tym samym wymusza jej implementację.
Najprostszym sposobem jest użycie wywołania fatalError("Not Implemented")
metody abstrakcyjnej (nie zmiennej) w rozszerzeniu protokołu.
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()
ale zadziała ! Klucz jest zawarty myMethod
w deklaracji protokołu; w przeciwnym razie połączenie zawiesza się.
Po kilku tygodniach zmagań w końcu zrozumiałem, jak przetłumaczyć abstrakcyjną klasę Java / PHP na 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());
}
}
Jednak myślę, że Apple nie zaimplementował klas abstrakcyjnych, ponieważ zazwyczaj używa zamiast tego wzorca delegat + protokół. Na przykład ten sam wzór powyżej byłby lepiej wykonany w ten sposób:
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());
}
}
Potrzebowałem tego rodzaju wzorca, ponieważ chciałem ujednolicić niektóre metody w UITableViewController, takie jak viewWillAppear itp. Czy to było pomocne?
Istnieje sposób na symulowanie klas abstrakcyjnych przy użyciu protokołów. To jest przykład:
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
}
}
Jeszcze jednym sposobem implementacji klasy abstrakcyjnej jest blokowanie inicjatora. Zrobiłem to w ten sposób:
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")
}
}
}
Próbowałem stworzyć Weather
abstrakcyjną klasę, ale używanie protokołów nie było idealne, ponieważ musiałem ciągle pisać te same init
metody. Rozszerzenie protokołu i napisanie init
metody sprawiało pewne problemy, zwłaszcza że korzystałem z NSObject
complianceNSCoding
.
Więc wymyśliłem to dla NSCoding
zgodności:
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...
}
Co do init
:
fileprivate init(param: Any...) {
// Initialize
}
Przenieś wszystkie odwołania do abstrakcyjnych właściwości i metod klasy Base do implementacji rozszerzenia protokołu, gdzie samoograniczenie do klasy podstawowej. Uzyskasz dostęp do wszystkich metod i właściwości klasy Base. Dodatkowo kompilator sprawdza implementację metod abstrakcyjnych i właściwości w protokole dla klas pochodnych
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;
}
}
Z ograniczeniem braku dynamicznej wysyłki, możesz zrobić coś takiego:
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()