Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Require calling method M after setting property P

I have a property and a method:

class C {
    var myProperty = ""
    func myFunc() {

    }
}

I would like to have a rule that when you set myProperty you must then proceed to call myFunc. Of course I can have a mental rule that requires this, but a mental contract is no contract at all. I want to enforce this contract through the class's API.

(NOTE All of this is purely about what goes on inside C. Both myProperty and myFunc can be private as far as visibility outside C is concerned; that doesn't affect the problem.)

Obviously we can do this with a setter observer:

class C1 {
    var myProperty = "" {
        didSet {
            self.myFunc()
        }
    }
    func myFunc() {

    }
}

However, there's something I don't like about that solution: it relies upon what amounts to a hidden side effect. It is up to me to know that saying self.myProperty = "..." also triggers a call to myFunc. I don't like that kind of hidden side effect; it makes my code harder to understand, and violates one of my key programming principles, Say What You Mean.

What do I mean? I mean something like this:

self.setMyPropertyAndCallMyFunc("...")

I like that, because it tells me exactly what this call does every time it occurs in my code. So we could write this:

class C2 {
    var myProperty = ""
    func myFunc() {

    }
    func setMyPropertyAndCallMyFunc(_ s:String) {
        self.myProperty = s
        self.myFunc()
    }
}

But there's a problem with that, namely that it is now possible to set myProperty without calling myFunc, breaking the rule that we set out to enforce in the first place! The only way I've found to enforce that rule is to have another class that defends the property with privacy. For example:

class C3 {
    class Inner {
        private(set) var myProperty = ""
        private func myFunc() {

        }
        func setMyPropertyAndCallMyFunc(_ s:String) {
            self.myProperty = s
            self.myFunc()
        }
    }
    let inner = Inner()
}

That is in fact what I'm doing, but it seems a bit nutty to have a second class just to enforce these privacy and naming rules. My question is: Is there another way?

like image 913
matt Avatar asked Mar 06 '26 08:03

matt


1 Answers

Yes, though I don't know if it's actually any better. You can hide myProperty inside of a closure rather than inside of a class/struct. For example:

class C {
    public var myProperty: String { return _myPropertyGetter() }
    public func setMyPropertyAndCallMyFunc(value: String) {
        _myPropertySetter(self, value)
    }

    func myFunc() {
        print("myFunc")
    }

    private let (_myPropertyGetter, _myPropertySetter): (() -> String, (C, String) -> ()) = {
        var property = ""
        return ({ property }, { property = $1; $0.myFunc() })
    }()
}

let c = C()
c.myProperty
c.setMyPropertyAndCallMyFunc(value: "x")
c.myProperty

There's a lot of complexity here, like passing self to _setMyPropertyAndCallMyFunc to allow myFunc to live outside the closure, and maybe some of that could be dispensed with. But the basic idea is to generate two functions that are the only functions that have access to the property storage, making it "super-private" without creating an inner class.

If you don't need myFunc() to be public (and from your Inner example, I think you don't), then you can do it a little simpler, and this almost might be better than Inner.

class C {
    public var myProperty: String { return _myPropertyGetter() }
    public func setMyPropertyAndCallMyFunc(value: String) { _myPropertySetter(value) }

    private let (_myPropertyGetter, _myPropertySetter): (() -> String, (String) -> ()) = {
        var property = ""
        func myFunc() {
            print("myFunc")
        }
        return ({ property }, { property = $0; myFunc() })
    }()
}
like image 191
Rob Napier Avatar answered Mar 08 '26 22:03

Rob Napier