【问题标题】:how can I modify the property of a class in its subclasses?如何修改其子类中的类的属性?
【发布时间】:2017-01-18 20:56:33
【问题描述】:
class Animals{
    var name : String = "default"
    var age : Int = 0
    func Details()-> String{
        return "This animal is a \(name) and has \(age) years old."
    }
}


class Dogs : Animals{
    name = "dog"
}

class Cats : Animals{
    name = "cat"
}

var MyAnimal = Dogs()

我想看到这条消息:“这只动物是一只狗,年龄为 0 岁。” 但每次我收到这个:“这个动物是默认的,有 0 岁。”

var HisAnimal = Cats()

【问题讨论】:

  • 一些旁注:类名应该是单数的,并且你应该避免在不是绝对必要的地方添加显式类型注释,并且变量名(例如MyAnimal)应该BeLowerCamelCase。
  • 对于语义,您可能还想将属性 name 重命名为例如species,并且可以选择保留前者,以防你真的想存储你邻居狗的 name(希望是 Fred 而不是 dog)。此外,由于species 将是(子)类型唯一的常量,您可以让它成为(不可变)类属性而不是实例属性。
  • 真的Animals(应该命名为Animal)在我看来很抽象(创建Animal 的实例真的有意义吗?)。您可能需要考虑将 Animal 改为协议,并让符合标准的类型使用您想要的任何默认值定义自己的 nameage 属性。

标签: swift inheritance overriding overloading


【解决方案1】:

如果你想要存储而不是计算的属性,你可以在initializer中设置name,像这样:

class Animal {
    let name: String
    var age: Int = 0
    /* designated initializer: fully initializes all instance properties */
    init(name: String) {
        self.name = name
    }
    func details() -> String {
        return "This animal is a \(name) and has \(age) years old."
    }
}


class Dog : Animal {
    /* designated initializer of subclass: must call a designated
       initializer from its immediate superclass                   */
    init() {
        super.init(name: "dog")
    }
}

class Cat : Animal {
    /* ... */
    init() {
        super.init(name: "cat")
    }
}

let myAnimal = Dog()

此机制确保name 仅从一个位置设置,并显式传递给初始化程序。

【讨论】:

    【解决方案2】:

    在类上交替使用协议(和泛型)

    您很可能会创建Cat:s 和Dog:s 的实例,但您可能不想为抽象的怪异Animal:s 这样做。 Animal 作为一个通用超类的另一种选择是让它成为一个协议。

    protocol Animal {
        static var species: String { get }
        var name: String? { get }
        var age: Int { get set }
        var details: String { get }
    }
    
    extension Animal {
        static var species: String { return "\(self)".lowercased() }
    
        // since all the properties used in 'details' are blueprinted,
        // we might as well supply a default implementation of it.
        var details: String {
            return "This animal is a \(Self.species)\(name.map{ " named \($0)" } ?? ""), aged \(age)."
        }
    }
    
    struct Dog: Animal {
        let name: String?
        var age: Int
    
        init(age: Int, name: String? = nil) {
            self.name = name
            self.age = age
        }
    }
    
    var myDog = Dog(age: 3, name: "Fred")
    print(myDog.details) // This animal is a dog named Fred, aged 3.
    myDog.age = 4 // grats to Fred!
    print(myDog.details) // This animal is a dog named Fred, aged 4.
    
    let wildDog = Dog(age: 6) // never named ...
    print(wildDog.details) // This animal is a dog, aged 3.
    

    请注意,我选择使用类属性species 来命名每种动物的物种,并为那些被命名的动物保留实例属性name;比如说,你亲爱的狗叫Fred(而不是你亲爱的狗叫dog)。

    使用协议也将自然而然地选择泛型而不是类型化抽象类型(后者在使用通用超类时可能很诱人):

    struct Cat: Animal {
        let name: String?
        var age: Int
    
        init(age: Int, name: String? = nil) {
            self.name = name
            self.age = age
        }
    }
    
    var wildCat = Cat(age: 2)
    
    func yieldBirthday<T: Animal>(for animal: inout T) {
        print(animal.details)
        animal.age += 1
        print("This \(T.species) now had a birthday!")
        print(animal.details)
    }
    
    yieldBirthday(for: &myDog)
    /* This animal is a dog named Fred, aged 4.
       This dog now had a birthday!
       This animal is a dog named Fred, aged 5. */
    
    yieldBirthday(for: &wildCat)
    /* This animal is a cat, aged 2.
       This cat now had a birthday!
       This animal is a cat, aged 3. */
    

    【讨论】:

    • 这确实是一个非常“Swifty”的解决方案
    • 我怎样才能更多地了解协议?这似乎是最终的解决方案!
    • @Ben 我建议您从Swift Language Guide - Protocols 开始。从那里开始,只需在谷歌上搜索“swift 协议”,您就会发现许多参考资料和博客文章,涵盖了该主题的不同有趣方面、用例和示例。还可以查看有关该主题的相关 WWDC14、15 和 16 演讲。
    【解决方案3】:

    一种解决方法是使用“模板方法模式”

    class Animals {
        lazy var name : String = self.defaultName
        var defaultName:String { return "default" }
    
        func Details()-> String{
            return "This animal is a \(name) and has \(age) years old."
        }
    }
    
    class Dogs : Animals {
        override var defaultName:String { return "dog" }
    }
    

    另一种方法是为每个子类创建init 方法并覆盖默认值

    【讨论】:

      【解决方案4】:

      这是我将如何做到这一点,通过使用初始化器来设置名称(使用默认值):

      class Animal: CustomStringConvertible {
          let name: String
          let age: Int
      
          init(name: String = "default", age: Int = 0) { // FIXME: Does a default age of 0 make sense?
              self.name = name
              self.age = age
          }
      
          public var description: String {
              return "This animal is a \(name) and is \(age) years old."
          }
      }
      
      
      class Dog: Animal {
          init() {
              super.init(name: "Dog")
          }
      }
      
      class Cat: Animal {
          init() {
              super.init(name: "Cat")
          }
      }
      
      var myAnimal = Dog()
      print (myAnimal)
      

      【讨论】:

        猜你喜欢
        • 2020-12-04
        • 2017-08-03
        • 1970-01-01
        • 1970-01-01
        • 2013-12-12
        • 1970-01-01
        • 1970-01-01
        • 2011-02-13
        • 1970-01-01
        相关资源
        最近更新 更多