1

As an example below, should the isClient() function be part of the Attendee class?

import Foundation

///A Node that can be used with the CriteriaEvaluator to determine if clients are present in a Meeting.
class ClientCriteria  : Node {

/**
 Calculates a score from 0..1 to identify the likelihood that a list of Attendees contains a client
 */
func score(attendees : [Attendee]) -> Double
{
    for attendee in attendees
    {
        if isClient(attendee: attendee)
        {
            return 1
        }
    }
    return 0
}

private func isClient(attendee: Attendee) -> Bool
{
    let IBMEmailRegex = "[\\/\\w+\\s*]+\\/IBM\\@?\\w*$"
    if attendee.email.contains(IBMEmailRegex) {
        return false
    }
    else{
        return true
    }
}

}

3
  • "IBMEmailRegex" that seems really specific, and not particularly relevant to a general class like ClientCriteria
    – Alexander
    Apr 30, 2017 at 4:41
  • Also, the score(attendees:) isClient methods can be static, and score(attendees:) can just be: return attendees.contains(where: ClientCriteria.isClient) ? 1 : 0
    – Alexander
    Apr 30, 2017 at 4:43
  • I would put this in the class, so that if you change how to know if an attendee is a client, you know where to change it! Maybe you want to start counting employees of other departments as clients for example.
    – user253751
    May 4, 2017 at 3:05

4 Answers 4

3

isClient() is a true function. It doesn't rely on state and always returns the same value given the same input.

This means it can be used anywhere. If it's valuable elsewhere, consider moving it to the super class, or perhaps into a utility class. If it's only valuable here, then keep it here to prevent clutter. You can always move it later if someone else can make use of it.

one minor personal preference: I prefer

return !attendee.email.contains(IBMEmailRegex) 

to what you have here. I prefer small code :)

4
  • That makes sense. In the case of functions that are influenced by state how do you determine the class it belongs to? Do you have anything you can point me to that defines true functions and other types of function? Feb 23, 2017 at 15:22
  • Look up Functional Programing in Wikipedia to start, but that article may be a bit too Mathmatical. Manning has a book, Grokking Functional Programming, in it's MEAP program, so it's readable, but no complete. You can probably find other books and articles as well by searching for tutorials. As far as state goes, methods which depend on state belong in the class that holds the state, or it descendants in classic OO languages.
    – WillD
    Feb 23, 2017 at 15:30
  • @Deco the important points about the function you showed are that (1) If doesn't read anything other than its input parameters, and (2) it doesn't modify anything. That's why it's a true function.
    – Simon B
    Feb 23, 2017 at 17:03
  • isClient is a pure function, you got that right, but it does access state. It accesses the email state of an Attendee object.
    – Daniel T.
    May 4, 2017 at 1:46
2

As noted in another answer, the specific function does not access the state of the object. Therefore it can be defined anywhere.

But any function that does access the state should be defined in the class itself. That's what encapsulation is all about.

5
  • any function that does access the state shouldn't that be 'any function that does access private state'? See e.g. drdobbs.com/cpp/how-non-member-functions-improve-encapsu/…
    – stijn
    Feb 23, 2017 at 18:58
  • This is true, but if it accesses state it is a method, not a function :)
    – WillD
    Feb 23, 2017 at 20:08
  • @WillD: Depends, in Java it's called method, in C++ function or member function. Feb 23, 2017 at 21:07
  • 1
    @WillD, method is just another name for "member function". If a function accesses state, then it's not a pure function. Many languages still call it a function though.
    – David Arno
    Feb 24, 2017 at 8:56
  • " then it's not a pure function" - exactly my point.
    – WillD
    Feb 24, 2017 at 13:23
1

The reason not to make it a method of Attendee is not because it would not access Attendee data, it would access attendee data were you to make it a method member (email address). The mask to compare it to though is quite arbitrary, it is knowledge outside the Attendee domain. That is why you would not include it. Next thing you would add a method that checks if the attendee has children, which is another thing unrelated to attendence and before you know it you have a God class.

So look at the scope of the data used. If it deals with attendee stuff only, make it a method. If there is other kind of knowledge involved, take it outside or inject the foreign data into the method through an argument.

1

A general rule of thumb is that if a method doesn't access self and it isn't there to implement/override a protocol/super-class method, then it's on the wrong class. If there is a different class where it would access self if it was moved there, then that's where it should be. In this case, isClient relies on Attendee so it should be attached to Attendee. However, since it doesn't rely on any private state, it should be put in an extension.

Let me explain the reasoning for making it an extension... Ultimately, the isClient method as defined below is a global function that takes one argument... The only difference between func Attendee.isClient: Bool and func isClient(attendee: Attendee) -> Bool is what it looks like at the call site. There is no other difference! By putting the method in an extension, this fact is made more evident. (If it was on the object itself, that would imply that the method requires private parts of the class to function.)

Also, since it's O(1) and always returns the same value given the same input, it should be defined as a computed property:

extension Attendee {
    var isClient: Bool {
        let IBMEmailRegex = "[\\/\\w+\\s*]+\\/IBM\\@?\\w*$"
        return email.contains(IBMEmailRegex) == false
    }
}

I see that score also doesn't depend on self... Maybe score is implementing a protocol that you didn't mention? If not, this should also be moved out of the class, but since it's implementation is O(n) it should be a function:

extension Sequence where Iterator.Element == Attendee {
    /// Calculates a score from 0..1 to identify the likelihood that a list of Attendees contains a client
    func score() -> Double {
        return contains(where: { $0.isClient }) ? 1 : 0
    }
}

Maybe ClientCriteria contains an array of attendees? If so, then I would leave it in the ClientCriteria class

class ClientCriteria : Node {
    private var attendees: [Attendees] = []
    /// Calculates a score from 0..1 to identify the likelihood that a list of Attendees contains a client
    func score() -> Double {
        return attendees.contains(where: { $0.isClient }) ? 1 : 0
    }
}

or:

class ClientCriteria : Node {
    private (set) var attendees: [Attendees] = [] // at least the setter should probably be private.
}

extension ClientCriteria {
    /// Calculates a score from 0..1 to identify the likelihood that a list of Attendees contains a client
    func score() -> Double {
        return attendees.contains(where: { $0.isClient }) ? 1 : 0
    }
}
2
  • What's the reasoning behind taking functions that don't rely on private properties outside of the class? May 3, 2017 at 21:11
  • Because they are usable by anything that has an object of that type. I'll edit the answer to explain better.
    – Daniel T.
    May 3, 2017 at 21:13

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.