+2

Object-Oriented Programming (OOP) trong Swift

Lập trình hướng đối tượng (OOP) giúp bạn cấu trúc code Swift của mình với các lớp. Các lớp này có các thuộc tính và chức năng và các lớp có thể kế thừa các thuộc tính của nhau.

Hướng dẫn phát triển ứng dụng này là phần giới thiệu về OOP với Swift. Chúng ta sẽ đi sâu vào các lớp, đối tượng, thuộc tính, hàm và một khái niệm gọi là kế thừa. Bạn sẽ tìm hiểu mục đích của OOP là gì, với các ví dụ trực tiếp và tại sao nó lại quan trọng trong quá trình phát triển iOS thực tế.

Khi bạn không sử dụng cấu trúc như OOP trong mã của mình, nó sẽ giống như một thác nước: code sẽ chạy từ trên xuống dưới. Mặt khác, OOP giống một ngôi nhà hơn: bạn xác định nền tảng mã của mình và xây dựng trên đó, từng phòng, từng khối.

OOP trong Swift: Classes and Instances

Hãy bắt đầu với phần quan trọng nhất của Lập trình OOP: Lớp. Chúng là 1 phần xây dựng lên mã của bạn.

Đây là một ví dụ về một lớp trong Swift:

class Car
{

}

Bên trong dấu ngoặc nhọn, bạn có thể xác định các thuộc tính và hàm thuộc lớp Car này. Bạn có thể xem lớp như một cấu trúc dành cho mã, giống như một hộp công cụ chứa tuốc nơ vít, cờ lê, búa và máy khoan.

Trong Swift, bạn có thể tạo các instances của một lớp. Như thế này:

class Car
{

}

let ferrari:Car = Car()

Hằng số ferrari hiện chứa một thể hiện của lớp Car. Bạn đã tạo một phiên bản bằng hàm khởi tạo Car() và với toán tử gán = bạn đã gán phiên bản đó cho hằng số ferrari.

Bây giờ bạn có thể chuyển phiên bản Car đó trong mã của mình, giống như bất kỳ giá trị nào khác. Lớp Car là loại ferrari, giống như một số có thể thuộc loại Int.

Bạn có thể tạo bao nhiêu phiên bản của một lớp tùy thích. Đó là sức mạnh của Lập trình hướng đối tượng! Bạn tạo lớp Car một lần, sau đó sử dụng nó để tạo toàn bộ gara ô tô. Đây được gọi là tái sử dụng mã và là một yếu tố quan trọng trong quá trình phát triển iOS thực tế.

Các instances của một lớp thường được gọi đơn giản là các đối tượng, hay còn gọi là Object.

Việc hình dung các khái niệm trừu tượng như lớp và thể hiện có thể là một thách thức. Hãy xem một trường hợp tương tự:

  • Một class giống như bản thiết kế của một tòa nhà. Một kiến trúc sư vẽ bản thiết kế, sau đó chuyển nó cho quản đốc hoặc công nhân xây dựng.
  • Một instance giống như một tòa nhà. Công nhân xây dựng sử dụng bản thiết kế để tạo ra tòa nhà.

Một tòa nhà không phải là bản sao chính xác của bản thiết kế của nó. Nó chỉ là bản trình bày trực tiếp của bản thiết kế, với cửa sổ, cửa ra vào, mái nhà, v.v.

Khi code ứng dụng của mình, bạn đang xây dựng cả một thành phố bằng cách sử dụng rất nhiều bản thiết kế. Một số bản thiết kế này được cung cấp bởi “chính phủ” (ví dụ SDK Cocoa Touch của Apple), chẳng hạn như bản thiết kế cho trạm cứu hỏa và ga xe lửa. Các bản thiết kế khác do bạn, kiến trúc sư thiết kế.

Thành phố của bạn không chỉ là một đống bản thiết kế. Bạn tạo các phiên bản của những bản thiết kế này: các tòa nhà thực tế! Và bạn tạo bản thiết kế cho một khu chung cư một lần, sau đó sử dụng nó để xây dựng các căn hộ trên toàn thành phố.

Có ý nghĩa? Hãy chuyển sang phần tiếp theo của Lập trình hướng đối tượng: thuộc tính và hàm.

Swift có một khối xây dựng quan trọng khác: struct. Struct (viết tắt của “structure”) tương tự như class, với một vài ngoại lệ: struct không có kế thừa và chúng là loại giá trị. Swift ưa chuộng struct hơn class đối với các kiểu dữ liệu đơn giản. Bạn có thể tìm hiểu thêm tại đây: Giải thích về Struct so với Class trong Swift.

Thuộc tính và phương thức trong OOP

Một lớp có thể định nghĩa các thuộc tính và phương thức.

  • Một thuộc tính giống như một biến (variable) thuộc về một lớp.
  • Một phương thức giống như một hàm (function) thuộc về một lớp.

Thuộc tính lưu trữ thông tin thuộc về một thể hiện của một lớp và các phương thức có thể thực thi các tác vụ thuộc về một thể hiện của một lớp. Điều này rất giống với các biến và hàm, ngoại trừ việc các biến và hàm hiện là một phần của cấu trúc lớn hơn – class.

Bạn có thấy cấu trúc class thông tin và nhiệm vụ trong mã của bạn như thế nào không? Class này “bao bọc” các biến và hàm thuộc về nhau.

Phương thức là một hàm thuộc về một lớp, nhưng để đơn giản, chúng ta sẽ gọi chúng là hàm.

Hãy xem một ví dụ:

class Car
{
    var wheels:Int = 0
    var maxSpeed:Int = 0
}

Đây là những gì xảy ra:

  • Khai báo một lớp tên là Car. Nội dung lớp nằm giữa dấu ngoặc nhọn { }.
  • Khai báo hai thuộc tính: một thuộc tính wheels loại Int và một thuộc tính khác là maxSpeed loại Int, cả hai đều có giá trị mặc định là 0.

Điều quan trọng cần lưu ý là trong đoạn mã trên, các thuộc tính wheelsmaxSpeed được khai báo ở cấp độ class. Chúng được thêm vào trong dấu ngoặc nhọn của class Car.

Bất cứ điều gì bạn có thể làm với một biến đều có thể làm với một thuộc tính. Một thuộc tính có một kiểu và bạn khai báo nó bằng var, giống như một biến. Bạn có thể khai báo các hằng số bằng let.

Tất nhiên, sự khác biệt lớn nhất là những thuộc tính này thuộc về một lớp. Mọi phiên bản của lớp Car bây giờ sẽ có các thuộc tính wheelsmaxSpeed.

Chúng ta có thể tạo ra một chiếc Ferrari như thế này:

let ferrari = Car()
ferrari.wheels = 4
ferrari.maxSpeed = 300

Và chúng ta có thể tạo ra một chiếc Xe chạy trên mọi địa hình (ATV) như thế này:

let atv = Car()
atv.wheels = 8
atv.maxSpeed = 80

Điều đó khá tuyệt phải không? Bạn sử dụng cú pháp dấu chấm để truy cập các thuộc tính trên một đối tượng, chẳng hạn như ferrari.wheels. Hầu hết các thuộc tính có thể được lấy ra (get) và được ghi vào (set), trừ khi có quy định khác.

Bạn muốn tìm hiểu thêm về các biến? Hãy xem hướng dẫn này: Giải thích về Biến và Hằng trong Swift.

OK, bây giờ hãy tiếp tục với các hàm. Mọi thứ chúng ta đã làm với các thuộc tính cho đến nay cũng áp dụng cho các hàm. Khi một lớp khai báo một hàm, hàm đó có sẵn trên mọi phiên bản của lớp đó.

Như thế này:

class Car
{
    func drive()
    {
        print(“VROOOOOOM!!!”)
    }
}

Trong ví dụ trên, chúng ta khai báo hàm drive() trên lớp Car. Nó in một dòng văn bản đơn giản ra Console khi hàm được thực thi.

Bây giờ bạn có thể gọi hàm đó drive() trên lớp Car với cùng cú pháp dấu chấm. Như thế này:

let ferrari = Car()
ferrari.drive()
// Output: VROOOOOOM!!!

Lớp con và kế thừa

Bây giờ bạn đã biết rằng Lập trình hướng đối tượng được sử dụng để cấu trúc mã của bạn trong các lớp. Một lớp có thể có các thuộc tính để lưu trữ thông tin và các hàm để thực thi các tác vụ.

Lập trình hướng đối tượng cũng có một số khái niệm nâng cao, chẳng hạn như tính kế thừa, tính đa hình, giao thức, ủy quyền và thành phần. Bây giờ chúng ta sẽ tập trung vào tính kế thừa.

Giả sử chúng ta đã định nghĩa một lớp đơn giản tên là Vehicle. Sau đó chúng ta có thể tạo các lớp con của Xe, chẳng hạn như:

  • Bicycle – phương tiện không có động cơ, bàn đạp và 2 bánh
  • RaceCar – chiếc xe có động cơ mạnh mẽ và 4 bánh
  • Bus – một phương tiện lớn có 8 bánh và có sức chứa 50 người

Hãy xem mỗi lớp con này là một loại phương tiện như thế nào và cũng xác định một số thuộc tính của riêng chúng như thế nào? Đó chính là nội dung của phân lớp con và kế thừa.

Đây là ý chính của nó:

  • Một lớp con kế thừa các thuộc tính và chức năng từ lớp cha của nó.
  • Một lớp con có thể ghi đè các thuộc tính và hàm từ siêu lớp của nó, thay thế chúng bằng cách triển khai của chính nó.
  • Một lớp con có thể mở rộng siêu lớp của nó với các thuộc tính và hàm mới.
  • Một lớp con và lớp cha có thể được tạo kiểu trong hệ thống phân cấp của chính chúng.

Nguyên tắc kế thừa cho phép bạn tạo hệ thống phân cấp trong mã của mình và giúp bạn sử dụng lại các phần trong mã của mình. Sẽ không có ý nghĩa gì khi sao chép các thuộc tính mà các loại phương tiện khác nhau chia sẻ khi bạn có thể sử dụng lại chúng.

Chúng ta hãy xem một số mã. Đây là class Vehicle cơ bản:

class Vehicle
{
    var wheels:Int = 0

    func drive()
    {
        print(“Driving this vehicle!”)
    }
}

Sau đó, hãy tạo một lớp con:

class Bus: Vehicle
{
    var seats:Int = 0
    var gears:Int = 0

    func openDoors()
    {
        print(“Opening bus doors…”)
    }

    override func drive()
    {
        print(“Out of the waaaayyy! This bus is unstoppable!”)
    }
}

Đây là những gì xảy ra trong đoạn mã trên:

  • Bạn khai báo một lớp có tên Bus và nó phân lớp Vehicle, với cú pháp class Bus: Vehicle { ... }.
  • Bạn khai báo hai thuộc tính trên lớp Bus, seatsgears, cả hai đều thuộc loại Int.
  • Bạn khai báo một hàm openDoors() trên lớp Bus để mở cửa xe buýt.
  • Bạn khai báo một hàm khác drive() trên lớp Bus. Chức năng này bị ghi đè (override). Việc triển khai lớp cha Vehicle được thay thế bằng một chức năng mới.

Để xem cách kế thừa hoạt động, hãy tạo một phiên bản của Bus.

let greyhound = Bus()
greyhound.wheels = 8
greyhound.seats = 200
greyhound.gears = 7

Hãy xem điều gì xảy ra trong đoạn mã trên?

  • Xe buýt Greyhound có 8 bánh. Thuộc tính wheels này được kế thừa từ Vehicle, vì tất cả các phương tiện đều có bánh xe. Xem cách các thuộc tính wheels không được khai báo trên lớp Bus? Thay vào đó, nó được khai báo trên Vehicle và được kế thừa lại vì Bus là lớp con của Vehicle.
  • Các thuộc tính seatsgears được khai báo trên lớp Bus. Điều quan trọng cần lưu ý là lớp Vehicle không có các thuộc tính này! Chúng dành riêng cho lớp Bus.

Khi bạn gọi hàm drive() trên greyhound, việc triển khai bị ghi đè sẽ được gọi. Như thế này:

greyhound.drive()
// Output: Out of the waaaayyy! This bus is unstoppable!

Đầu ra không phải là Driving this vehicle!, vì hàm drive() bị ghi đè bởi việc triển khai lớp con Bus.

Điều quan trọng cần lưu ý ở đây là bicycle, bus và racecar đều có bánh xe, mặc dù số lượng bánh xe khác nhau. Việc phân lớp không liên quan nhiều đến giá trị của các thuộc tính, chẳng hạn như số lượng bánh xe khác nhau, mà là về bản thân các thuộc tính và chức năng.

Bicycle có bàn đạp và khung, trong khi bus có cửa có thể mở được. Vấn đề không phải là về số lượng bánh xe mà thay vào đó là về các thuộc tính mà các lớp trong hệ thống phân cấp chia sẻ với nhau.

Hãy thử lập trình OOP cho chính bạn!

Được rồi, hãy áp dụng những gì bạn đã học vào thực tế! Bạn có thể sử dụng Swift Sandbox bên dưới để dùng thử mã Swift. Thử nghiệm với các lớp, thuộc tính, hàm và phân lớp.

class Vehicle
{
    var wheels:Int = 0
    var maxSpeed:Int = 0

    func drive()
    {
        print(“This vehicle is driving!”)
    }
}

class RaceCar: Vehicle
{
    var hasSpoiler = true

    override func drive()
    {
        print(“VROOOOM!!!”)
    }
}

class Bus: Vehicle
{
    var seats:Int = 0
    var gear:Int = 1

    func shiftGears()
    {
        gear += 1
    }
}

let ferrari = RaceCar()
ferrari.wheels = 4
ferrari.hasSpoiler = false
ferrari.drive()

let bicycle = Vehicle()
bicycle.wheels = 2
bicycle.drive()

let greyhound = Bus()
greyhound.wheels = 8
greyhound.shiftGears()
greyhound.shiftGears()
print(“Gear: \(greyhound.gear)”)

Lời nói thêm

Lập trình hướng đối tượng giúp bạn cấu trúc mã của mình. Nó cho phép bạn sử dụng lại và mở rộng mã mà không cần lặp lại chính mình một cách không cần thiết. Cuối cùng, điều đó giúp bạn tránh được lỗi và viết mã hiệu quả hơn.

Hãy suy nghĩ về ý nghĩa của từ “hướng đối tượng”. Lập trình hướng đối tượng được định hướng khá theo nghĩa đen xung quanh các đối tượng. Thay vì để mã của bạn chảy như thác nước, bạn sắp xếp nó thành các lớp và đối tượng cùng với các thuộc tính và chức năng của chúng. Rõ ràng hơn nhiều!

Lập trình hướng đối tượng là một chủ đề cơ bản trong phát triển iOS. OOP thực sự có mặt ở khắp mọi nơi, điều này khiến việc thành thạo đúng cách càng trở nên quan trọng hơn.

Tham khảo: https://www.appypie.com/object-oriented-programming-oop-swift-introduction


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí