简体   繁体   中英

In Swift, how do I prevent a function from being called on a subclass?

I have a base class that stores a lot of other data objects within some data structures, and this class manages those data objects in my collections through a group of add / remove functions that keep my data structures in sync.

Now I go to subclass this base class, and the subclass is just like the base class except that it has special rules for what kinds of data objects it will accept (it looks at the values on the data objects, and rejects them if the values aren't right). Because of this "validity" check, I created a new function just for the subclass called change . In the change function, it examines all of the incoming data objects and verifies that they are ok, and replaces all of the data objects in the data structures with these data objects.

The problem is that I don't want someone to be able to make a subclass object and allow them to call the base class add / remove functions, because I only want the subclass to be able to be changed through the subclass's change function.

I haven't found a good way to "disable" the use of those base class functions in the subclass. I can override the functions and implement empty implementations, but there's no feedback to the caller that the function didn't do anything. And if I use something like fatalError , it isn't compile time, it's runtime.

My other thoughts are to break the functionality of the base class into multiple protocols, and change the base class to simply have all of the data structures, but conforming to none of the protocols and then have multiple subclasses, where one that wants the add functionality can inherit from the base and additionally conform to the add protocol, but one that doesn't want add or remove can inherit from the base, and simply not conform to any of the protocols and instead create their own change functions to modify the data structures.

Here's a simpler hierarchy to explain:

class Parent {
  
  func doThis() { print("Parent Did This") }
  func doThat() { print("Parent Did That") }
  
}

class Child: Parent {
  
  override func doThis() {
    print("Child Did This")
  }
  
  // I want this to be a compile time error
  override func doThat() {
    print("Not Supported")
    return
  }
  
}

Is there an easier way to "hide" a function in a subclass?

EDIT 1

To better explain my proposed solution, and whether or not there is an easier way to achieve it with my current hierarchy, here's what the hierarchy would have to look like using some protocols:

protocol CanDoThis {
  func doThis()
}

protocol CanDoThat {
  func doThat()
}

class Parent {
  // Important properties that all children should have
  var property1: Int = 0
  var property2: String = ""
}

class Child1: Parent, CanDoThis {
  func doThis() { print("Child1 Can Do This") }
}

class Child2: Parent, CanDoThat {
  func doThat() { print("Child2 Can Do That") }
}

class Child3: Parent, CanDoThis, CanDoThat {
  func doThis() { print("Child3 Can Do This") }
  func doThat() { print("Child3 Can Do That") }
}

Disclaimer

The protocol version would probably be better design - see the Liskov Substitution Principle amount the other things you mentioned.

Answering the question

You can use the attribute @available() (see Swift -> Language Reference -> Attributes ).

class Parent {
    func doThis() { print("Parent Did This") }
    func doThat() { print("Parent Did That") }
}

class Child: Parent {
    override func doThis() {
        print("Child Did This")
    }

    @available(*, unavailable, message:"Child can't doThat")
    override func doThat() {
        print("Not Supported")
    }
}


let parent = Parent()
parent.doThis()
parent.doThat()

let child = Child()
child.doThis()
child.doThat()

You get the following error message on child.doThat() :

'doThat()' is unavailable: Child can't doThat

However you can get around this by doing the following (again, see Liskov substitution principle):

let maybeChild: Parent = Child()
maybeChild.doThis()
maybeChild.doThat()

I'd handle this using @availability . Something like:

@available(*, deprecated=1.0, message="Do not call this method")
final func foo(){ //function you want to protect
}

This will give you a compiler warning any time you call the function.

Interestingly, marking the function as unavailable will throw a compiler error, making it impossible to build the app if anyone calls this function. Unfortunately, there is no way to suppress that error in Swift, so that will break it for your good use cases as well. Maybe you can get around this by using self.performSelector("foo") .

Also note I marked foo() as final , which prevents it from being overridden by a subclass.

What you want is access control. Swift's modifier for this is internal . You can read more about it here in the docs under Access Control.

I think the most obvious solution is this:

just set the add and remove function in the base class as private

Example

class Parent {
  
  func doThis() { print("Parent Did This") }
  private func doThat() { print("Parent Did That") }
  
}

class Child: Parent {
  
  override func doThis() {
    print("Child Did This")
  }
  
  // This is a compile time error as the subclass will not be allowed to override this class, because it's private.
  override func doThat() {
    print("Not Supported")
    return
  }
  
}

in this example, overriding doThat method will cause a compile time error as the subclass will not be allowed to override this method, because it's private.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM