Chủ Nhật, 29 tháng 1, 2012

OOP - Bài 1: Tính lương trước thuế



Center of Excellence


Bài 1

Tính lương trước thuế


Càng khởi sự viết mã (code) sớm chừng nào,
chương trình (program) càng hoàn thành trễ chừng đó.
-- Roy Carlson, University of Wisconsin


Đối với người mới học lập trình hướng đối tượng (Object-Oriented Programming - OOP), bài đầu tiên có thể có nhiều chi tiết khiến bạn cảm thấy khó khăn. Đừng nản lòng. Điều quan trọng là bạn có thể đi theo các bước hướng dẫn trong bài. Sau đó nếu cần, bạn xóa hết đi rồi làm lại những bước này cho đến khi thuần thục. Lập trình là một nghề cần đức tính tận tụy, đổi lại, lập trình sẽ đem lại niềm vui đặc biệt vì có thể nhận biết ngay tác dụng của kết quả mà ta vừa tạo ra.


I. Bài toán

Hãy phát triển chương trình tính lương chính thức (gross pay), hay còn được gọi là lương chưa trừ thuế, của một nhân viên biết rằng thông tin để tính lương bao gồm số giờ làm việc (number of hours worked, được qui tròn về một số nguyên không âm) và lương một giờ (hourly pay rate, là một số thực dương từ 6.00 trở lên, đơn vị là USD, đô-la Mỹ). Sau đây là công thức tính gross pay:

gross-pay = number-of-hours-worked x hourly-pay-rate

Bạn không phải phát triển phần tương tác với người dùng (user interface – UI), nhưng để hiểu rõ hơn yêu cầu bài toán, bạn có thể tham khảo một UI mẫu dưới đây.

Hình 1

II. Qui trình giải quyết vấn đề

Để phát triển giải pháp, ta sẽ đi theo một qui trình giải quyết vấn đề gồm 4 bước cơ bản sau:

Bước 1. Phát biểu lại yêu cầu bài toán

Bước này nhằm một lần nữa khẳng định rằng ta đã đọc và hiểu rõ yêu cầu, rồi viết lại theo cách hiểu của mình. Viết càng cô đọng càng tốt. Nhớ ghi xuống dữ liệu hiện có (input) và kết quả cần phải đưa ra (output).


Bước 2. Thiết kế sơ bộ lớp (class) để giải quyết bài toán đã đặt ra

Dựa vào kết quả ở Bước 1 ta xác định tên phương thức (method), các tham biến (parameters) truyền vào method đó, tên thuộc tính (fields), và tên class. Điểm quan trọng ở bước này là việc đặt tên, đặc biệt là tên class. Đôi khi cần thảo luận nhóm để tìm được một tên thích hợp nhất. Tuy nhiên, bước này chỉ là thiết kế sơ bộ vì rất có thể ta sẽ sửa lại thiết kế trong quá trình viết mã (coding).


Bước 3. Xây dựng bộ dữ liệu kiểm thử (test cases)

Bước này giúp xác định rõ hơn và cụ thể hơn yêu cầu bài toán, đồng thời có thể giúp ta có được manh mối nào đó từ đó có thể tìm ra được giải pháp để giải quyết vấn đề. Một test case bao gồm dữ liệu cụ thể của input và output. Để có được dữ liệu input, cách tốt nhất là ta thảo luận và thu thập từ người đặt ra bài toán. Nếu họ không thể cung cấp, ta mới tiến hành tự xây dựng input. Output là kết quả có được sau các tính toán bằng tay với sự trợ giúp của một Calculator hay một phần mềm bảng tính (spreadsheet).

Sau khi đã tạo test cases, ta có cơ sở để xác định kiểu dữ liệu (data type) cho field(s), parameter(s), và kết quả trả về của method ở Bước 2.


Bước 4: Phát triển class kiểm thử (test class)

Dựa vào kết quả ở Bước 23, ta tiến hành phát triển test class, rồi dựa vào test class để suy luận, từ đó tìm ra giải pháp thích hợp.


III. Giải pháp

Bước 1. Phát biểu lại yêu cầu bài toán

Tính gross pay của một nhân viên
  • Input: number of hours worked và hourly pay rate
  • Output: gross pay

Bước 2. Thiết kế sơ bộ class để giải quyết bài toán đã đặt ra

Ta dùng UML class diagram (Hình 2) để thiết kế class.

Hình 2

Dựa vào Bước 1, đầu tiên ta xác định tên method và tên parameters (Hình 3). Kiểu dữ liệu (data type) của kết quả và của parameter sẽ được xác định ở Bước 3. Các dấu chấm hỏi (???) là những phần được bổ sung sau. Cách này giúp ta không phải tập trung giải quyết quá nhiều vấn đề tại cùng một thời điểm.

Hình 3

Sau đó ta thử xem có nên chuyển parameters thành fields của class hay không (Hình 4).

Hình 4

Tiếp theo, ta dựa vào ý nghĩa của fields và method để xác định một tên class thích hợp. Ta cần trả lời câu hỏi: Đối tượng nào có number of hours worked và hourly pay rate, từ đó tính được gross pay? Đó chính là một khoản trả lương (Hình 5).

Hình 5

Lưu ý rằng quá trình đặt tên không nhất thiết phải theo đúng trình tự như trên. Có thể từ phát biểu bài toán, ta xác định được ngay tên class và fields. Tuy nhiên, việc tập trung vào hành động (method) chính mà bài toán yêu cầu sẽ giúp ta phát triển được một thiết kế vừa đủ để giải quyết vấn đề đã đặt ra, vừa tránh được những thiết kế phức tạp không cần thiết.


Bước 3. Xây dựng test cases

Test case 1
  • Input
    • hoursWorked: 40
    • hourlyPayRate: $25.00
  • Output
    • grossPay: $1000.00

Test case 2
  • Input
    • hoursWorked: 10
    • hourlyPayRate: $15.50
  • Output:
    • grossPay: $155.00

Test cases có thể được trình bày dưới dạng bảng như sau:

computeGrossPay()
hoursWorked hourlyPayRate grossPay?
40 25.00 1000.00
10 15.50 155.00

Sau khi xây dựng test cases, ta tiến hành xác định data type của kết quả trả về từ computeGrossPay() method và data type của fields. Ở đây kết quả trả về là một số thực (double), hoursWorked là một số nguyên (int), và hourlyPayRate là một double (Hình 6).

Hình 6


Bước 4: Phát triển test class

Ta sẽ dùng Eclipse IDE (Integrated Development Environment – Môi trường Phát triển Tích hợp) để phát triển test class. Phần Phụ Lục sẽ hướng dẫn việc cài đặt IDE này. Sau đây là cấu trúc của một dự án (Hình 7).

Hình 7

Bước 4.1. Tạo dự án (project)

Bước này nhằm tạo một thư mục chính (project folder) để chứa các class và thư viện (library) cần thiết cho việc thực thi project.


Bước 4.2. Tạo gói (package)

Bước này tạo ra một hay nhiều thư mục con (subfolder) bên trong project để chứa các class. Việc tạo ra package nhằm mục đích tập hợp các class có cùng chức năng về một chỗ.


Bước 4.3. Tạo class kiểm thử (test class)

Bước này chuẩn bị cho việc phát triển phương thức test (test method) ở bước tiếp theo.


Bước 4.4. Phát triển test method

Sau khi đã tạo test class, ta sẽ phát triển test method dựa vào test cases ở Bước 3.


Bước 4.5. Phát triển class chính (main class)

Dựa vào test class để phát triển main class cho đến khi test class không còn lỗi.


Bước 4.6. Thực thi project


4.1. Tạo project

Dựa vào yêu cầu bài toán ở Bước 1, ta tạo project với tên gọi Payment: Trong File menu chọn New, rồi chọn Java Project (Hình 8).

Hình 8

Nhập Project name Payment, rồi chọn Finish (Hình 9).

Hình 9


4.2. Tạo package

Dựa vào thiết kế class sơ bộ ở Bước 2, ta tạo một package với tên gọi payment: Click phải (right-click) vào project, chọn New, rồi chọn Package (Hình 10).

Hình 10

Nhập Namepayment rồi chọn Finish (Hình 11).

Hình 11


4.3. Tạo test class

Cũng dựa vào thiết kế sơ bộ ở Bước 2, ta tạo một test class bằng cách ghép tên class Payment với từ Test để trở thành PaymentTest: Right-click vào payment package, chọn New, rồi chọn JUnit Test Case (Hình 12).

Hình 12

Chọn New JUnit 4 test, nhập NamePaymentTest, rồi chọn Finish (Hình 13).

Hình 13

Chọn OK (Hình 14).

Hình 14


4.4. Phát triển test method

Dựa vào thiết kế ở Bước 2, ta phát triển test method cho computeGrossPay() (Hình 15).

Hình 15

Trong test method, ta dùng câu lệnh assertEquals() để khẳng định sự ăn khớp giữa kết quả mà ta dự kiến sẽ nhận được (expected) và kết quả thật sự mà computeGrossPay() sẽ trả về (actual). Ta biết rằng expectedactual là các số thực (double), và do máy tính có giới hạn trong việc biểu diễn miền số thực, ta cần bổ sung vào lệnh assertEquals() một độ lệch cho phép giữa hai giá trị so sánh. Ở đây ta chỉ định độ lệch đó là 0.0, tức yêu cầu hai giá trị phải khớp với nhau hoàn toàn (Hình 16).

Hình 16

Lưu ý ở Hình 16 rằng ta đã sử dụng dấu gạch dưới làm dấu phân cách hàng ngàn cho kết quả dự kiến 1000.0 (được viết là 1_000.0).

Bây giờ ta cần thay thế actual bằng lệnh gọi thi hành computeGrossPay().computeGrossPay() được đặt bên trong Payment class, nên ta phải tạo đối tượng (object) Payment bằng toán tử new trước khi có thể gọi thi hành method (Hình 17).

Hình 17

Dựa vào thiết kế ở Bước 2 và test cases ở Bước 3, ta thay thế các giá trị vào phần ??? (Hình 18).

Hình 18


Bước 4e: Phát triển main class

Bây giờ ta sẽ làm cho PaymentTest class không còn lỗi.

Phát sinh Payment class

Di chuyển thiết bị chuột (mouse) vào Payment, chọn Create class ‘Payment’, rồi chọn Finish. Eclipse sẽ phát sinh Payment class bên trong payment package (Hình 19 20).

Hình 19

Hình 20


Phát sinh phương thức tạo object (constructor) cho Payment class

Constructor là một method nhằm hỗ trợ việc tạo ra một object thuộc một class. Đây là method đặc biệt vì tên của nó trùng với tên class. Để phát sinh constructor, ta quay về PaymentTest class, tiếp tục di chuyển mouse vào Payment, chọn Create constructor (Hình 21 22).

Hình 21

Hình 22

Ta hoàn chỉnh constructor bằng cách đổi tên parameters và bổ sung fields như theo thiết kế ban đầu (Hình 25).

Hình 23

Ở đây constructor có nhiệm vụ tiếp nhận các giá trị vào parameters rồi lưu chúng vào fields để xây dựng một object mới. Để ý rằng ta có hai cặp field và parameter trùng tên. Để phân biệt đâu là field, ta thêm từ khóa this vào trước tên.


Phát sinh computeGrossPay() method

Ta quay về PaymentTest class, di chuyển mouse vào computeGrossPay, rồi chọn Create method (Hình 24 25).

Hình 24

Hình 25


Phát triển computeGrossPay() method

Dựa vào công thức tính gross pay của đề bài, ta hoàn tất phần tính toán của method này (Hình 26).

Hình 26


4.5. Thực thi project

Quay về PaymentTest class, click menu Run, chọn Run As, rồi chọn tiếp JUnit Test (Hình 27). Nếu thấy tín hiệu màu xanh tức là kết quả dự kiến của ta và kết quả tính toán từ chương trình khớp với nhau (Hình 28).

Hình 27

Hình 28


IV. Kết luận

Bài viết đã trình bày một qui trình giải quyết vấn đề để tìm ra một giải pháp lập trình hướng đối tượng. Đây là qui trình cần theo trước khi khởi sự viết code. Giải pháp cuối cùng bao gồm hai phần: phần mã kiểm thử (test code) và phần code chính. Một giải pháp chỉ được xem là hoàn tất khi ta thu được tín hiệu màu xanh.


V. Tài liệu tham khảo
  1. Gaddis T. (2010) Starting Out with Java From Control Structures Through Objects (4th edition), Pearson Education, Boston. Chương 1 và 2.
  2. Felleisen M., Findler R.B., Flatt M., and Krishnamurthi S. (2010) How To Design Programs, Second Edition, The MIT Press. http://www.ccs.neu.edu/home/matthias/HtDP2e/index.html
  3. Dromey R.G. (1982) How to Solve it by Computer, Prentice-Hall, London.

VI. Thuật ngữ tiếng Anh

class lớp
class diagram sơ đồ lớp
click kích nút chuột trái
code
constructor phương thức tạo mới một đối tượng
data type kiểu dữ liệu
double kiểu số thực
field trường, thuộc tính
IDE
xem Integrated Development Environment
input dữ liệu nhập
int kiểu số nguyên
Integrated Development Environment
Môi trường Phát triển Tích hợp
method phương thức
mouse thiết bị chuột
object đối tượng
Object-Oriented Programming Lập trình Hướng Đối tượng
OOP xem Object-Oriented Programming
output dữ liệu xuất
package gói
parameter tham biến
private riêng tư
program chương trình (danh từ), lập trình (động từ)
project dự án
public công cộng
return trả về kết quả cho nơi gọi phương thức
right-click kích nút chuột phải
test case một trường hợp cần kiểm thử
test code mã kiểm thử
test class lớp kiểm thử
test method phương thức kiểm thử
UI xem user interface
UML xem Unified Modeling Language
Unified Modeling Language Ngôn ngữ Mô hình hóa Thống nhất
user interface giao diện người dùng


VII. Phụ lục. Hướng dẫn cài đặt Java SE Development Kit (JDK)Eclipse

1. Cài đặt JDK
Download phiên bản JDK mới nhất tại http://www.oracle.com/technetwork/java/javase/downloads/index.html, hiện nay là Java SE Development Kit 7u3.




Double-click file vừa download để tiến hành cài đặt.


Click Next.

Tiếp tục click Next.


Tiếp tục click Next.

Click Continue


Click Cancel rồi click Yes để bỏ qua quá trình cài đặt JavaFX (ta sẽ không dùng JavaFX trong loạt bài này) và hoàn tất quá trình cài đặt.



2. Cài đặt Eclipse IDE for Java Developers

Download phiên bản mới nhất tại http://www.eclipse.org/downloads/, hiện nay là eclipse-java-indigo-SR2.


Giải nén file này để được folder có tên là eclipse.

Double-click eclipse.exe để chạy Eclipse.


Chương trình yêu cầu xác định nơi lưu trữ (workspace) các chương trình java mà sẽ được phát triển sau này. Click OK.