Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 30 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
30
Dung lượng
212,9 KB
Nội dung
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 2 Tiếp tục khám phá 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, Kiến trúc phần mềm, ThoughtWorks Tóm tắt: 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)). Đây là phần thứ hai của loạt bài viết gồm hai phần bài minh họa về tính biểu cảm của ngôn ngữ máy tính giúp cho thiết kế nổi lên bằng cách cho phép bạn tập trung nhiều hơn vào bản chất hơn là vào nghi lễ như thế nào. Sự cách biệt lớn giữa ý định và kết quả là đặc trưng của nhiều ngôn ngữ đã có từ hàng chục năm nay (bao gồm cả ngôn ngữ Java™), khi nó thêm những nghi lễ không cần thiết cho việc giải quyết vấn đề. Các ngôn ngữ biểu cảm hơn làm cho việc tìm các mẫu diễn đạt đặc trưng trở nên dễ dàng hơn, vì mã chứa ít tạp nhiễu hơn. Tính biểu cảm này là dấu hiệu của các ngôn ngữ hiện đại như Groovy và Scala; của ngôn ngữ cũ hơn nhưng có tính biểu cảm hơn như ngôn ngữ Ruby, mà JRuby là một biến thể JVM của ngôn ngữ đó; hoặc của những ngôn ngữ cũ hơn nhưng đã được tân trang lại (reimagined) như là ngôn ngữ Clojure, là ngôn ngữ Lisp hiện đại trên JVM (xem mục Tài nguyên). Trong bài viết này tôi tiếp tục phần giải thích mà tôi đã bắt đầu trong Phần 1 — triển khai thực hiện các mẫu truyền thống của Gang of Four từ cuốn Mẫu thiết kế bằng các ngôn ngữ có tính diễn cảm hơn. Mẫu Decorator Cuốn Gang of Four định nghĩa mẫu Decorator (cái trang trí) như sau: Mẫu Decorator gắn thêm các trách nhiệm bổ sung cho đối tượng theo phương thức động. Các mẫu Decorators cung cấp một lựa chọn linh hoạt để tạo lớp con nhằm mở rộng chức năng. Nếu bạn đã từng sử dụng các gói java.io.* thì bạn ý thức được một cách sâu sắc về mẫu Decorator. Rõ ràng là các nhà thiết kế các thư viện I/O đã đọc phần Decorator của cuốn Gang of Four và thực sự đã yêu thích nó! Đầu tiên, tôi sẽ cho bạn xem việc thực hiện theo cách truyền thống cho một mẫu Decorator bằng ngôn ngữ Groovy, sau đó làm cho nó trở nên động hơn trong các ví dụ tiếp theo. Cái trang trí truyền thống Liệt kê 1 cho thấy một lớp Logger cùng với hai cái trang trí dành cho nó ( TimeStampingLogger và UpperLogger), cả hai cái trang trí này được thực hiện bằng ngôn ngữ Groovy: Liệt kê 1. Lớp Logger và hai cái trang trí class Logger { def log(String message) { println message } } class TimeStampingLogger extends Logger { private Logger logger TimeStampingLogger(logger) { this.logger = logger } def log(String message) { def now = Calendar.instance logger.log("$now.time: $message") } } class UpperLogger extends Logger { private Logger logger UpperLogger(logger) { this.logger = logger } def log(String message) { logger.log(message.toUpperCase()) } } Lớp Logger là một trình ghi nhật ký đơn giản, nó viết thông điệp ghi nhật ký ra màn hình. Lớp TimeStampingLogger thêm dấu ấn thời gian thông qua việc trang trí, và lớp UpperLogger chuyển thông điệp ghi nhật ký sang dạng chữ hoa. Để sử dụng một trong các cái trang trí này, bạn bao bọc một cá thể Logger bằng một cái trang trí thích hợp, như trong liệt kê 2: Liệt kê 2. Sử dụng các cái trang trí để bọc một trình ghi nhật ký def logger = new UpperLogger( new TimeStampingLogger( new Logger())) logger.log("Groovy Rocks") Kết quả đầu ra từ Liệt kê 2 cho bạn thấy một thông điệp ghi nhật ký với dấu ấn thời gian đã chuyển sang dạng chữ hoa: Tue May 22 07:13:50 EST 2007: GROOVY ROCKS Cho đến đây, điều khác thường duy nhất về cái trang trí này là việc thực hiện nó bằng Groovy. Nhưng tôi có thể thực hiện một cái trang trí mà không cần thêm cấu trúc phụ như trong cách tiếp cận dựa trên lớp. Trang trí tại chỗ Các mẫu thiết kế truyền thống trong cuốn Gang of Four giả định rằng giải pháp cho mọi bài toán đều yêu cầu xây dựng thêm các lớp. Tuy nhiên, các ngôn ngữ hiện đại trên JVM có những phương tiện khác, chẳng hạn như các lớp mở, cho phép bạn mở lại các lớp hiện có và thêm các phương thức mới cho chúng mà không đòi hỏi tạo lớp con. Điều này đặc biệt tiện dụng khi bạn cần thay đổi hành vi của một lớp được sử dụng một phần bởi cơ sở hạ tầng (ví dụ: Các sưu tập API), đòi hỏi một lớp nhất định. Bạn có thể sửa đổi một lớp hiện có, chuyển nó như một tham số và tận dụng các API mà không đòi hỏi API cơ sở phải khai báo một lớp trừu tượng hay một giao diện. Các lớp mở cũng cho phép bạn thực hiện sửa đổi “tại chỗ” mà không cần phải tạo lớp con. Tuy nhiên, việc thay đổi định nghĩa cho toàn bộ lớp nghe có vẻ đáng sợ: bạn có thể không muốn thay đổi ở tất cả mọi nơi. May mắn thay, cả hai ngôn ngữ Groovy và Ruby cho phép bạn thêm các phương thức mới vào các cá thể đơn lẻ của lớp. Nói cách khác, bạn có thể thêm một phương thức mới chỉ vào một cá thể của lớp Logger mà không làm ảnh hưởng đến tất cả các cá thể khác của nó. Liệt kê 3 cho thấy việc sử dụng lớp ExpandoMetaClass trong Groovy ghi đè lên phương thức log() trên một cá thể đơn lẻ của lớp Logger: Liệt kê 3. Ghi đè lên phương thức log() của một cá thể của lớp Logger def logger = new Logger() logger.metaClass.log = { String m -> println m.toUpperCase() } logger.log "this log message brought to you in upper case" Một khi bạn hiểu cơ chế hoạt động như thế nào, thì việc đọc mã này trở nên đơn giản hơn nhiều so với việc đọc mã tương ứng khi sử dụng các lớp bổ sung. Tất cả các mã trang trí liên quan sẽ xuất hiện ở một nơi thay vì bị phân tán rải rác trong vài tệp tin (vì trong ngôn ngữ Java, mỗi lớp công cộng (public) phải nằm trong một tệp tin riêng của mình). Khả năng này cũng tồn tại trong Ruby bằng cách sử dụng một tính năng của Ruby được biết đến như là phương thức đơn độc singleton method (là một cái tên hay gây nhầm lẫn vì chữ (singleton) được sử dụng với quá nhiều nghĩa) hoặc như là lớp riêng (eigenclass) tùy từng chỗ. Cùng mã đó được thực hiện trong JRuby có trong liệt kê 4: Liệt kê 4. Trang trí tại chỗ bằng cách sử dụng eigenclass của Ruby class Logger def log(msg) puts msg end end l = Logger.new def l.log m puts m.upcase end l.log "this log message brought to you in upper case" Phiên bản Ruby không sử dụng phương tiện thêm ngoài chẳng hạn như ExpandoMeta Class. Trong Ruby, bạn có thể định nghĩa một phương thức nội tuyến cho một cá thể cụ thể bằng cách đặt tên biến ở phần đầu của khai báo phương thức. Ruby có sự linh hoạt tuyệt vời về cú pháp, áp đặt ít quy tắc hơn về khi nào và ở đâu bạn có thể định nghĩa phương thức. Tính năng này cũng áp dụng được với các lớp Java được xây dựng sẵn. Ví dụ: Lớp ArrayList đáng lẽ phải có định nghĩa phương thức first() và last(), nhưng than ôi, nó đã không được làm như vậy. Tuy nhiên, thật dễ dàng để thêm các phương thức đó trong Groovy, như được thể hiện trong liệt kê 5: Liệt kê 5. Việc thêm phương thức first() và last () của Groovy cho lớp ArrayList ArrayList.metaClass.getFirst { delegate.size > 0 ? get(0) : null } ArrayList.metaClass.getLast { delegate.size > 0 ? get(delegate.size - 1) : null } ArrayList l = new ArrayList() l << 1 << 2 << 3 println l.first println l.last ArrayList emptyList = new ArrayList() println emptyList.first println emptyList.last Phương tiện ExpandoMetaClass cho phép bạn định nghĩa các thuộc tính mới của lớp (bằng cách sử dụng mẫu đặt tên get/set quen thuộc của Java). Một khi bạn đã định nghĩa các thuộc tính mới cho lớp, thì bạn có thể gọi chúng ra như bạn có thể làm với các thuộc tính bình thường. Và bạn có thể làm tương tự như vậy trong JRuby, như trong liệt kê 6, bằng cách sử dụng các lớp JDK hiện có: Liệt kê 6. Thêm các phương thức vào lớp ArrayList bằng cách sử dụng Jruby require 'java' include_class 'java.util.ArrayList' class ArrayList def first [...]... đích của bài tập này không nhất thiết phải là đi sâu vào các phù phép siêu lập trình trong Ruby, mà là để chứng tỏ những gì có thể làm được trong các ngôn ngữ có tính biểu cảm rất cao Các ngôn ngữ thông dịch luôn luôn có một lợi thế hơn các ngôn ngữ biên dịch, vì chúng có thể thi hành mã vào các thời điểm mà các ngôn ngữ biên dịch không thể làm được Thực vậy, Groovy đã đưa vào một cơ chế siêu lập trình... với trình biên dịch (xem phần Tài nguyên) Tóm tắt Vậy thì tất cả những thứ này chứng minh điều gì? Trong ngôn ngữ, tính biểu cảm tương đồng với quyền lực Bạn không thấy nhiều những kỹ thuật như thế trong ngôn ngữ Java, mặc dù chúng có thể thực hiện được về mặt kỹ thuật thông qua các khía cạnh (aspects) và việc sinh mã bytecode bằng cách sử dụng các công cụ như Javassist (xem phần Tài nguyên) Tuy nhiên,... hai ngôn ngữ Groovy và Ruby đều hỗ trợ các lớp mở, cho phép bạn bổ sung phương thức cần thiết trực tiếp vào lớp đang xét Liệt kê 11 là triển khai thực hiện chốt vuông và lỗ tròn trong ngôn ngữ Ruby (thông qua JRuby): Liệt kê 11 Cái tiếp hợp bằng lớp mở trong ngôn ngữ Ruby class SquarePeg attr_reader :width def initialize(width) @width = width end end class SquarePeg def radius Math.sqrt(((@width /2) ... làm chúng tiếp hợp được với nhau).” Đó là vấn đề mà tôi sẽ giải quyết, với hai cách thực hiện khác nhau, mỗi cách làm nổi bật tính biểu cảm trong ngôn ngữ Cách thực hiện đầu tiên sử dụng Groovy; trong liệt kê 9 có ba lớp và một giao diện liên quan đến: Liệt kê 9 Các chốt gỗ hình vuông và các lỗ tròn interface RoundThing { def getRadius() } class SquarePeg { def width } class RoundPeg { def radius } class... ký tự chữ thường: Tue May 22 07 :27 :18 EST 20 07: important message Bạn lưu ý rằng dấu ấn thời gian không được đặt ở dạng chữ thường nhưng tham số String thì ở dạng chữ thường Có thể thực hiện điều này trong ngôn ngữ Java, nhưng rất khó Thực vậy, sử dụng các khía cạnh (aspects) (thông qua AspectJ chẳng hạn), là cách duy nhất để đạt được hiệu ứng này trong ngôn ngữ Java (xem phần Tài nguyên) Để nhận được... thể thấy các mẫu đó là riêng cho ứng dụng của bạn, nếu cách để thu thập chúng là quá khó khăn, thì bạn sẽ không bận tâm, từ đó tạo ra món nợ về mặt kỹ thuật không cần thiết cho dự án của bạn Ý nghĩa của tính biểu cảm trong các ngôn ngữ máy tính là rất nhiều! Mục lục Mẫu Decorator Mẫu Adaptor Tóm tắt ... đủ hiệu lực Ta hãy xem mã của ngôn ngữ Ruby trong liệt kê 13: Liệt kê 13 Chuyển giao diện class SquarePeg include InterfaceSwitching def radius @width end def_interface :square, :radius def radius Math.sqrt(((@width /2) ** 2) * 2) end def_interface :holes, :radius def initialize(width) set_interface :square @width = width end end Mã này hoàn toàn không thể viết được bằng ngôn ngữ Java hay Groovy Bạn... dịch mã này Tuy nhiên, Ruby (và do đó JRuby) là một ngôn ngữ thông dịch, cho phép bạn thi hành mã vào thời điểm thông dịch Khi bạn nghe một số môn đồ của Ruby nói đến các cấu kiện trong Ruby như là “các công dân hạng nhất”, nghĩa là tất cả các bộ phận của ngôn ngữ này có sẵn tại mọi thời điểm Điều kỳ diệu ở đây nằm trong lời gọi phương thức def_interface (giống như từ khóa) Đây là một phương thức của... gỗ hình vuông và thực hiện phương thức getRadius() mà phương thức pegFits() của RoundHole chờ đợi Tuy nhiên, Groovy cho phép tôi bỏ qua việc cấu trúc thêm một lớp bổ xung, định nghĩa cái tiếp hợp của tôi một cách trực tiếp nội tuyến như trong liệt kê 10: Liệt kê 10 Kiểm thử mẫu adaptor nội tuyến @Test void pegs_and_holes() { def adapter = { p -> [getRadius:{Math.sqrt( ((p.width /2) ** 2) *2) }] as RoundThing... trường hợp này, tôi chặn từng cuộc gọi phương thức và duyệt qua tất cả các tham số của phương thức Nếu bất kỳ tham số nào có kiểu chuỗi ký tự, thì tôi sẽ thêm phiên bản chữ thường của nó vào một danh sách các đối số mới, và để nguyên các đối số khác Tại phần cuối của phương thức móc nối, tôi gọi phương thức ban đầu trên đối tượng đã được trang trí và sử dụng danh sách mới của tôi Cái trang trí này . 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 2 Tiếp tục khám phá 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ư. nhiễu hơn. Tính biểu cảm này là dấu hiệu của các ngôn ngữ hiện đại như Groovy và Scala; của ngôn ngữ cũ hơn nhưng có tính biểu cảm hơn như ngôn ngữ Ruby, mà JRuby là một biến thể JVM của ngôn ngữ. 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