Strong type hay là bóng ma
swift
57
White

VietHQ viết ngày 08/06/2018

Strong type qua bài toán qui đổi tiền tệ

Strong type là gì? Tại sao lại cần?

Strong type là cách ta định nghĩa ra một kiểu dữ liệu mới, từ những dữ liệu có sẵn nhưng mang tính định danh cao hơn. Ở các ngôn ngữ khác, ví dụ như haskell, người ta hay gọi đấy là Phantom types (kiểu bóng ma)

Một ví dụ điển hình và đơn giản nhất:

Ta có một struct Bank để gửi tiền

struct Bank {
    let money: Double
    init(_ money: Double) {
        self.money = money
    }
}

Ngân hàng chỉ cho gửi VND, nhưng chưa có cơ chế check. Ta gửi VND bình thường theo cách sau

Bank(100) // VND

Nhưng một hôm, ta bận và nhờ bạn gửi hộ, bạn không biết, cầm $ đi gửi

Bank(100) // vẫn được chấp nhận

Vậy nên ta cần một kiểu dữ liệu mới để người dùng chỉ nhập được duy nhất VND. Đây là lúc Strong type phát huy tác dụng.

Cách thức tạo Strong type

VND, USD, AUD là đơn vị tiền tệ của mỗi quốc gia. Ta coi mỗi loại tiền là 1 Unit, Ta cần một cái gì đó để xác định 3 Unit trên cùng chung một nhóm.
Thế nên ta định nghĩa 2 protocol: UnitFamily tạo nhóm, MyUnit để định nghĩa các đơn vị trong nhóm.

// Define UnitFamily to group all of units have the same family
protocol UnitFamily {
    associatedtype BaseUnit
}

// Define MyUnit, own unit will be based on MyUnit protocol
protocol MyUnit {
    associatedtype Family: UnitFamily
    static var symbol: String { get }
    static var converted: UnitConverter { get }
}

Ở đây ta để ý rằng UnitFamily có chứa 1 type BaseUnit nhưng không sử dụng đến, tại sao lại cần đến nó? Như ta biết trong thực tế, bất cứ công thức qui đổi nào đều cần dùng một đơn vị chuẩn. Giống như F = ma, m luôn là kg, mọi khối lượng đều phải qui chuẩn về kg để đảm bảo tính đúng đắn của công thức.

Thế nên BaseUnit có thể hiểu là một Unit bất kì trong group được lấy ra làm chuẩn.

Tiếp theo ta cần tạo ra Strong type để không nhầm lẫn Unit này với Unit khác.

// Unit wrapper, this one help us convert data type easier
struct MyMeasurement<UnitType: MyUnit> {
    let value: Double
    init(_ value: Double) {
        self.value = value
    }
}

UnitType không được sờ đến trong MyMeasurement, tác dụng của nó là định danh, qua đó hình thành Strong type, cũng có thể gọi là Phantom Types. Ta đã có Strong type theo cách rất đơn giản phải không? Tiếp theo là vấn đề convert.

Để convert được giữa các đơn vị theo cách chung nhất thì ta tạo extension cho MyMeasurement và có sử dụng generic.

extension MyMeasurement {
    func converted<TargetUnit>(to: TargetUnit.Type) -> MyMeasurement<TargetUnit> where
        TargetUnit: MyUnit, UnitType.Family == TargetUnit.Family {
            let valueInBase = UnitType.converted.baseUnitValue(fromValue: self.value)
            let convertValue = TargetUnit.converted.value(fromBaseUnitValue: valueInBase)
            return MyMeasurement<TargetUnit>(convertValue)
    }
}

Bây giờ ta bắt đầu định nghĩa các loại tiền với kiểu dữ liệu Enum. Các Unit VND, USD, AUD đều chung 1 nhóm (Family) MyCurrency . Ta lấy VND làm chuẩn, cho nên khi convert nó sẽ nhận đơn vị là 1

static var converted: UnitConverter = UnitConverterLinear(coefficient: 1)

Ta sử dụng UnitConverter có sẵn trong SDK của Táo khuyết (có từ ios 10.0)

// Group
enum MyCurrency: UnitFamily {
    typealias BaseUnit = VND
}

// Unit
enum VND: MyUnit {
    typealias Family = MyCurrency
    static var symbol: String {
        return "VND"
    }

    static var converted: UnitConverter = UnitConverterLinear(coefficient: 1)
}

enum USD: MyUnit {
    typealias Family = MyCurrency
    static var symbol: String {
        return "$"
    }

    static var converted: UnitConverter = UnitConverterLinear(coefficient: 1*22.5)
}

// ...

OK, mọi thứ đã được định nghĩa một cách ổn thỏa. Bây giờ sếp trả lương cho ta theo 2 đợt, đợt 1 lấy vnd, đợt 2 lấy dollar Úc, ta muốn qui đổi ra VND để tiêu cho dễ.

let vnd = MyMeasurement<VND>(20)
let aud = MyMeasurement<AUD>(10)

let salaryValue = vnd.value + aud.converted(to: VND.self).value
let salary = MyMeasurement<VND>(salaryValue)

Ta phải qua ngân hàng để chuyển đổi đồng dollar Úc qua VND nhưng điều đó quá bất tiện, ta yêu cầu ngân hàng tạo phương thức qui đổi tiền tệ cho mình. Tất nhiên là ngân hàng đáp ứng

func + <Unit1,Unit2>(lhs: MyMeasurement<Unit1>, rhs: MyMeasurement<Unit2>) -> MyMeasurement<VND>
    where
    Unit1: MyUnit
    , Unit2: MyUnit
    , Unit1.Family == Unit2.Family
    , Unit1.Family == VND.Family {
        let lhsToVND = Unit1.converted.baseUnitValue(fromValue: lhs.value)
        let rhsToVND = Unit2.converted.baseUnitValue(fromValue: rhs.value)
        return MyMeasurement<VND>(lhsToVND + rhsToVND)
}

Giờ ta chỉ cần thực hiện lệnh qua phần mềm để tự động chuyển về VND, nhẹ hơn 1 dòng :v

let vnd = MyMeasurement<VND>(20)
let aud = MyMeasurement<AUD>(10)
let newSalary = vnd + aud

OK giờ ta có lương, ta muốn tiết kiệm nên làm sổ tiết kiệm VND. Ta bận nên nhờ bạn đến gửi hộ (như lần trước), bạn mang theo $ nhưng lần này ngân hàng nâng cấp hệ thống kiểm tra đầu vào nên bạn không gửi tùy tiện được. Bank cảnh báo lỗi luôn

alt text

struct Bank {
    let money: MyMeasurement<VND>
    init(_ money: MyMeasurement<VND>) {
        self.money = money
    }
}

Để ý rằng money: Double được thay thế bằng MyMeasurement là Strong type

Nhưng về sau ta không muốn ra tận ngân hàng để gửi tiền nữa, muốn gửi trực tuyến. Ngân hàng lại cải tiến đáp ứng khách hàng bằng cách sử dụng tool ExpressibleByIntegerLiteralExpressibleByFloatLiteral mà Apple cung cấp.

extension MyMeasurement: ExpressibleByIntegerLiteral {
    init(integerLiteral value: IntegerLiteralType) {
        self.value = Double(value)
    }
}

extension MyMeasurement: ExpressibleByFloatLiteral {
    init(floatLiteral value: FloatLiteralType) {
        self.value = Double(value)
    }
}

Bây giờ thì ta chỉ cần thao tác

Bank(5.2) // ngân hàng vẫn check format VND

Tham khảo:
Measurement ở blog https://oleb.net/
Phantom types của @VuNhatMinh

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

VietHQ

17 bài viết.
8 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
7 1
Giới thiệu Callback là kĩ thuật được ưa chuộng trong lập trình hiện nay. Ngặt nỗi, nếu sử dụng không khéo rất dễ xảy ra callback hell. Dưới con mắ...
VietHQ viết 2 năm trước
7 1
White
5 1
Thời gian đầu làm việc với objc mình khá băn khoăn trong việc sử dụng các thuộc tính trong property như strong, weak, copy, assign. Nhân lúc rảnh r...
VietHQ viết hơn 5 năm trước
5 1
White
5 0
1. Giới thiệu Từ hồi mới bắt đầu làm IOS, thằng nào cũng hỏi mình có biết sử dụng AFNetworking không? Khổ nỗi lúc đó, mình mới chuyển từ làm game ...
VietHQ viết hơn 3 năm trước
5 0
Bài viết liên quan
White
14 4
(Link) (Link) (Link) Ở 2 phần tut trước, mình đã hướng dẫn khá chi tiết cách viết một ứng dụng camera có tích hợp chức năng nhận diện khuôn mặ...
HoangPH viết hơn 5 năm trước
14 4
White
2 0
Có nhiều cách viết blog công nghệ hơn là làm bánh hay làm tình. Những ngày này Hà Nội mưa liên miên, được cái mát giời, mình lại tức cảnh sinh tìn...
VietHQ viết hơn 3 năm trước
2 0
White
0 0
Swift là ngôn ngữ do Apple tạo ra và nhận được sử ủng hộ lớn từ cộng đồng. Nhất là từ khi nó opensource. Bản thân ngôn ngữ vẫn đang ngày một hoàn t...
VietHQ viết 2 năm trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
17 bài viết.
8 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!