2
\$\begingroup\$

I need some advice on my code structure. I have read that setters and getters break one of the OOP principals, encapsulation, and that we should avoid them if possible but I cannot think of any other way to expose info other than getters.

I have a class which calculates price, savings and taxes based three inputs from the user through the initializer (initialPrice, discount and taxRate).

I'm using three computer properties to exposed the results, is this considered a bad thing? If yes, why and how can I improve my code?

Besides what I described above the class also has the ability to add all of the items in an array, assuming the instances of the CalculatorBrain were placed in an array.

Again, I'm more concern about the structure of my code. Any advice will be appreciated.

class CalculatorBrain {  

        private var taxRate:Double = 0  
        private var discount:Double = 0  
        private var initialPrice:Double = 0  

        func setInitialValues(initialPrice:Double, discount:Double, taxRate:Double){  
            self.taxRate = taxRate  
            self.discount = discount  
            self.initialPrice = initialPrice  
        }  

        private func salePrice()->Double{  
            return initialPrice - savings  
        }  

        var price:Double{  
            get{  
                return salePrice() + taxes  
            }  
        }  

        var savings:Double{  
            get{  
                return initialPrice * discount / 100  
            }  
        }  

        var taxes:Double{  
            get{  
                return salePrice() * taxRate / 100  
            }  
        }  

        internal func grandTotals(list:Array<Item>)->(grandPrice:Double, grandSavings:Double, grandTaxes:Double){  
            var grandPrice:Double = 0  
            var grandSavings:Double = 0  
            var grandTaxes:Double = 0  

            for i in 0..<list.count {  
                grandPrice = grandPrice + Double(list[i].totalPrice)  
                grandSavings =  grandSavings + Double(list[i].savings)  
                grandTaxes = grandTaxes + Double(list[i].taxes)  
            }  
            return (grandPrice, grandSavings, grandTaxes)  
        }  
    } 
\$\endgroup\$
3
  • \$\begingroup\$ Just to make sure I understand your input, are you saying that my getters may not be so appropriate because they are exposing computed data? Sorry if I misunderstood it. Thanks \$\endgroup\$
    – fs_tigre
    Commented Jul 21, 2016 at 13:11
  • 2
    \$\begingroup\$ Titles on code review should describe what your code does, not what you want to achieve from the review. Please edit your question to try and summarise the purpose of your code. \$\endgroup\$
    – forsvarir
    Commented Jul 21, 2016 at 13:14
  • 2
    \$\begingroup\$ @forsvarir is correct, you might want to look at this help page codereview.stackexchange.com/help/how-to-ask. Tell what your code does, it will attract more viewers and get you better answers. Ask your question in the body of the question. \$\endgroup\$
    – pacmaninbw
    Commented Jul 21, 2016 at 13:33

1 Answer 1

2
\$\begingroup\$

Set Initial Values?

private var taxRate:Double = 0  
private var discount:Double = 0  
private var initialPrice:Double = 0  

func setInitialValues(initialPrice:Double, discount:Double, taxRate:Double){  
    self.taxRate = taxRate  
    self.discount = discount  
    self.initialPrice = initialPrice  
}  

I don't care for this at all.

First, I don't see a good reason for these three properties to necessarily be private. Why should the user have to guess what they've set their properties to?

Further more, you're not protecting these values from being reset. The user simply has to call setInitialValues again. The method name may be an attempted hint at that being the incorrect use of the class, but my mantra is let the compiler do as much for us as possible.

We should expose these properties to the user. We should not allow the user to modify these properties after initialization. We should write an init method.

let taxRate: Double
let discount: Double
let basePrice: Double

init(taxRate: Double = 0, discount: Double = 0, basePrice: Double = 0) {
    self.taxRate = taxRate
    self.discount = discount
    self.basePrice = basePrice
}

Why is salePrice() a function?

You have implemented basically everything else as a computed, read-only property. Why can't salePrice also be a computed, read-only property? If you want it to be private, that's fine if that makes sense for your class, but it can still be a computed property.

var salePrice: Double {
    return basePrice - savings
}

Also, note what I've done here. The get { } is omitted. Computed properties that just return a value can omit the get and are implicitly read-only. Any code is assumed to be for the get if you have omitted the get & set keywords.


internal is the default access level.

You have marked some things as private. You have omitted an access specifier on most things, and then your grand total method is specified as internal. Marking internal is the same as omitting an access specifier. You should drop this internal keyword here, or consistently use it with all the other properties. None of your members are public in your current implementation.


About that grandTotals method...

This method almost seems out of place if it weren't for the fact that the class is called CalculatorBrain. But it doesn't depend on any state, so it should probably be a static method.

Your loop is far from optimal. But, we don't even need a loop. We can use reduce.

return list.reduce((0,0,0)) { 
    return (
        $0.totalPrice + $1.totalPrice,
        $0.savings + $1.savings,
        $0.taxes + $1.taxes
    )
}

But... I might avoid returning a tuple. Why not just make a struct?

struct GrandTotal {
    let price: Double
    let savings: Double
    let taxes: Double
}

And now instead of a method that feels like it's in a weird place, we just add an appropriate init:

struct GrandTotal {
    let price: Double
    let savings: Double
    let taxes: Double

    init(list: [Item]) {
        let totals = list.reduce((0,0,0)) { 
            return (
                $0.totalPrice + $1.totalPrice,
                $0.savings + $1.savings,
                $0.taxes + $1.taxes
            )
        }
        
        price = totals.0
        savings = totals.1
        taxes = totals.2
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Thanks a lot for the very GOOD suggestions I will refactor to your suggestions. Quick question, on your first suggestion you suggest to make my variables inmutables (let) which makes a lot of sense but, so I know, can you please show me how could the user modify their values? I thought that by being private was enough since they are not exposed. I just don't know how could the user modify its values, sorry. Thanks a LOT! \$\endgroup\$
    – fs_tigre
    Commented Jul 24, 2016 at 0:22
  • 1
    \$\begingroup\$ All they have to do to modify them in your original implementation is call setInitialValues multiple times. Nothing in your code prevents that. \$\endgroup\$
    – nhgrif
    Commented Jul 24, 2016 at 0:25
  • 1
    \$\begingroup\$ But if you want them to be immutable, use let. The compiler can sometimes make some optimizations over let variables which can't be made over var variables. \$\endgroup\$
    – nhgrif
    Commented Jul 24, 2016 at 0:27
  • \$\begingroup\$ I see, thanks a lot for the clarification. I really appreciate it. \$\endgroup\$
    – fs_tigre
    Commented Jul 24, 2016 at 0:33

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.