PDF:

Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu
cảm và thiết kế, Phần 1
Tính biểu cảm trong mã lệnh của bạn tạo khả năng cho thiết kế nổi dần
như thế nào
Neal Ford ([email protected])
Kiến trúc phần mềm
ThoughtWorks
08 01 2010
Khả năng xem và thu lượm các mẫu (pattern) diễn đạt đặc trưng là rất quan trọng đối với thiết
kế nổi dần. Và điều quan trọng sống còn đối với thiết kế là tính biểu cảm của mã lệnh. Trong
loạt bài viết gồm hai phần, Neal Ford sẽ bàn về chỗ giao nhau giữa tính biểu cảm và mẫu diễn
đạt đặc trưng, giải thích các khái niệm này bằng cả mẫu diễn đạt đặc trưng lẫn mẫu thiết kế hình
thức hóa. Ông viết lại một số mẫu cổ điển của Gang of Four trong các ngôn ngữ động cho JVM
để cho bạn thấy rằng các ngôn ngữ biểu cảm hơn cho phép bạn thấy các phần tử thiết kế bị
che khuất bởi các ngôn ngữ mờ tối hơn như thế nào. (N.D: Gang of Four hay GoF - Nhóm bốn
người - là cuốn sách của bốn tác giả : Erich Gamma, Richard Helm, Ralph Johnson và John
Vlissides, được coi là nền tảng của các mẫu thiết kế khác, được phân loại làm 3 nhóm: tạo lập
(Creation), cấu trúc (Structure) và hành vi (Behavior)).
Xem thêm bài trong loạt bài này
Một trong những điều chính yếu cho phép thiết kế nổi dần là khả năng xem và thu lượm các mẫu
diễn đạt đặc trưng: các quy trình, các cấu trúc và các đặc ngữ, chúng lặp lại một cách không tầm
thường trong cơ sở mã lệnh của bạn. Tuy nhiên, đôi khi các mẫu ấy bị ẩn đi. Trong phần đầu tiên của
loạt bài viết Kiến trúc tiến hóa và thiết kế nổi dần tôi đã mô tả các vấn đề che khuất tầm nhìn của
các mẫu này, chẳng hạn như vấn đề khái quát quá đáng. Việc xây dựng các ứng dụng nhiều tầng
có thể có hiệu lực tốt cho các dạng tách biệt mối quan tâm nhằm cho phép khả năng mở rộng và
phân đoạn, nhưng nó che giấu các mẫu diễn đạt đặc trưng bởi vì bây giờ bạn phải tìm chúng xuyên
qua nhiều tầng. Muốn trở thành một nhà thiết kế và một kiến trúc sư giỏi thì bạn phải phát triển các
"con mắt" để phân biệt các mẫu đó.
Về loạt bài viết này
Loạt bài viết này nhằm cung cấp một phối cảnh tươi mới về các khái niệm thường được thảo
luận nhưng khó nắm bắt về kiến trúc và thiết kế phần mềm. Thông qua các ví dụ cụ thể, Neal
© Copyright IBM Corporation 2010
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Nhẫn hiệu đăng ký
Trang 1 của 12
developerWorks®
ibm.com/developerWorks/vn/
Ford mang đến cho bạn một nền tảng vững chắc cho cách làm thực tế lanh lẹn của kiến trúc
tiến hóa và thiết kế nổi dần. Bằng cách trì hoãn các quyết định quan trọng về thiết kế và kiến
trúc cho đến thời điểm chịu trách nhiệm cuối cùng, bạn có thể ngăn ngừa được những phức
tạp không cần thiết không để chúng ngầm phá hoại các dự án phần mềm của bạn
Một điều khác ức chế việc thu lượm các mẫu là khả năng biểu cảm của chính ngôn ngữ lập trình. Ví
dụ, ta rất khó thu lượm các mẫu từ hợp ngữ (assembly language) bởi vì các đặc tính của ngôn ngữ
này đối chọi với tính biểu cảm. Ngay cả khi bạn đã học được cách để đọc hợp ngữ như tiếng mẹ đẻ
của bạn, thì các hạn chế khắc nghiệt về cách viết mã lệnh che khuất khả năng để bạn có được một
cái nhìn toàn diện. Ví dụ: việc chuyển các biến vào và ra khỏi các thanh ghi thay vì có thể tạo ra
các biến và các phương thức có tên phù hợp có nghĩa là bạn tốn nhiều thời gian để xử lý các gánh
nặng công việc cố hữu trong ngôn ngữ này.
Việc so sánh ngôn ngữ Java™ với hợp ngữ là đòi hỏi quá đáng (stretch), nhưng tính biểu cảm của
các ngôn ngữ máy tính xếp dọc theo một dải phổ. Một số ngôn ngữ có tính biểu cảm hơn các ngôn
ngữ khác, làm cho việc xem các bản mẫu hiệu quả trở nên dễ dàng hơn. Nhằm mục đích đó, bài
viết này — là bài viết đầu tiên của loạt bài viết gồm hai phần — sử dụng một ngôn ngữ động cho
JVM (ngôn ngữ Groovy) để giải thích các triển khai thay thế của một số mẫu Gang of Four.
Ôn lại về các mẫu thiết kế
Một trong những cuốn sách có ảnh hưởng sâu xa về phát triển phần mềm là cuốn Design Patterns:
Elements of Reusable Object-Oriented Software (Các mẫu thiết kế: Các phần tử phần mềm hướng
đối tượng có thể sử dụng lại được) của các tác giả Eric Gamma, Richard Helm, Ralph Johnson và
John Vlissides (xem phần Tài nguyên). Cuốn sách này bao gồm hai phần riêng biệt: mô tả các vấn
đề chung thường gặp trong quá trình phát triển phần mềm và các ví dụ về các giải pháp của chúng.
Phần đầu tiên là có giá trị như là một danh mục liệt kê các vấn đề chung, nhưng việc triển khai thực
hiện các mẫu nhất thiết phải thiên về một ngôn ngữ cụ thể. Các triển khai thực hiện mẫu có ở trong
cả hai ngôn ngữ: C++ và Smalltalk, chúng tận dụng lợi thế của một vài tính năng ngôn ngữ tiên tiến
của Smalltalk. Bằng nhiều cách, các triển khai làm rõ các hạn chế của ngôn ngữ C++ và cách khắc
phục cần thiết để giải quyết các vấn đề cố hữu của ngôn ngữ này.
Các khía cạnh thuật ngữ của cuốn sách Gang of Four ngày nay vẫn còn có giá trị, nhưng các triển
khai thực hiện đã trở nên lỗi thời. Nhiều vấn đề mà các triển khai thực hiện ở đây đã giải quyết
bằng biện pháp cấu trúc (bằng cách xây dựng một hệ thống phân cấp các lớp tương tác với nhau)
hiện có những giải pháp đẹp đẽ hơn trong các ngôn ngữ mạnh hơn và biểu cảm hơn.
Một sự thay đổi thú vị khác đã xảy ra từ khi xuất bản cuốn sách Gang of Four. Nhiều ngôn ngữ đã
gộp các mẫu vào chính ngôn ngữ đó. Ví dụ: Ngôn ngữ Java đã thay đổi phong cách sưu tập-lặp
tuần tự (collection-iteration) của nó khi chuyển từ phiên bản JDK 1.1 sang 1.2, thay thế giao diện
Enumerator bằng giao diện Iterator để làm cho các trình lặp tuần tự (iterators) trong ngôn ngữ
Java tuân thủ chặt chẽ hơn với mẫu Iterator của Gang of Four. Các ngôn ngữ có xu hướng kết hợp
các mẫu diễn đạt đặc trưng và các đặc ngữ thông dụng khác, ở đây chúng biến mất, chỉ còn như là
một phần trong trừu tượng hóa của ngôn ngữ.
Mấy ví dụ đầu tiên cho thấy bằng việc tích hợp các mẫu Iterator và Command trực tiếp vào ngôn
ngữ, các ngôn ngữ dựa trên Java hiện đại hơn đã ôm trọn chính xác các mẫu của Gang of Four.
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 2 của 12
ibm.com/developerWorks/vn/
developerWorks®
Mẫu Iterator
Cuốn sách Gang of Four định nghĩa mẫu Iterator như sau:
Mẫu Iterator cung cấp cách để truy cập vào các phần tử của một đối tượng gộp nhóm
một cách tuần tự mà không lộ ra cách biểu diễn của nó ở bên dưới.
Các trình lặp tuần tự của Groovy
Để có một giới thiệu chi tiết về các trình lặp tuần tự của Groovy, xin xem bài viết "Reaching
for each" (lấy từng cái một) trong loạt bài viết Practically Groovy trên trang developerWorks.
Mẫu Iterator là một trong những mẫu đầu tiên xuất hiện như là một bổ sung cho ngôn ngữ Java,
dưới hình thức là giao diện và triển khai thực hiện Iterator. Groovy đã tiến một bước xa hơn, bổ
sung thêm các trình lặp tuần tự nội bộ làm một phần của API các sưu tập. Vì thế, bạn có thể lặp
tuần tự qua một sưu tập khá dễ dàng bằng cách sử dụng phương thức each kết hợp với một khối
mã lệnh, như được minh hoạ trong liệt kê 1. Liệt kê này minh hoạ một trình lặp tuần tự nội bộ (cũng
được gọi là trình lặp đẩy (push iterator) bởi vì nó đẩy lần lượt mỗi phần tử vào trong khối mã lệnh).
Liệt kê 1. Toán tử each của Groovy
def numbers = [1,2,3,4]
numbers.each { n ->
println n
}
Groovy cho phép phép lặp làm việc đối với tất cả các loại sưu tập, kể cả một mớ hỗn độn, như trong
liệt kê 2:
Liệt kê 2. Phép lặp trên một sưu tập hỗn độn
def months = [Mar:31, Apr:30, May:31]
months.each {
println it
}
Groovy cũng thực hiện hành vi mặc định rất tiện dụng là tự động cung cấp một tham số cho phép
lặp của bạn có tên là it mà bạn có thể tham chiếu đến trong khối mã lệnh.
Và Groovy hỗ trợ một trình lặp tuần tự bên ngoài (còn gọi là trình lặp kéo (pull iterator) vì bạn phải
yêu cầu tường minh hạng mục kế tiếp trong bộ sưu tập), được hiển thị trong liệt kê 3. Đây chính là
trình lặp tuần tự được xây dựng trong bản thân ngôn ngữ Java.
Liệt kê 3. Trình lặp kéo
iterator = numbers.iterator()
while (iterator.hasNext()) {
println iterator.next()
}
Trình lặp tuần tự quá phổ biến đến nỗi nó không còn là một mẫu chính thức nữa, nó chỉ là một đặc
tính của ngôn ngữ. Đây là một điều phổ biến trong thiết kế nổi dần của bản thân các ngôn ngữ máy
tính.
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 3 của 12
developerWorks®
ibm.com/developerWorks/vn/
Mẫu Command
Cuốn Gang of Four định nghĩa mẫu Command như sau:
Mẫu Command đóng gói một yêu cầu thành một đối tượng, do đó cho phép bạn tham
số hoá các trình khách với các yêu cầu khác nhau, xếp hàng hoặc ghi lại các yêu cầu,
và hỗ trợ các hoạt động có thể hoàn ngược lại (undoable).
Triển khai thực hiện chung của mẫu này trong ngôn ngữ Java tạo ra một lớp Command có bao gồm
phương thức execute(). Mẫu thiết kế Command có mặt trong Groovy như là một khối mã lệnh, đó
là bất cái gì được định nghĩa bên trong cặp dấu ngoặc ôm, đứng riêng một mình ({ và }). Thay vì
buộc bạn phải tạo một lớp mới và một phương thức tương ứng, bạn có thể thi hành khối mã lệnh
hoặc bằng cách gọi ra phương thức call() của nó hoặc bằng cách đặt một cặp dấu ngoặc đơn sau
tên biến đang nắm giữ khối mã lệnh (có kèm theo hoặc không kèm theo các tham số). Liệt kê 4 là
một ví dụ:
Liệt kê 4. Mẫu Command với các khối mã lệnh bằng ngôn ngữ Groovy
def count = 0
def commands = []
1.upto(10) { i ->
commands.add { count++ }
}
println "count is initially ${count}"
commands.each { cmd ->
cmd()
}
println "did all commands, count is ${count}"
Hỗ trợ phép hoàn ngược lại (undo)
Một trong những ưu điểm của việc sử dụng khối mã lệnh so với một cơ chế tương tự ví dụ như các
lớp bên trong vô danh là ở tính cô đọng của nó. Bởi vì việc xác định các hoạt động có thể hoàn
ngược lại (undoable) là một nhu cầu phổ biến, nên cú pháp này trở nên quan trọng. Ta hãy xem mã
lệnh của Groovy trong liệt kê 5, nó cho bạn thấy cách bạn có thể hỗ trợ các hoạt động có thể hoàn
ngược lại bằng cách sử dụng các khối mã lệnh cùng với mẫu thiết kế Command như thế nào:
Liệt kê 5. Sử dụng các khối mã lệnh để hỗ trợ các hoạt động có thể hoàn ngược lại
class Command {
def cmd, uncmd
Command(doCommand, undoCommand) {
cmd = doCommand
uncmd = undoCommand
}
def doCommand() {
cmd()
}
def undoCommand() {
uncmd()
}
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 4 của 12
ibm.com/developerWorks/vn/
developerWorks®
}
def count = 0
def commands = []
1.upto(10) { i ->
commands.add(new Command({count++}, {count--}))
}
println "count is initially ${count}"
commands.each { c -> c.doCommand() }
commands.reverseEach { c -> c.undoCommand() }
println "undid all commands, count is ${count}"
commands.each { c -> c.doCommand() }
println "redid all command, count is ${count}"
Việc chuyển các khối mã lệnh như các tham số là tầm thường, cho phép dùng cú pháp rút gọn
commands.add(new Command({count++}, {count--})), nhưng vẫn còn dễ đọc.
Khối mã lệnh, tính biểu cảm và các mẫu diễn đạt đặc trưng
Mặc dù sự khác biệt giữa các khối mã lệnh và các lớp bên trong vô danh có vẻ như chỉ là về ngữ
nghĩa, nó vẫn có tác động đến tính dễ đọc của mã lệnh của bạn và do đó tác động đến việc bạn có
thể thu lượm các mẫu diễn đạt đặc trưng dễ dàng hay không. Ta hãy xem xét ví dụ này của một
mẫu diễn đạt đặc trưng mà tôi gọi là Đơn vị công việc Unit of Work. Trước tiên là phiên bản Java (sử
dụng các lớp bên trong vô danh), như trong liệt kê 6:
Liệt kê 6. Mẫu Đơn vị công việc với một lớp bên trong vô danh
public void wrapInTransaction(Command c) throws SQLException {
setupDataInfrastructure();
try {
c.execute();
completeTransaction();
} catch (RuntimeException ex) {
rollbackTransaction();
throw ex;
} finally {
cleanUp();
}
}
public void addOrderFrom(final ShoppingCart cart, final String userName,
final Order order) throws SQLException {
wrapInTransaction(new Command() {
public void execute() throws SQLException{
add(order, userKeyBasedOn(userName));
addLineItemsFrom(cart, order.getOrderKey());
}
});
}
Bây giờ, ta hãy xem xét chính ví dụ này, được viết trong Groovy, hiển thị trong liệt kê 7. Nó tận
dụng cú pháp ngắn gọn hơn được cung cấp cùng với các khối mã lệnh:
Liệt kê 7. Sử dụng các khối mã lệnh để thực hiện mẫu Đơn vị công việc
public class OrderDbClosure {
def wrapInTransaction(command) {
setupDataInfrastructure()
try {
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 5 của 12
developerWorks®
ibm.com/developerWorks/vn/
command()
completeTransaction()
} catch (RuntimeException ex) {
rollbackTransaction()
throw ex
} finally {
cleanUp()
}
}
def addOrderFrom(cart, userName, order) {
wrapInTransaction {
add order, userKeyBasedOn(userName)
addLineItemsFrom cart, order.getOrderKey()
}
}
}
Trong khi mã lệnh trong Liệt kê 7 định nghĩa phương thức wrapInTransaction(), gần giống như mã
lệnh trong Liệt kê 6, thì mã lệnh gọi nó sạch sẽ hơn nhiều. Phiên bản Java yêu cầu tạo ra nhiều cú
pháp để thực hiện các lớp bên trong vô danh; cú pháp này che lấp ý nghĩa của những gì tôi đang
cố gắng thực hiện. Càng có nhiều cú pháp bạn phải lội qua để xem các phần tử thiết kế, thì càng
khó khăn để nhận ra rằng một mẫu đang hiện diện. Phiên bản Groovy có số lượng cú pháp tối thiểu
để thực hiện các mẫu, nó chỉ để lại nội dung thích hợp.
Mẫu Strategy
Cuốn Gang of Four định nghĩa mẫu Strategy như sau:
Mẫu Strategy định nghĩa một họ các thuật toán, đóng gói mỗi thuật toán đó, và làm
cho chúng hoán đổi được cho nhau. Mẫu Strategy cho phép các thuật toán biến đổi
một cách độc lập từ các trình khách sử dụng nó.
Triển khai thực hiện theo cách truyền thống của mẫu Strategy trong các ngôn ngữ Java yêu cầu
một giao diện định nghĩa ngữ nghĩa của thuật toán, và các lớp cụ thể cung cấp việc thực hiện các
ngữ nghĩa đó. Liệt kê 8 minh hoạ việc triển khai thực hiện bằng ngôn ngữ Java của mẫu Strategy để
nhân các số:
Liệt kê 8. Các mẫu strategies của phép nhân trong ngôn ngữ Java
public interface Calc {
public int product(int x, int y);
}
public class CalcByMult implements Calc {
public int product(int x, int y) {
return x * y;
}
}
public class CalcByAdds implements Calc {
public int product(int x, int y) {
int result = 0;
for (int i = 1; i <= y; i++)
result += x;
return result;
}
}
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 6 của 12
ibm.com/developerWorks/vn/
developerWorks®
Ngôn ngữ Java buộc bạn phải tạo ra cấu trúc để giải quyết bài toán này. Trên thực tế, các giải pháp
của Gang of Four rất thiên về việc tạo cấu trúc để thực hiện các giải pháp của mẫu — bạn có nhận
thấy rằng mỗi mẫu bao gồm một sơ đồ UML cho thấy các giải pháp? Nhưng việc xây dựng cấu trúc
không phải luôn luôn là cách rõ ràng nhất hoặc là ngắn gọn nhất để xử lý các vấn đề. Ta hãy xem
liệt kê 9, liệt kê này thực hiện cùng mẫu đó trong Groovy:
Liệt kê 9. Các mẫu strategies của phép nhân viết bằng ngôn ngữ Groovy
interface Calc {
def product(n, m)
}
def multiplicationStrategies = [
{ n, m -> n * m } as Calc,
{ n, m -> def result = 0
n.times { result += m }
result
} as Calc
]
def sampleData = [
[3, 4, 12],
[5, -5, -25]
]
sampleData.each{ data ->
multiplicationStrategies.each{ calc ->
assert data[2] == calc.product(data[0], data[1])
}
}
Trong ví dụ Groovy, bạn không cần phải tạo ra thêm các lớp một cách tường minh để thực hiện các
giao diện định nghĩa ngữ nghĩa của hoạt động gọi ra. Toán tử as rất mạnh trong Groovy sử dụng
khối mã lệnh và tạo ra một lớp mới, lớp này thực hiện giao diện, mà sau đó bạn có thể gọi ra như
thể nó là một lớp cụ thể thực hiện giao diện ấy. Như vậy, trong ví dụ thi hành này, tất cả các khối
mã lệnh định nghĩa một strategy khi đang hành trình (on the fly) vẫn có thể hành động như các lớp
cụ thể chính thức, thực hiện giao diện Calc.
Mẫu Interpreter
Mẫu Interpreter của Gang of Four là một trường hợp đặc biệt. Định nghĩa về mẫu này như sau:
Cho một ngôn ngữ, nó định nghĩa một biểu diễn cho ngữ pháp của nó cùng với một
trình thông dịch sử dụng cách biểu diễn ấy để thông dịch các câu trong ngôn ngữ này.
Mẫu này chủ yếu là một mẫu "thoát khỏi nhà tù" (ý nói là mẫu này là mẫu tự do, không theo thể
thức). Đây là dạng chấp nhận chính thức của cái mà Philip Greenspun đã tiếp thu tốt hơn dưới dạng
quy tắc thứ mười của Greenspun (Greenspun's Tenth Rule) (xem phần Tài nguyên):
Là bất kỳ chương trình C hoặc Fortran đủ phức tạp nào chứa một triển khai thực hiện
phi thể thức (ad hoc), được xác định một cách không chính thức, đầy lỗi (bug-ridden)
và chạy chậm bằng một nửa của Common Lisp.
Philip Greenspun muốn nói là khi bạn xây dựng một phần mềm ngày càng phức tạp hơn trong một
ngôn ngữ yếu hơn, thì bạn thực sự đang thực hiện một cách phi thể thức các đặc tính của các ngôn
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 7 của 12
developerWorks®
ibm.com/developerWorks/vn/
ngữ mạnh mẽ hơn (như ngôn ngữ Lisp), mỗi lần một đặc tính, mà không nhận thấy điều đó. Mẫu
Interpreter là sự chấp nhận rằng ngôn ngữ cơ sở của bạn có lẽ không đủ cho tác vụ đang phải làm,
và trong trường hợp đó thì giải pháp tốt nhất là sử dụng ngôn ngữ này để xây dựng một ngôn ngữ
tốt hơn nằm bên trên nó.
Mẫu này cho ta thấy thời của cuốn sách Gang of Four và tư duy của nó. Bốn tác giả chủ trương từ
bỏ ngôn ngữ lõi của bạn và xây dựng một ngôn ngữ hoàn toàn mới trên nó, tạo ra từ vựng, phân
tích cú pháp, ngữ pháp của riêng bạn, v.v… Tuy nhiên, một giai đoạn trung gian của mẫu này đã
nổi lên thành xu hướng chủ đạo trong một vài năm qua (mặc dù nó đã có từ thời của ngôn ngữ
Lisp): làm cho ngôn ngữ của bạn trở nên biểu cảm hơn bằng cách xây dựng một ngôn ngữ đặc thù
cho miền (Domain-specific language- DSL) nằm bên trên nó.
Việc xây dựng các DSL ở bên trên của ngôn ngữ Java rất khó khăn vì cú pháp của ngôn ngữ này
khá cứng nhắc, và nó có ít điểm mở rộng ở mức ngôn ngữ. Phổ biến hơn là xây dựng các DSL bằng
các ngôn ngữ như Groovy và Ruby bởi vì cú pháp vừa mở rộng được vừa khoan dung hơn.
Liệt kê 10 là một ứng dụng trình diễn một công thức chế biến để làm DSL nhỏ được viết bằng ngôn
ngữ Groovy:
Liệt kê 10. Công thức chế biến DSL trong Groovy
def recipe = new Recipe("Spicy Bread")
recipe.add 1.gram.of("Nutmeg")
recipe.add 2.lbs.of("Flour")
println recipe
Siêu lập trình của Groovy
Hãy tìm hiểu thêm về lớp ExpandoMetaClass và các tính năng siêu lập trình
(metaprogramming) khác của Groovy trong cuốn "Groovy theo cách thực hành: Siêu lập trình
với bao đóng, ExpandoMetaClass và các loại hình."
Những dòng thú vị của mã lệnh trong Liệt kê 10 là những dòng ở giữa, những dòng này xác định
các thành phần của công thức chế biến. Groovy cho phép bạn thêm các phương thức mới vào bất
kỳ lớp nào (bao gồm cả java.lang.Integer, là cách Groovy xử lý các trực kiện (literal) kiểu số). Đó
là tại sao mà tôi có thể gọi các phương thức trên các giá trị số. Để thêm một phương thức mới vào
một lớp hiện có, bạn có thể sử dụng một cơ chế của Groovy có tên là ExpandoMetaClass, như trong
liệt kê 11:
Liệt kê 11. Thêm phương thức vào lớp Integer thông qua ExpandoMetaClass
Integer.metaClass.getGram { ->
delegate
}
Integer.metaClass.getGrams {-> delegate.gram }
Integer.metaClass.getPound { ->
delegate * 453.29
}
Integer.metaClass.getPounds {-> delegate.pound }
Integer.metaClass.getLb {-> delegate.pound }
Integer.metaClass.getLbs {-> delegate.pound }
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 8 của 12
ibm.com/developerWorks/vn/
developerWorks®
Trong Liệt kê 11, tôi định nghĩa một thuộc tính mới có tên là getGram (nó cho phép tôi gọi nó ra từ
Groovy mà không cần có tiền tố get) của siêu lớp của Integer. Trong định nghĩa của thuộc tính,
delegate tham chiếu đến giá trị của cá thể đang xét (this) của lớp Integer; tôi đang dùng tất cả
các đơn vị đo lường trong DSL của tôi là theo gam, do đó, nó sẽ trả về giá trị số nguyên. Một trong
những mục tiêu của DSL là tính lưu loát, vì vậy tôi cũng định nghĩa một thông dịch sang số số nhiều
của thuộc tính gam với tên là getGrams, làm cho mã lệnh DSL dễ đọc hơn. Tôi cũng cần phải hỗ
trợ cho pound (pao-đơn vị đo lường của Anh) làm một đơn vị đo, vì vậy tôi cũng định nghĩa một họ
của các thuộc tính pound.
Các thuộc tính mới xử lý phần đầu tiên của DSL của tôi, chỉ để nguyên phương thức of. Phương
thức of cũng là một phương thức được bổ sung vào Integer, có trong liệt kê 12. Phương thức này
nhận một tham số duy nhất, gán tên của thành phần, đặt số lượng, và trả về đối tượng của thành
phần mới được tạo ra.
Liệt kê 12. Phương thức of được bổ sung vào Integer
Integer.metaClass.of { name ->
def ingredient = new Ingredient(name);
ingredient.quantity = delegate
ingredient
}
Có một chút tế nhị trong mã lệnh trong Liệt kê 10 được phô ra bởi mã lệnh trong Liệt kê 12. Mặc dù
dòng đầu tiên của DSL bây giờ làm việc tốt (recipe.add 1.gram.of("Nutmeg")), dòng thứ hai thất
bại vì phương thức of mà tôi đã định nghĩa không còn áp dụng được nữa. Một khi lệnh gọi phương
thức of xuất hiện trong dòng ví dụ mẫu recipe.add 2.lbs.of("Flour"), kiểu dữ liệu của lệnh gọi
đã thay đổi từ Integer sang BigDecimal, là định dạng mặc định của Groovy cho các số dấu phẩy
động (floating-point). Điều này đã xảy ra như thế nào? Trong lệnh gọi đến các đơn vị đo bằng pao,
kiểu kết quả trả về bây giờ là một số dấu phẩy động (2 * 453,29). Vì vậy, tôi cần phải có một phương
thức of nữa bổ sung thêm cho BigDecimal, như trong Liệt kê 13:
Liệt kê 13. Phương thức of được bổ sung vào BigDecimal
BigDecimal.metaClass.of { name ->
def ingredient = new Ingredient(name);
ingredient.quantity = delegate
ingredient
}
Vấn đề này xuất hiện thường xuyên một cách đáng ngạc nhiên trong việc triển khai các DSL. Nhiều
DSL cần phải có số lượng của các sự vật, ví dụ: 1 tuần, 2 pao , 6 đô la. Việc thêm các phương thức
vào Integer cho phép bạn tạo nhiều mã lệnh biểu cảm hơn bởi vì bạn có thể sử dụng các số thực để
biểu diễn các giá trị số. Các dòng mã lệnh trong DSL thường bắt đầu với một số lượng, gọi ra một
số phương thức trung gian để hoàn tất công việc, và cuối cùng trả về một cá thể của kiểu cuối cùng
đáng quan tâm. Trong Liệt kê 10, số lượng khởi chạy việc gọi phương thức, việc gọi phương thức
này di chuyển qua Integer, sau đó qua BigDecimal, cuối cùng trả về một thành phần (ingredient).
Các DSL có xu hướng tạo ra mã lệnh nhỏ gọn hơn, bỏ đi cú pháp dài dòng không cần thiết. Việc
loại bỏ cú pháp giúp ta dễ đọc hơn, và đến lượt nó, tính dễ đọc làm cho dễ thấy hơn các phần tử
thiết kế ẩn trong mã lệnh của bạn, bị che khuất bởi cú pháp, cần thiết nhưng lộn xộn.
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 9 của 12
developerWorks®
ibm.com/developerWorks/vn/
Kết luận cho phần 1
Trong bài viết này, tôi đã bàn về việc khả năng biểu cảm của ngôn ngữ ảnh hưởng như thế nào đến
tính dễ đọc và khả năng nhìn thấy (và do vậy thu lượm được) các mẫu đặc ngữ trong mã lệnh của
bạn – đó là các phần tử thiết kế thực mà bạn muốn tìm thấy và sử dụng lại. Tôi đã bàn về cách mà
một số mẫu trong cuốn Gang of Four có thể được thực hiện như thế nào trong các ngôn ngữ diễn
cảm hơn (ví dụ như Groovy). Trong Phần 2, tôi sẽ tiếp tục bàn luận về giao điểm của tính biểu cảm
và ngôn ngữ, các cách thức mà trong đó các mẫu thiết kế chính thức có thể được thể hiện tốt hơn,
và cách mà các ngôn ngữ cung cấp cho bạn các khả năng vốn không tồn tại theo nghĩa đen trong
các ngôn ngữ ít diễn cảm hơn.
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 10 của 12
ibm.com/developerWorks/vn/
developerWorks®
Tài nguyên
Học tập
• Nhà lập trình năng suất cao (Tác giả: Neal Ford, nhà xuất bản: O'Reilly Media, 2008): Cuốn
sách gần đây nhất của Neal Ford mở rộng một số chủ đề của loạt bài viết này.
• Các mẫu thiết kế: Các phần tử của phần mềm hướng đối tượng có thể sử dụng lại được (Tác
giả: Erich Gamma et al, nhà xuất bản: Addison.-Wesley, 1994). Tác phẩm kinh điển của Gang
of Four về các mẫu thiết kế.
• Practically Groovy: Khám phá loạt bài viết này trên trang developerWorks để tìm hiểu thêm về
Groovy, một ngôn ngữ năng động cho các JVM.
• Quy tắc thứ 10 của Greenspun: Từ điển Wikipedia giải thích cách ngôn của Philip Greenspun.
• "Vượt qua ranh giới: Vẻ đẹp của Lisp" (Tác giả Bruce Tate, developerWorks, tháng 2, 2007):
Nếu bạn tò mò muốn biết lý do tại sao các ngôn ngữ khác vay mượn từ Lisp, thì hãy đọc bài
viết này.
• Duyệt qua hiệu sách công nghệ để tìm các sách về chủ đề này và các chủ đề kỹ thuật khác.
• Vùng công nghệ Java trên trang developerWorks: Tìm hàng trăm bài viết về mọi khía cạnh
của lập trình Java.
Lấy sản phẩm và công nghệ
• Tải về phiên bản đánh giá sản phẩm IBM hoặc khám phá dùng thử trực tuyến tại trang IBM
SOA Sanbox và thực hành trên các công cụ phát triển ứng dụng và các sản phẩm phần giữa
của DB2®, Lotus®, Rational®, Tivoli® và WebSphere®.
Thảo luận
• Hãy tham gia vào cộng đồng My developerWorks.
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 11 của 12
developerWorks®
ibm.com/developerWorks/vn/
Đôi nét về tác giả
Neal Ford
Neal Ford là một kiến trúc sư phần mềm và Meme Wrangler tại Thought Works, một
văn phòng tư vấn CNTT toàn cầu. Ông cũng thiết kế và phát triển các ứng dụng, tài
liệu hướng dẫn, các bài báo trên tạp chí, học liệu và các bài thuyết trình video/DVD;
và ông là tác giả hoặc người biên tập các cuốn sách bao trùm nhiều loại công nghệ,
bao gồm cả cuốn sách gần đây nhất là The Productive Programmer. Ông tập trung
vào việc thiết kế và xây dựng ứng dụng doanh nghiệp có quy mô lớn. Ông cũng là một
diễn giả được quốc tế hoan nghênh tại hội nghị của các nhà phát triển trên toàn thế
giới
© Copyright IBM Corporation 2010
(www.ibm.com/legal/copytrade.shtml)
Nhẫn hiệu đăng ký
(www.ibm.com/developerworks/vn/ibm/trademarks/)
Kiến trúc tiến hóa và thiết kế nổi dần: Ngôn ngữ, tính biểu cảm và
thiết kế, Phần 1
Trang 12 của 12