-1
\$\begingroup\$

Say I'm using Parse Server and utilising the PFObject class from the iOS SDK. Say I want to have a Chat class.

To use inheritance code (design 1):

import Foundation
import Parse

class ChatInheritance: PFObject, PFSubclassing {

    init(parseObject: PFObject, chatStatus: ParseChatStatus? = nil) {
        self.parseObject = parseObject
        self.chatStatus = chatStatus
    }

    var chatStatus: ChatStatus? {
        set {
            self["status"] = newValue
        }
        get {
            return self["status"]
        }
    }

    var lastMessage: ParseMessage? {
        get {
                return parseObject.objectForKey("lastMessage")
        }
        set {
            parseObject.setObject(newValue, forKey: "lastMessage")
        }
    }

    var participants: [User]? {
        get {
            return self["participants"]

        }
        set {
            self["participants"] = newValue
        }
    }

    func participantsWithoutUser(user: User) -> [User] {
        return participants.filter { $0 != user }
    }

    class func parseClassName() -> String {
        return "Chat"
    }
}

About 55 lines of code.

The composition route being proposed is involves this construction (design 2)

import Foundation

protocol Chat {
    var chatId: String { get }
    var participants: [User] { get }
    var lastMessage: ChatMessage { get set }
    var chatStatus: ChatStatus? { get }
    var updatedAt: NSDate { get }
    func participantsWithoutUser(user: User) -> [User]
}

and then utilising delegation essentially to create the ParseChat class:

import Foundation
import Parse

class ParseChat: Chat {

    var parseObject: PFObject
    private var parseParticipants: [User]?
    private var parseLastMessage: ParseChatMessage?

    init(parseObject: PFObject, chatStatus: ParseChatStatus? = nil) {
        self.parseObject = parseObject
        self.chatStatus = chatStatus
    }

    var chatId: String {
        return parseObject.objectId!
    }

    private(set) var chatStatus: ChatStatus?

    var lastMessage: ChatMessage {
        get {
            if let parseLastMessage = parseLastMessage {
                return parseLastMessage
            } else {
                parseLastMessage = ParseChatMessage(parseObject: parseObject.objectForKey("lastMessage") as! PFObject)
                return parseLastMessage!
            }
        }
        set {
            parseLastMessage = (newValue as! ParseChatMessage)
            parseObject.setObject(parseLastMessage!.parseObject, forKey: "lastMessage")
        }
    }

    var participants: [User] {
        if let parseParticipants = parseParticipants {
            return parseParticipants
        } else {
            let parseUsers = parseObject.objectForKey("participants") as! [PFUser]
            var users = [User]()
            for u in parseUsers {
                users.append(ParseUser(parseObject: u) as User)
            }
            parseParticipants = users
            return users
        }
    }

    var updatedAt: NSDate {
        return parseObject.updatedAt!
    }

    func participantsWithoutUser(user: User) -> [User] {
        return participants.filter { $0 != user }
    }
}

Approximately 90 lines of code. Also, by inheriting from PFObject I get a lot of functionality for free.

One of my most valued design principles is "less code is better", and clearly since there is significant heavy lifting and functionality in the PFObject class I want to utilise, why would I every choose the Protocol + Delegate method (design 2), at least in this case?

\$\endgroup\$
1
  • \$\begingroup\$ There are metrics other than lines of code by which to judge the quality of code. Lines of code by itself is not a meaningful metric. \$\endgroup\$
    – nhgrif
    Commented Aug 13, 2016 at 13:42

2 Answers 2

2
\$\begingroup\$

The Protocol + Delegate method is a standard pattern in Cocoa/Cocoa Touch programming, and is used by Apple in many ways (i.e., NSTableViewControllerDeleagte, NSFetchedResultsControllerDelegate, CLLocationManagerDelegate, etc.) due to its versatility. As such, it's a common practice and is easy to understand by most programmers.

The inheritance code looks to be quite valid, but it's a bit harder to read and more difficult to understand.

You mention that

One of my most valued design principles is "less code is better"

which I would dare say will lead to problems eventually. Sacrificing legibility for brevity doesn't lead to maintainable code and is hard for others to work with, if need be.

\$\endgroup\$
0
0
\$\begingroup\$

Why would I ever choose the Protocol + Delegate method (design 2), at least in this case?

In this case...

Ultimately it comes down to you design goals and the expected lifetime of your app (how long will you or someone else have to support it?). In my opinion, based on what you described, using protocols provides a more flexible design that is better prepared to handle changes down the road.


The Details

Let me expand on my answer by first saying that Inheritance vs. Composition type discussions are subjective and are debated amongst developers of various languages. There are many blogs, articles and posts on StackExchange sites that cover the debate.

Also, since we are talking Swift, I would highly recommend taking a look at this WWDC session:

Protocol-Oriented Programming in Swift https://developer.apple.com/videos/play/wwdc2015/408/

I highlight this session because instead of Inheritance vs. Composition, we should be discussing Inheritance vs. Protocols. Why? Because not only do protocols allow you to do composition, they also provide other features that let you model things in interesting and flexible ways.

I would also like to point out that this question is challenging to answer (though I think it is a good question), because you have two seemingly working implementations in the present, but your design choices may not reveal their true cost until some unknown time in the future. This is where the advice based on the experiences of others will inevitably reveal itself but at the same time seem subjective if you cannot relate to their experiences.

Inheritance

Is Chat really a PFObject?

According to Parse documentation:

The PFObject class is a local representation of data persisted to the Parse cloud.

Someone consuming your ChatInheritance class will get the entire public interface of PFObject. This is a lot of mental baggage that doesn't seem to be related to using ChatInheritance class.

PFObject and the concept of chat don't seem to be related at all.

I would think of family of chat objects as:

       Chat
        |
   ___________
   |         |
SMSChat    IMChat    

You are also tightly coupled to the PFObject.

What happens if you want to replace Parse in the future? You will likely have to throw away the ChatInheritance implementation and create something new.

Protocols

Your Chat protocol provides an abstraction for what the concept of chat means to your application. It conveys this concept without dictating its implementation.

It doesn't give you working implementation, but it does score you a point for the future maintenance of your app. It gives you flexibility.

With Chat now a defined concept, other types can adopt it, filling in the details of what Chat means to the conforming type (in this case ParseChat).

Combining this with delegation (as you have), puts you on the path to a decoupled, flexible ParseChat type that doesn't expose the public interface of PFObject and doesn't require your consumer to know implementation details about how Parse is being used to facilitate "chatting".

"Less code is better"

You don't say why this is or how you prioritize this design principle against things like readability. Is it better because its less to maintain? Less to look at?

Personally I would add a couple of caveats to this statement:

  • ... if it doesn't obfuscate understanding
  • ... if it is not just for the sake of writing less code. Similar to the first caveat, but has more to do with your motivation rather than the end result.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.