Phần 2: “Khám phá” OOP trong Objective-C


Phần 1: “Lần đầu” với lập trình Objective-C
Phần 2: “Khám phá” OOP trong Objective-C
Phần 3: “Quan hệ” giữa các đối tượng trong Objective-C
Phần 4: “Tự sướng A2Z” với một chương trình Objective-C hoàn thiện
Phần 5: “Vật lộn” với quản lý bộ nhớ trong Objective-C
Phần 6: Chuẩn bị “lên đỉnh” với iPhone application

Kết thúc bài 1 các bạn đã nắm được một số nguyên tắc cơ bản của ngôn ngữ C cũng như Objective-C. Trong bài học thứ 2 này, chúng ta sẽ tập trung tìm hiểu: tại sao Objective-C lại trở thành một ngôn ngữ tuyệt vời trong việc phát triển phần mềm, đặc biệt là những phần mềm trên MacOS và iOS? Cụ thể, chúng ta sẽ thảo luận về các nguyên tắc cơ bản của lập trình hướng đối tượng và làm thế nào để tạo ra một lớp hay gửi các thông điệp cho các đối tượng khác nhau trong chương trình Objective-C.

Lập trình hướng đối tượng (OOP)

Tại sao chúng ta lại dùng Objective-C? Tại sao không chỉ sử dụng mỗi ngôn ngữ C? Đơn giản vì Objective-C là ngôn ngữ OOPOOP giờ là một phương pháp lập trình hiện đại với rất nhiều ưu thế để các LTV có thể sử dụng trong quá trình phát triển phần mềm. OOP là một mô hình lập trình mà cho phép các nhà phát triển phần mềm có thể suy nghĩ và lựa chọn cách thức thiết kế một phần mềm theo hướng các đối tượng (objects) và các thuộc tính (attributes) của nó mà không theo cách truyển thống là phân tích dựa trên các biến dữ liệu và chức năng. Nói một cách đơn giản là OOP tiếp cận một bài toán cụ thể dưới cách nhìn là các đối tượng chứ không nhìn cụ thể ngay các chức năng và dữ liệu. Cụ thể, OOP cố gắng để có được trừu tượng hóa dữ liệu (data abstraction), đóng gói (packaging), mô đun (modularity), đa hình (polymophism) và thừa kế (inheritance). Nếu mà đi sâu vào chủ đề của OOP thì chúng tôi cam đoan rằng nói cả ngày sẽ không hết. Cho nên chúng tôi xin giải thích đơn giản qua các ví dụ.

Hãy tưởng tượng rằng bạn muốn mua một chiếc xe hơi (car) và bạn có thể nghĩ xe của bạn như một đối tượng. Có rất nhiều xe khác trên thế giới và thậm chí bạn có thể sở hữu nhiều hơn một. Hãy thử tưởng tượng xem chiếc xe hơi bạn định mua có những tính chất khác nhau như: mô hình, động cơ loại màu sắc, và nhiều hơn nữa. Về Lập trình hướng đối tượng, chúng ta gọi quá trình diễn tả này là trừu tượng hóa khái niệm một chiếc xe  thành một “lớp” (class) và khi bạn sở hữu thực sự một chiếc xe thì khi đó bạn sẽ đã có được một chiếc xe có với hình ảnh, chất liệu và máy móc thật và nó gọi là một sự thể hiện của (instantiated object) của lớp xe hơi (car class). Khi một chiếc xe khác mới được sản xuất và bán cho một khách hàng khác thì lại một sự thể hiện (instantiated object ) khác của lớp xe hơi (car class) được tạo ra.

Vì vậy, tại sao chúng ta lại nghĩ theo hướng các đối tượng? Một trong những lý do tốt nhất là bởi vì đây là cách bộ não của bạn tự nhiên quan niệm cuộc sống trong thế giới thực. Trong đời sống thực, hầu hết các hệ thống nghiệp vụ hàng ngày ta thường quan niệm đó là sự tương tác của các đối tượng khác nhau. Vì vậy, chúng ta sẽ có rất nhiều lợi ích khi trừu tượng hóa sự phát triển phần mềm theo cách tương tự như những suy nghĩ trong đời sống thực.

Phân biệt lớp (class) và đối tượng (object)

Trong OOP, class là một tập hợp các dữ liệu đóng gói và phương pháp tùy chỉnh. Một lớp có thể tổ chức nhiều loại dữ liệu khác nhau, và các phương thức lớp thường (nhưng không phải luôn luôn) thực hiện hành động liên quan đến dữ liệu đó. Trong Objective-C, một lớp thường bao gồm hai tập tin: một file interface và một file thực thi. Các tập tin giao diện sử dụng phần mở rộng (.h). Theo quy ước interface là nơi mà LTV dùng để khai báo các phương thức và dữ liệu mô tả cho lớp đó. Các tập tin thực thi sử dụng phần mở rộng (.m) theo quy ước là nơi các mã thực thi thực sự sẽ được xây dựng và tất nhiên những mã đó sẽ ghi đè nên những khai báo về phương thức và thuộc tính dữ liệu trong file interface.

Vậy, lớp khác đối tượng ở chỗ nào? Và đối tượng là gì? Thực tế, một đối tượng là một thể hiện của một lớp. Lớp giống như một khuôn mẫu chứa những khai báo về định dạng và nguyên tắc hoạt động và đối tượng được coi như một sản phẩm thực sự được tạo ra từ khuôn mẫu đó. Hãy nhớ lại ví dụ của chúng tôi ở trên với chiếc xe, cụ thể Car là một lớp với những tính chất chung và phương thức hoạt động chung của một cái xe. Tuy nhiên Car vẫn chỉ là ở trên giấy và chưa thành sản phẩm thực tế. Từ lớp Car nhà máy sẽ sản xuất và bán một chiếc xe cho Dans và chúng tôi gọi đó là đối tượng dansCar vì nó là đối tượng thực sự có thể hoạt động ngay được.

Các class (dùng để khởi tạo các đối tượng – object) được tạo bởi các phương thức (methods) và các thuộc tính (abttributes). Nếu bạn đã làm quen với một ngôn ngữ không phải hướng đối tượng, bạn có thể là phải làm quen với một số khái niệm như: phương thức (methods) thì gần giống như với khái niệm chức năng (functions) và các thuộc tính (attributes) thì gần giống như với khái niệm các biến (variables). Tuy nhiên ở trong lập trình C thì dữ liệu và hàm ta có thể khai báo riêng biệt còn trong Objective-C thì dữ liệu và các phương thức hành động phải được đóng gói trong các class.

Giới thiệu một số class được xây dựng sẵn trong Objective-C

Chắc các bạn sẽ rất ngạc nhiên khi bắt tay vào viết mã lệnh trong Objective-C vì các đối tượng sẵn có trong foundation framework sao lại có chữ “NS” ở đằng trước. Tại sao lại thế? Phải chăng Apple cố ý tạo ra như thế để bắt các LTV phải viết thêm những câu lệnh dài dòng.

Thực ra không phải là như vậy. Tất cả là do lịch sử. Các class trong foundation framework  của Apple được thêm vào phía trước chữ “NS”, đó là chữ viết tắt của NeXTSTEP. Khi Steve Jobs rời Apple, ông thành lập NeXT, tạo ra các máy tính trạm làm việc trên hệ điều hành của nó. Ngôn ngữ OOP được sử dụng để xây dựng hệ điều hành trên máy tính của NeXT có tên là NeXTSTEP (viết tắt là “NS”) và đó cũng là tên của hệ điều hành. Tuy nhiên do NeXTSTEP không được thương mại hóa nhiều nên ít người biết đến. Khi Apple mua lại NeXTSTEP, họ đã quyết định đổi tên ngôn ngữ đó thành Objective-C và xây dựng hệ điều hành MacOS X dựa trên NeXTSTEP.

Dưới đây là một số class mà chúng ta hay sử dụng:

NSLog: in ra màn hình console một xâu ký tự
NSString: lớp chuỗi văn bản không thay đổi
NSMutableString: lớp chuỗi văn bản có thể thay đổi
NSArray: lớp mảng của các đối tượng không thay đổi
NSMutableArray: lớp mảng của các đối tượng có thể thay đổi
NSNumber: lớp chứa giá trị số…

Phương thức (Methods)

Như vậy, chúng ta đã có một “sự thể hiện” (instance) của một chiếc xe, bây giờ chúng ta phải làm gì với nó? Vâng, chúng tôi đổ xăng và khởi động xe và còn rất nhiều việc khác nữa như kiểm tra đèn, phanh… Đổ xăng và khởi động xe chỉ áp dụng cho những chiếc xe mà chúng tôi sở hữu và sử dụng. Điều đó có nghĩa rằng, 2 hành động trên chỉ tác động đến cái xe mà chúng tôi tương tác trực tiếp còn những cái xe khác trên thế giới sẽ không bị tác động và những cái xe đó lại bị tác động bởi người sở hữu và sử dụng chúng. Vì vậy, hành động đổ xăng và khởi động xe được coi như là các các phương thức được thể hiện (instance method) của các đối tượng thể hiện cụ thể (instantiated object). Và tất nhiên còn nhiều phương thức hoạt động của chiếc xe khác nữa mà ta có thể gọi ra, ví dụ: tiến, lùi, phanh, bật đèn, còi…, tuy nhiên chúng ta không thể gọi những hành động mà không được định nghĩa sẵn có trong xe hơi ví dụ như bay hoặc lặn. Tóm lại nếu ta là người sở hữu chiếc xe (chiếc xe đã được khởi tạo) thì chúng ta hoàn toàn có thể kiểm tra mọi thuộc tính cũng như các phương thức hoạt động của chiếc xe.

Trong Objective-C, chúng ta gọi các phương thức của đối tượng bằng cách gởi thông điệp (passing messages). Ví dụ, khi chúng ta muốn biết có bao nhiêu khí trong bản thể hiện xe của chúng ta, chúng ta sẽ gửi một thông điệp tới bản thể hiện của chiếc xe (instance of car) và thông điệp đó chính là lời gọi một phương thức hoạt động (method) của bản thể hiện của chiếc xe mà ta muốn nó thực thi. Ví dụ như sau:

[recipient message];

Các dấu ngoặc vuông cho thấy chúng tôi đang gửi thông điệp. Tham số đầu tiên là đối tượng sẽ nhận được thông điệp này và tham số thứ hai là thông điệp đó thực sự là gì? Cụ thể là sẽ gọi tới phương thức nào của đối tượng được thể hiện “recipient”. Cuối cùng, chúng tôi kết thúc bằng một dấu chấm phẩy như là phổ biến với hầu hết các ngôn ngữ lập trình. Để rõ hơn ví dụ trên ta sẽ xem xét tiếp khi chiếc xe của Dans muốn thêm gas thì chúng ta sẽ làm gì?

[dansCar addGas];

Trong ví dụ trên này, ta giả sử chúng ta đã tạo ra một sự thể hiện của lớp xe hơi (car class) hay đơn giản hơn khi một khách hàng tên là Dans mua một chiếc xe và ta đặt tên cho nó là “dansCar”. Sau đó ta truyển tới đối tượng được thể hiện dansCar này một thông điệp tên là “addGas” hay ngắn gọi là yêu cầu đối tượng danCars thực thi phương thức hành động addGas và nó tương đương với gọi hàm addCars trong ngôn ngữ lập trình C hay Pascal.

Thuộc tính (Attributes)

Như các bạn đã biết, thực tế khi nhắc đến một đối tượng cụ thể thì ta thường hay nhớ đến những đặc điểm hay tính chất cụ thể của đối tượng của đối tượng đó. Giả sử khi nói đến một chiếc xe thì ta thường nhớ đến là chiếc xe của hãng nào, model nào hay như có mã số đăng ký là bao nhiêu? Chính bởi vậy, trong OOP, khi tạo ra các đối tượng để phản ánh trực tiếp các đối tượng trong cuộc sống hay trong một nghiệp vụ nào đó, chúng ta sẽ tạo ra một số thuộc tính để lưu trữ các giá trị mô tả đặc tính của một đối tượng. Tiếp tục với ví dụ đối tượng class Car ta sẽ tạo ra một số attributes như make, model hay vin. Các attributes đều được coi là các giá trị nội bộ có nghĩa là phạm vi truy suất nó chỉ ở trong đối tượng khởi tạo mà thôi. Thông thường để truy suất các attributes từ bên ngoài chúng ta thường tạo ra cặp phương thức get và set để thay đổi và lấy các giá trị của attribute đó.

Câu lệnh sau giúp bạn có thể truy suất vào thuộc tính make của đối tượng thể hiện dansCar:

[dansCar getMake];
[dansCar make];

Như ta đã thấy ở trên, toàn bộ methods và attributes được đóng gói trong class (encapsulation) và class sẽ quản lý methods và attributes. LTV sau này muốn sử dụng method hay attribute nào sẽ chỉ việc tham chiếu qua đối tượng thể hiện mà không phải quan tâm đến thực sự nội dung của chúng được tạo như thế nào. Tuy nhiên khi có nhiều các lớp có tính chất giống nhau và hành động giống nhau thì ta sẽ làm như thế nào?. Điều này chúng ta sẽ tìm hiểu kỹ ở phần tiếp theo.

Interface và lớp khởi tạo (class)

Theo mặc định, trong Xcode, khi bạn tạo một lớp Objective-C mới thì trong project của bạn sẽ có hai tập tin. Một là tập tin đại diện cho lớp thực thi với định dạng là (*.m) và tập tin của interface với đinh dạng là (*.h). Về nguyên tắc chung là lớp thực thi sẽ thừa kế các tính chất của interface mà nó import. Chính vì vậy ở trên interface ta sẽ định nghĩa những thuộc tính và những hành động chung đại diện cho một nhóm đối tượng. Sau đó ta sẽ tạo ra một đối tượng thừ kế tới interface và khởi tạo nội dung chi tiết của các phương thức hành động đã được định nghĩa trong interface.

Giả sử ta sẽ tạo một project mới như sau: File> New Project> Mac OS X> Application> Command Line> Type: Foundation . Sau đó ta sẽ tạo một lớp Objective-C mới bằng cách vào File> New File> MacOS X> Cocoa> Objective-C class. Tiếp đó ta sẽ đặt tên lớp là Car với nội dung như sau:

Car Interface (Car.h):

interface

Như trên, chúng ta đã khai báo rằng đây là interface Car, và trong dòng khai báo ta đặt thêm “: NSObject” có nghĩa là lớp Car do ta tạo sẽ thừa kế từ lớp NSObject (super class của mọi lớp trong Objective-C). Chúng tôi sẽ nói nhiều hơn về thừa kế trong những bài học tới. Trong interface Car ta khai báo các biến có tên là “make”, “model” và “vin” thực chất đây là là cá attribute của interface Car. Dòng tiếp theo có nghĩa trong interface Car có chứa một phương thức addGas. Tiền tố (void) ở trước có nghĩa là phương thức này không trả về bất cứ giá trị nào khi nó kết thúc thực hiện. Như thế chúng ta đã khai báo interface Car với thuộc tính và phương thức như trên, tất nhiên trong thực tế thì ta có thể thêm nhiều thuộc tính hay phương thức khác như: color, model, run, stop, break… Và như thế khi có bất cứ lớp thực thi nào thừa kế tới interface Car thì chúng đều có thuộc tính và method như trên.

Car class (Car.m):

class

Như vậy là lớp thực thi Car.m là lớp khởi tạo dựa trên interface Car nên ta sẽ phải khởi tạo nội dung của tất cả các phương thức đã được khai báo ở interface Car. Cụ thể, để khởi tạo nội dụng cho phương thức addGas cho lớp Car.m, ta phải viết câu lệnh #import “Car.h” ở đầu file và xây dựng phần thân của phương thức addGas nằm trong dấu ngoặc {}. Khi khai báo phương thức addGas ta chú ý tiền tố -(void) có ý nghĩa là báo với compiler rằng method này sau khi chạy xong sẽ không trả về giá trị gì. Ở ví dụ trên, nội dung của phương thức đơn giản chỉ là một hành động in ra màn hình console một dòng chữ “Gas is added!”.

Tuy nhiên để có thể truy suất các thuộc tính hay gọi một phương thức của 1 lớp thì điều trước tiên ta phải tạo một đối tượng thể hiện (instance of object) của lớp đó. Đoạn mã lệnh sau đây sẽ minh họa điều trên (chúng ta sẽ sử dụng lại việc định nghĩa lớp Car ở trên):

Car *myCar = [[Car alloc] init];
[myCar addGas];

Phần tiếp theo

Phần tiếp theo ta sẽ đi sâu hơn về OOP trong Objective-C và cụ thể là “quan hệ” giữa các đối tượng trong Objective-C. Để tiếp tục xin mới bạn đọc bài học thứ 3…

3 Responses to Phần 2: “Khám phá” OOP trong Objective-C

  1. Pingback: Tự học Objective-C – Bài 1 « A to Z for Mac Developer

  2. Pingback: Tự học Objective-C: Bài 3 « A to Z for Mac Developer

  3. Pingback: Tự học Objective-C: Bài 4 « A to Z for Mac Developer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: