I thought about how to use Swift's willSet did Set properly.

First post for beginners with 3 months of Swift history

While learning Swift on your own, you will use the output to flesh out the knowledge you have learned. It is the first time to output itself, and it also serves as an output practice.

The task I made this time

Learn while refactoring a class of rectangular parallelepipeds whose sides are Int type.

Purpose

To be able to use the learned knowledge by coding it in a form close to practice.

-About the usefulness of ** willSet ** and ** didSet ** --Usefulness of ** Read Only Property ** using Compute Properties --Resolving the violation of the DRY principle

Condition: What kind of rectangular parallelepiped?

--Has Int type height and width properties --The maximum on one side is 6 --Has an Int type volume property --Has an enum-type size (S, M, L) property that changes with volume --The size condition is S (1 ... 8) M (9 ... 64) L (65 ... 216)

Initial code

class IntBox{
    enum Size{
        case S, M, L
    }

    //Horizontal and vertical height
    var width: Int
    var height: Int
    var depth: Int

    //volume
    var volume: Int

    //size
    var size:Size
    
    init(w: Int, h: Int, d: Int){
	//Initialization of horizontal and vertical height
        width = w
        height = h
        depth = d
	
	//Volume initialization
        volume = w * h * d
	
	//Size initialization
        switch volume{
        case 1...8:
            size = .S
        case 9...64:
            size = .M
        default:
            size = .L
        }
    }

    //A function that displays a status list is provided for verification.
    func printStatus(){
        var status = """
        ---status---
        width: \(width)
        height: \(height)
        depth: \(depth)
        volume: \(volume)
        size: \(size)
        """
        print(status)
    }
}

Where to plunge

  1. The condition of maximum 6 on each side is ignored.
  2. No error occurs when the length of one side is 0 or less.
  3. The volume and size do not change even if the length of the side is changed.
  4. The volume can be changed from the outside.
  5. The size can be changed from the outside.

Verification

let cb = IntBox(w: 1, h: 1, d: 1)
cb.printStatus()

//Try changing the width
cb.width = 11
cb.printStatus()

//Try changing the volume
cb.volume = -20
cb.printStatus()

//Try changing the size
cb.size = .L
cb.printStatus()

Execution result

---status---
width: 1
height: 1
depth: 1
volume: 1
size: S

---status---
width: 11
height: 1
depth: 1
volume: 1 #Should change!!
size: S

---status---
width: 11
height: 1
depth: 1
volume: -20 #It is strange that 0 or less can not be set(Volume depends on the length of each side)
size: S

---status---
width: 11
height: 1
depth: 1
volume: -20
size: L #It is strange that it can be set as well as the volume. Think about dependencies

So I will solve it one by one.

# 1 Validation

[Pushing point](#Pushing point) 1 and 2 are solved.

1 The condition of a maximum of 6 on each side is ignored.

2 No error occurs when the length of one side is 0 or less.

Insert the following at the beginning of the initialization.

if w <= 0 || w > 6 {fatalError("w:\(w) not in 1...6")}
if h <= 0 || h > 6 {fatalError("h:\(h) not in 1...6")}
if d <= 0 || d > 6 {fatalError("d:\(d) not in 1...6")}

# 2 willSet and didSet

[Pushing point](#Pushing point) 3 is solved.

The volume and size do not change even if the length of the three sides is changed.

I want to change the volume and size when the height and height values change. Add ** willSet ** and ** didSet ** to each property and set the volume and size there.

will did what to do at which timing

Validation means verifying whether you can set the length you want to change, so before setting it. After setting, the volume and size will change due to the change in length.

--willSet: Validation --detSet: Volume and size set

var width: Int{
    willSet{
        if newValue<= 0 || newValue> 6 {fatalError("<width: \(newValue)> not in 1...6")}
    }
    didSet{
        _volume = width * height * depth
        switch _volume{
        case 1...8:
            _size = .S
        case 9...64:
            _size = .M
        default:
            _size = .L
        }
    }
}

var height: Int{
    willSet{
        if newValue<= 0 || newValue> 6 {fatalError("<height: \(newValue)> not in 1...6")}
    }
    didSet{
        _volume = width * height * depth
        switch _volume{
        case 1...8:
            _size = .S
        case 9...64:
            _size = .M
        default:
            _size = .L
        }
    }
}

var depth: Int{
    willSet{
        if newValue<= 0 || newValue> 6 {fatalError("<depth: \(newValue)> not in 1...6")}
    }
    didSet{
        _volume = width * height * depth
        switch _volume{
        case 1...8:
            _size = .S
        case 9...64:
            _size = .M
        default:
            _size = .L
        }
    }
}

# 3 Read Only Property.

[Pushing point](#Pushing point) Solves 4 and 5.

4 The volume can be changed from the outside. 5 The size can be changed from the outside.

Setting it to Read Only Property makes it impossible to set.

--Added get {} to volume and size --Keep data in private var _volume and private var _size that cannot be seen from the outside.

private var _volume: Int
private var _size: Size

var volume: Int {
    get {
        return _volume
    }
}

var size: Size {
    get {
        return _size
    }
}

Once completed! ??

.. .. .. It's verbose code as you can imagine.

class IntBox{
    enum Size{
        case S, M, L
    }
    private var _volume: Int
    private var _size: Size

    var width: Int{
        willSet{
            if newValue<= 0 || newValue> 6 {fatalError("<width: \(newValue)> not in 1...6")}
        }
        didSet{
            _volume = width * height * depth
        switch _volume{
            case 1...8:
                _size = .S
            case 9...64:
                _size = .M
            default:
                _size = .L
            }
        }
    }
    
    var height: Int{
        willSet{
            if newValue<= 0 || newValue> 6 {fatalError("<height: \(newValue)> not in 1...6")}
        }
        didSet{
            _volume = width * height * depth
        switch _volume{
            case 1...8:
                _size = .S
            case 9...64:
                _size = .M
            default:
                _size = .L
            }
        }
    }
    
    var depth: Int{
        willSet{
            if newValue<= 0 || newValue> 6 {fatalError("<depth: \(newValue)> not in 1...6")}
        }
        didSet{
            _volume = width * height * depth
        switch _volume{
            case 1...8:
                _size = .S
            case 9...64:
                _size = .M
            default:
                _size = .L
            }
        }
    }
    
    var volume: Int {
        get {
    	    return _volume
        }
    }
    
    var size: Size {
        get {
    	    return _size
        }
    }
    
    init(w: Int, h: Int, d: Int){
        if w <= 0 || w > 6 {fatalError("<w: \(w)> not in 1...6")}
        if h <= 0 || h > 6 {fatalError("<h: \(h)> not in 1...6")}
        if d <= 0 || d > 6 {fatalError("<d: \(d)> not in 1...6")}
        width = w
        height = h
        depth = d
        _volume = w * h * d
        switch _volume{
        case 1...8:
            _size = .S
        case 9..<64:
            _size = .M
        default:
            _size = .L
        }
    }
    
    func printStatus(){
        var status = """
        ---status---
        width: \(width)
        height: \(height)
        depth: \(depth)
        volume: \(volume)
        size: \(size)
        """
        print(status)
    }
}

let cb = IntBox(w: 1, h: 1, d: 1)
cb.printStatus()

//Try changing the width
cb.width = 11
cb.printStatus()

Since there are many violations of the DRY principle, I will create a common function and put it together.

# 4 DRY principle applied

The validation part should be put together in a function

private func lengthValidate(_ length: Int, _ name: String = "length"){
    if length <= 0 || length > 6 { fatalError("<\(name): \(length)> not in 1...6") }
}

Summarize volume calculations and size evaluations

private func setStatus() {
    setVolume()
    setSize()
}

private func setVolume() {
    _volume = width * height * depth
}

private func setSize(){
    switch _volume{
        case 1...8:
            _size = .S
        case 9...64:
            _size = .M
        default:
            _size = .L
    }
}

I also want to eliminate the fact that *** setStatus () *** is called every time the height and width are changed.

It would be nice if ** setStatus () ** could be created when volume or size is referenced. However, there is no change I also want to avoid calling ** setStatus () ** every time volume or size is referenced. I want to call ** setStatus () ** only once after changing the height and width. ** private var shouldSetStatus: Creates a Bool ** and evaluates whether to call ** setStatus () **.

private var shouldSetStatus: Bool

var width: Int = 1 {
    willSet { lengthValidate(newValue, "width") }
    didSet { shouldSetStatus = true } // <-Change
}
...

var volume: Int {
    get {
        if shouldSetStatus { setStatus() }
        return _volume
    }
}
...

private func setStatus() {
    setVolume()
    setSize()
    shouldSetStatus = false // <-add to
}

Post-resolution code for DRY principle violations

class IntBox{
    enum Size{
        case S, M, L
    }
    
    private var _volume: Int = 1
    private var _size: Size = .S
    private var shouldSetStatus: Bool = false
    
    var width: Int = 1 {
        willSet { lengthValidate(newValue, "width") }
        didSet { shouldSetStatus = true }
    }
    var height: Int = 1 {
        willSet { lengthValidate(newValue, "height") }
        didSet { shouldSetStatus = true }
    }
    var depth: Int = 1 {
        willSet { lengthValidate(newValue, "depth") }
        didSet { shouldSetStatus = true }
    }

    var volume: Int {
        get {
            if shouldSetStatus { setStatus() }
            return _volume
        }
    }
    var size: Size {
        get {
            if shouldSetStatus { setStatus() }
    	    return _size
        }
    }
    
    init(w: Int, h: Int, d: Int){
        lengthValidate(w, "w")
        lengthValidate(h, "h")
        lengthValidate(d, "d")
        width = w
        height = h
        depth = d
        setStatus()
    }
    
    private func lengthValidate(_ length: Int, _ name: String = "length"){
    	if length <= 0 || length > 6 { fatalError("<\(name): \(length)> not in 1...6") }
    }
    
    private func setStatus() {
        setVolume()
        setSize()
        shouldSetStatus = false
    }
    
    private func setVolume() {
        _volume = width * height * depth
    }
    
    private func setSize(){
        switch _volume{
            case 1...8:
                _size = .S
            case 9...64:
                _size = .M
            default:
                _size = .L
        }
    }
    
    //Verification code
    func printStatus(){
        let status = """
        ---status---
        width: \(width)
        height: \(height)
        depth: \(depth)
        volume: \(volume)
        size: \(size)
        """
        print(status)
    }
}

//Verification
let cb = IntBox(w: 1, h: 2, d: 3)
cb.printStatus()

//Try changing the width
cb.width = 5
cb.printStatus()

Finally

Originally, it was designed from an abstract protocol so that the maximum value of one side and the evaluation of S, M, L could be changed flexibly. Regarding the error, it is only ** fatalError ** without using ** Optional **, but it is better to return ** nil ** at the time of initialization or separate it from the error at the time of length change. It may be good. .. .. I once wrote an article about my current best efforts. Someday I'll grow to the point where this seems like an embarrassing article. I also spent 2 hours x 3 days on this article. .. .. I will write an article considering cost performance.

Recommended Posts

I thought about how to use Swift's willSet did Set properly.
I was curious about how to use Optional orElse () and orElseGet () properly.
About the matter that I was addicted to how to use hashmap
How to set up and use kapt
I thought about how to make it serverless while avoiding vendor lock-in
How to use Swift's Codable Super personal memo
How to use Map
How to use rbenv
How to use letter_opener_web
How to use with_option
How to use fields_for
How to use java.util.logging
How to use Maven that I can't hear anymore
How to use map
How to use collection_select
Understand how to use Swift's JSON Decoder in 3 minutes
[Rails] I don't know how to use the model ...
How to use Twitter4J
How to use active_hash! !!
How to use MapStruct
How to use hidden_field_tag
How to use TreeSet
[How to use label]
How to use identity
How to use hashes
How to use JUnit 5
How to use Dozer.mapper
How to use Gradle
How to use org.immutables
How to use java.util.stream.Collector
How to use VisualVM
How to use Map
A story about misunderstanding how to use java scanner (memo)
[Java] How to use Map
How to use Chain API
[Java] How to use Map
How to use Priority Queuing
[Rails] How to use enum
How to use java Optional
How to use JUnit (beginner)
How to use Ruby return
[Rails] How to use enum
How to use @Builder (Lombok)
[Swift] How to use UserDefaults
How to use java class
How to use Swift UIScrollView
How to use Big Decimal
[Java] How to use Optional ②
[Java] How to use removeAll ()
How to set Docker nginx
How to use String [] args
[Java] How to use string.format
How to use rails join
How to use Java Map
How to set Java constants
Ruby: How to use cookies
How to use dependent :: destroy
How to use Eclipse Debug_Shell
How to use Apache POI
[Swift] I thought about compare
[Rails] How to use validation