More questions about protocol/value programming


(Rick M) #1

In my schematic capture app, I had a class hierarchy that started with Element. From that I derived PartDefinition and PartInstance. Element has an immutable UUID, name, and description. Element knows how to encode itself as XML by conforming to an XMLCoding protocol.

Now I'm trying to make Element into a Protocol, and make PartDefinition and PartInstance into structs. But I can't quite figure out how to inherit the XMLCoding implementation for Element. That is, Element can encode/decode its UUID, name, and desc. It does the decode in a convenience init(xml:) method that takes an XMLNode to parse.

So how to I add another init(xml:) method to one of my structs (like PartDefinition), and have it call the init(xml:) method on Element? With class inheritance, I was able to do this with a call to super. It's not clear to me how to do this with Protocols.

It needs to be in init() because I want some of the instance variables to be let and non-optional. Not actually sure how to do this.

protocol Element
{
    var uuid : UUID { get }
    var name : String? { get }
    var desc : String? { get }
}

extension Element
{
    init(xml inElement: XMLElement)
    {
        self.uuid = <parse from inElement>
    }
}

class
PartInstance : Element
{
    init()
    {
        self.uuid = UUID()
    }
    
    init(xml inElement: XMLElement)
    {
        // Use shared implementation in Element extension?
    }

    let uuid: UUID
    var name: String?
    var desc: String?
}

class
PartDefinition : Element
{
    etc...
}

I may be missing some aspect of this entirely, but I'm having a hard time seeing how to avoid lots of code duplication in this scenario. Appreciate thoughts and advice…

···

--
Rick Mann
rmann@latencyzero.com


(Karl) #2

A protocol can require that a certain initialiser exists, but it can’t provide a default implementation (you can add initialisers in a protocol extension IIRC, but then they must delegate to an actual, required initialiser).

A protocol is not the same thing as a subclass. Any type could conform to a protocol. In that case, your init method wouldn’t be valid - there may be other stored properties besides “uuid”; in fact, “uuid” may not be a stored property at all by that type. For example, I could write an extension for String or Array<T> which makes them now conform to Element. The fact that “uuid” isn’t declared mutable but you are trying to set it, is a sign that there is not enough knowledge about the type to do this.

If you do have some common properties and want to encapsulate their logic, create a struct. For example, you might create an “XMLHeader” struct, and PartDefinition and PartInstance can contain it by composition. You would need to add forwarding accessors, but they can be added in an extension to avoid cluttering your code.

e.g.:

// Could this even be a nested type inside XMLElement, perhaps? That would be even cleaner. Maybe there could then be a “var header: Header?" computed property on XMLElement?

struct XMLElementHeader {
  let uuid : UUID
  var name : String?
  var desc : String?

  static func parse(_ xml: XMLElement) -> XMLElementHeader? {
    // Validate xml
    return XMLElementHeader(uuid: ..., name: .none, desc: .none)
  }
}

class PartInstance {

  var header : XMLElementHeader

  init(xml: XMLElement) {
    guard let parsedHeader = XMLElementHeader.parse(xml) else { fatalError() }
    header = parsedHeader
  }
}

class PartDefinition {
  
  var header : XMLElementHeader

  init(xml: XMLElement) {
    guard let parsedHeader = XMLElementHeader.parse(xml) else { fatalError() }
    header = parsedHeader
  }
}

Then, if you want to provide a nicer interface, do it with an extension:

protocol Element {
  var uuid : UUID { get }
  var name : String? { get, set }
}

extension PartInstance : Element {
  var uuid : UUID { return header.uuid }
  var name : String? { get { return header.name }
              set { header.name = newValue } }
}

That’s how I would do it, anyway.

Karl

···

On 1 Aug 2016, at 22:32, Rick Mann via swift-users <swift-users@swift.org> wrote:

In my schematic capture app, I had a class hierarchy that started with Element. From that I derived PartDefinition and PartInstance. Element has an immutable UUID, name, and description. Element knows how to encode itself as XML by conforming to an XMLCoding protocol.

Now I'm trying to make Element into a Protocol, and make PartDefinition and PartInstance into structs. But I can't quite figure out how to inherit the XMLCoding implementation for Element. That is, Element can encode/decode its UUID, name, and desc. It does the decode in a convenience init(xml:) method that takes an XMLNode to parse.

So how to I add another init(xml:) method to one of my structs (like PartDefinition), and have it call the init(xml:) method on Element? With class inheritance, I was able to do this with a call to super. It's not clear to me how to do this with Protocols.

It needs to be in init() because I want some of the instance variables to be let and non-optional. Not actually sure how to do this.

protocol Element
{
   var uuid : UUID { get }
   var name : String? { get }
   var desc : String? { get }
}

extension Element
{
   init(xml inElement: XMLElement)
   {
       self.uuid = <parse from inElement>
   }
}

class
PartInstance : Element
{
   init()
   {
       self.uuid = UUID()
   }

   init(xml inElement: XMLElement)
   {
       // Use shared implementation in Element extension?
   }

   let uuid: UUID
   var name: String?
   var desc: String?
}

class
PartDefinition : Element
{
   etc...
}

I may be missing some aspect of this entirely, but I'm having a hard time seeing how to avoid lots of code duplication in this scenario. Appreciate thoughts and advice…

--
Rick Mann
rmann@latencyzero.com

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Rick M) #3

Thanks, Karl, that's great!

In the end, I think I need to use classes, because I do have reference semantics in my model (e.g. a part definition can be referenced by multiple instances).

But this helps me get my head around the techniques. Thanks!

···

On Aug 1, 2016, at 21:39 , Karl <razielim@gmail.com> wrote:

A protocol can require that a certain initialiser exists, but it can’t provide a default implementation (you can add initialisers in a protocol extension IIRC, but then they must delegate to an actual, required initialiser).

A protocol is not the same thing as a subclass. Any type could conform to a protocol. In that case, your init method wouldn’t be valid - there may be other stored properties besides “uuid”; in fact, “uuid” may not be a stored property at all by that type. For example, I could write an extension for String or Array<T> which makes them now conform to Element. The fact that “uuid” isn’t declared mutable but you are trying to set it, is a sign that there is not enough knowledge about the type to do this.

If you do have some common properties and want to encapsulate their logic, create a struct. For example, you might create an “XMLHeader” struct, and PartDefinition and PartInstance can contain it by composition. You would need to add forwarding accessors, but they can be added in an extension to avoid cluttering your code.

e.g.:

// Could this even be a nested type inside XMLElement, perhaps? That would be even cleaner. Maybe there could then be a “var header: Header?" computed property on XMLElement?

struct XMLElementHeader {
  let uuid : UUID
  var name : String?
  var desc : String?

  static func parse(_ xml: XMLElement) -> XMLElementHeader? {
    // Validate xml
    return XMLElementHeader(uuid: ..., name: .none, desc: .none)
  }
}

class PartInstance {

  var header : XMLElementHeader

  init(xml: XMLElement) {
    guard let parsedHeader = XMLElementHeader.parse(xml) else { fatalError() }
    header = parsedHeader
  }
}

class PartDefinition {
  
  var header : XMLElementHeader

  init(xml: XMLElement) {
    guard let parsedHeader = XMLElementHeader.parse(xml) else { fatalError() }
    header = parsedHeader
  }
}

Then, if you want to provide a nicer interface, do it with an extension:

protocol Element {
  var uuid : UUID { get }
  var name : String? { get, set }
}

extension PartInstance : Element {
  var uuid : UUID { return header.uuid }
  var name : String? { get { return header.name }
              set { header.name = newValue } }
}

That’s how I would do it, anyway.

Karl

On 1 Aug 2016, at 22:32, Rick Mann via swift-users <swift-users@swift.org> wrote:

In my schematic capture app, I had a class hierarchy that started with Element. From that I derived PartDefinition and PartInstance. Element has an immutable UUID, name, and description. Element knows how to encode itself as XML by conforming to an XMLCoding protocol.

Now I'm trying to make Element into a Protocol, and make PartDefinition and PartInstance into structs. But I can't quite figure out how to inherit the XMLCoding implementation for Element. That is, Element can encode/decode its UUID, name, and desc. It does the decode in a convenience init(xml:) method that takes an XMLNode to parse.

So how to I add another init(xml:) method to one of my structs (like PartDefinition), and have it call the init(xml:) method on Element? With class inheritance, I was able to do this with a call to super. It's not clear to me how to do this with Protocols.

It needs to be in init() because I want some of the instance variables to be let and non-optional. Not actually sure how to do this.

protocol Element
{
  var uuid : UUID { get }
  var name : String? { get }
  var desc : String? { get }
}

extension Element
{
  init(xml inElement: XMLElement)
  {
      self.uuid = <parse from inElement>
  }
}

class
PartInstance : Element
{
  init()
  {
      self.uuid = UUID()
  }

  init(xml inElement: XMLElement)
  {
      // Use shared implementation in Element extension?
  }

  let uuid: UUID
  var name: String?
  var desc: String?
}

class
PartDefinition : Element
{
  etc...
}

I may be missing some aspect of this entirely, but I'm having a hard time seeing how to avoid lots of code duplication in this scenario. Appreciate thoughts and advice…

--
Rick Mann
rmann@latencyzero.com

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Rick Mann
rmann@latencyzero.com