Thursday, 12 April 2012

Chương VI: ĐƯA MỘT CÁI CHỐT HÌNH TRÒN VÀO MỘT LỖ HÌNH VUÔNG VỚI MẪU CHUYỂN ĐỔI ADAPTER VÀ MẪU HÌNH DÁNG FAÇADE

copyright from: congdongcviet.com

Trích dẫn:
Trong chương này:
- Sử dụng mẫu chuyển đổi Adapter
- Tạo một adapter
- Chuyển đổi một đối tượng Ace thành đối tượng Acme
- Xử lý các rắc rối với việc chuyển đổi
- Sử dụng mẫu façade
Đôi khi một đối tượng không thích hợp với những gì ta mong muốn. Một lớp có thể thay đổi, hoặc một đối tượng trở nên khó khăn khi sử dụng. Chương này sẽ là giải pháp tốt cho vấn đề trên khi sử dụng hai mẫu thiết kế: mẫu chuyển đổi adapter và mẫu façade. Mẫu adapter cho phép bạn chuyển đổi một đối tượng để cung cấp cho một lớp khác có thể sử dụng chúng. Mẫu façade cũng tương tự vậy, nó thay đổi vẻ ngoài của một đối tượng, nhưng có một chút khác biệt: bạn sử dụng mẫu này để đơn giản hóa các chức năng của nó, làm cho nó dễ làm việc với đối tượng hay lớp khác.
Kịch bản của mẫu Adapter

“Được,” trưởng nhóm phát triển MegaGigaCo nói, khi đang bước vào phòng, “Giữ tất cả mọi thứ, quản lý đã ra lệnh rằng chúng ta phải chuyển đổi cơ sở hạ tầng sang một hệ thống mới, hệ thống đã được bán bởi công ty con của giám đốc”

“Hmm”, các lập trình viên nói, “đó là một vấn đề. Giao diện khách hàng trực tuyến của chúng ta cho phép khách hàng sử dụng phần mềm từ công ty Ace và đóng gói chúng trong một lớp tên Ace. Làm sao để chúng ta có thể chuyển đổi chúng cho phù hợp với hệ thống mới?

“Chỉ có thể là một đối tượng Acme mới”, trưởng nhỏm nói “không phải là đối tượng Ace”

“Oh, không”, mọi người nói “Làm sao có thể như vậy được”

Bạn có thể thấy rắc rối ở đây. Hiện tại, hệ thống phù hợp với một đối tượng Ace như hình vẽ



Nhưng khi hệ thống thay đổi, nó yêu cầu một đối tượng Acme ( không phải Ace), vì vậy đối tượng Ace không thích hợp nữa. Xem hình sau:



“Tôi có một giải pháp”. Bạn nói. Mọi người quay sang, và bạn nói tiếp ”Dĩ nhiên, với tư cách là một tư vấn viên, tôi sẽ tính chi phí cao cho việc này”

“OK”, trưởng nhóm nói.

“Bạn cần sử dụng mẫu chuyển đổi Adapter”, bạn giải thích, “Mẫu adapter cho phép bạn chuyển đổi một lớp hoặc một đối tượng sang một lớp hoặc đối tượng mới mà bạn mong muốn”. Bạn vẽ ra giải pháp lên tấm bảng như hình sau:



“Ah” cả nhóm phát triển phần mềm nói “Chúng tôi đang hiểu ra vấn đề”

“Tốt” bạn nói “Vậy phải trả phí cho tôi”

Chỉnh sửa rắc rối khi kết nối với mẫu Adapter:
Mẫu thíêt kế Adapter cho phép bạn sửa đổi một giao diện giữa đối tượng và một lớp mà không phải sửa đổi trực tiếp lên chúng. Khi bạn làm việc với một ứng dụng mua sẵn, sản phẩm bạn nhận được thường không tương thích với những gì bạn thật sự muốn.
Đây là phần đặc biệt quan trọng trong phát triển trực tuyến. Ngày càng nhiều các công ty tạo ra các sản phẩm cho những công ty lớn, họ đang bỏ qua các phần mềm cho những công ty nhỏ. Và điều đó thật đáng xấu hổ vì việc tương thích của hệ thống, phần mềm từ công ty nhỏ không thể giao tiếp với một hoặc nhiều thành phần khác trong toàn hệ thống. Nhưng việc chuyển sang một giải pháp đắt tiền thì không phải lúc nào cũng cần thiết. Thông thường, giải pháp có thể là một bộ chuyển đổi nhỏ.

Cách tốt nhất để xem mẫu Adapter làm việc là thông qua ví dụ. Hiện tại giao tiếp người dùng công ty MegaGigaCo mà tôi đã nhắc tới trong phần trước của chương này, dữ liệu người dùng được đóng gói trong lớp Ace. Lớp này quản lý tên của khách hàng, với hai hàm sau:



Nhưng theo bạn biết, công ty MegaGigaCo đang chuyển sang sử dụng phần mềm Acme, mà cách thức quản lý khách hàng có khác một chút. Vấn đề ở đây là phần mềm Acme cần một đối tượng Acme. Đối tượng này có tới bốn hàm, chứ không phải hai, dùng để quản lý tên khách hàng. Chúng là:



Vì vậy bạn cần một bộ chuyển đổi để chắc chắn rằng hệ thống mới Acme có thể xử lý được đối tượng Ace. Bộ chuyển đổi này gọi hai hàm của đối tượng Ace và mở rộng chúng thành bốn hàm mà đối tượng Acme yêu cầu, như hình sau:



Đây chính là cách thức làm việc của mẫu Adapter

Ghi nhớ: Sách của GoF định nghĩa mẫu Adapter như sau: “Chuyển đổi giao tiếp của một lớp sang một kiểu giao tiếp khác mà khách hàng mong muốn. Mẫu adapter cho phép các lớp có thể làm việc với nhau cho dù giao tiếp của chúng không tương thích nhau”

Thông qua định nghĩa chính thức của mẫu Adapter nói về các lớp, mẫu này bao gồm hai phần chính như sau: một cho đối tượng, một cho lớp. Chúng ta sẽ xem xét cả hai trong chương này.
Bạn sử dụng mẫu Adapter khi bạn cố gắng đưa một cái chốt hình vuông vào cái lỗ hình tròn. Nếu một lớp có giao tiếp không tương thích, bạn có thểm thêm vào một bộ chuyển đổi – giống như bộ chuyển đổi điện trong những chuyến du lịch toàn cầu – để có thể đạt được yêu cầu mong muốn
Mẫu này đặc biệt tốt trong trường hợp bạn đang làm việc với mã nguồn cũ mà yêu cầu là không được thay đổi mã cũ, trong khi phần mềm giao tiếp với mã nguồn cũ này lại thay đổi.
Giờ đã đến lúc để mẫu Adapter làm việc.

Tạo một đối tượng Ace:

Trước khi công ty con của MegaGigaCo phá hỏng mọi thứ trong phòng bạn, đối tượng Ace đang quản lý khách hàng với hai hàm như sau: setName và getName – đây là một giao diện với hai hàm sau:


Đối tượng Ace sẽ được tạo ra từ lớp AceClass, lớp này hiện thực giao diện trên



Với hai hàm setName và getName đơn giản như sau:



Đó là tất cả những gì bạn cần trong hệ thống cũ. Một đối tượng Ace được trả về. Tuy nhiên hiện tại, công ty bạn chuyển sang hệ thống Acme, hệ thống mới cần giao tiếp với đối tượng Acme. ( Một lần nữa xin cảm ơn công ty con MegaGigaCo)

Tạo đối tượng Acme

Đối tượng Acme cần quản lý tên khách hàng với bốn hàm sau: setFirstName, setLastName, getFirstName và getLastName. Đây là giao diện của nó, AcmeInterface. Mã như sau:



Đối tượng Acme được tạo từ lớp Acme, hiện thực giao diện AcmeInterface như sau:



Và đây là bốn hàm của lớp Acme



Tới lúc này, bạn đã có đối tượng Ace, và đối tượng Acme. Bây giờ bạn cần một bộ chuyển đổi để gắn đối tượng Ace và hệ thống mới Acme.

Tạo đối tuợng chuyển đổi Ace-to-Acme

Bạn muốn tạo một bô chuyển đổi để giúp cho ứng dụng có thể làm việc với đối tượng Ace ( cho dù nó mong muốn một đối tượng Acme), vì vậy bạn phải tạo một đối tượng chuyển đổi. Đối tượng chuyển đổi này làm việc với sự “kết hợp” composition ( xem chương hai để biết thêm về “kết hợp” composition) – một bộ chuyển đổi lưu trữ chính đối tượng mà nó muốn chuyển đổi.
Tiếp tục với ví dụ trong chương này, tôi đặt tên cho bộ chuyển đổi là AceToAcmeAdapter và bởi vì nó trông giống một đối tượng Acme, nó sẽ hiện thực giao diện AcmeInterface:



Bộ chuyển đổi này sử dụng đối tượng kết hợp “composition” để lưu giữ đối tượng chuyển đổi, đó là một đối tượng AceClass. Bạn có thể chuyển đối tượng này tới lớp thông qua hàm khởi dựng. Mã như sau:



Điểm khác biệt giữa đối tượng Ace và Acme là đối tượng Ace chứa tên khách hàng như là một chuỗi duy nhất, trong khi đối tượng Acme lưu trữ tên và họ khách hàng riêng biệt. Để chuyển đổi giữa đối tượng Ace và Acme, tôi tách phần tên trong đối tượng Ace ra thành tên và họ. Bạn vẫn có thể nhận được tên khách hàng lưu trữ trong đối tượng Ace khi sử dụng hàm getName. Mã như sau:



Bây giờ bạn đã có tên và họ của khách hàng. Để tương thích với đối tượng Acme, bạn phải hiện thực các hàm của Acme như setFirstName, setLastName, getFirstName và getLastName. Mã như sau:



Tuyệt vời. Bạn đã có một bộ chuyển đổi. Nó làm việc ra sao?

Cho chạy thử bộ chuyển đổi:

Thông qua phần trên, bạn đã chuyển đổi một đối tượng Ace để chúng trông giống như một đối tượng Acme. Giờ là lúc để thấy cách mẫu Adapter làm việc. Bắt đầu bằng việc tạo một đối tượng Ace chứa thông tin khách hàng tên Cary Grant.



Sau đó bạn chuyển đối tượng Ace sang cho đối tượng AceToAcmeAdapter



Và tiếp tục, bạn có thể sử dụng các hàm của Acme như getFirstName và getLastName một cách dễ dàng, không rắc rối gì:



Kết quả nhận được:



Đó là những gì bạn mong muốn khi bạn sử dụng đối tượng Acme thật sự. Bạn đã sử dụng một đối tượng Acme từ một đối tượng Ace nhờ bộ chuyển đổi. Đó là cách thức làm việc của mẫu Adapter. 
Một Adapter sử dụng một “composition” để lưu trữ một đối tượng mà nó muốn chuyển đổi, và khi các hàm của bộ chuyển đổi được gọi, nó sẽ thay đổi các hàm trong đối tượng gốc sang các hàm của đối tượng mới. Đoạn mã gọi một bộ chuyển đổi sẽ không cần quan tâm tới cách thức làm việc của bộ chuyển đổi, vì bộ chuyển đổi đã thực hiện bên trong và trả về giá cần thiết cho hệ thống.

Sử dụng một đối tượng kết hợp “composition” để bao bọc đối tượng chuyển đổi là một thiết kế hướng đối tượng tốt, như những gì chúng ta đã nói tới trong chương hai. Và chú ý rằng nếu bạn kế thừa một đối tượng adapter, bộ bao bọc adapter sẽ có thể quản lý các đối tượng kế thừa với sự thay đổi ít nhất.
ĐƠN GIẢN HÓA CUỘC SỐNG VỚI MẪU FACADE

Một mẫu thiết kế tương tư với mẫu Adapter là mẫu Façade. Hai mẫu này làm việc theo cùng một cách, nhưng mục đích sử dụng của chúng khác nhau. Mẫu adapter chuyển đổi mã nguồn để làm việc được với mã nguồn khác. Nhưng mẫu Façade cho phép bạn bao bọc mã nguồn gốc để nó có thể giao tiếp với mã nguồn khác dễ dàng hơn.

Ví dụ như, có một người thiết kế ra một cái máy in, và đưa cho bạn một cách tự hào. “Làm sao để máy có thể in?” bạn hỏi

“Đầu tiên,” anh ta nói với bạn, “gọi hàm khởi động.”

“Được” bạn nói. “Bây giờ nó in chưa?”

“Chưa, bạn phải gọi turnFanOn”

“OK, giờ nó sẽ in chứ”, bạn hỏi

“Chưa, hãy gọi hàm làm nóng máy warmUp”

“Được rồi. Giờ nó sẽ in, phải không?”

“Vẫn chưa. Bạn phải gọi hàm getData để đưa dữ liệu từ máy vi tính tới máy in”

“OK, hàm getData. Còn gì nữa không?”

“Hàm định dạng dữ liệu formatData”

“Và gì nữa?”

“Hàm kiểm tra đầu mực checkToner, hàm kiểm tra giấy checkPaperSupply, hàm kiểm tra hệ thống runInternalDiagnostic, hàm …”

“Khoan đã”, bạn nói, vậy viết cho tôi một hàm đại diện façade cho tất cả cái đống lộn xộn này. Hàm façade này sẽ gọi tất cả các hàm khác bên trong nó, và đơn giản hóa một cách đáng kể việc giao tiếp. “Chỉ vậy thôi”

“Đó là cái gì?”, người thiết kế máy in hỏi

“Hàm in ấn print”, bạn nói. “Chỉ cần gọi hàm in ấn print, và máy in hoạt động. Không cần làm gì khác.”

“Hey”, anh ta nói “đó có thể là một ý tưởng tuyệt vời. Bây giờ bạn có thể thêm một hàm prepareToCallThePrintMethod , một hàm callThePrintMethod, một hàm cleanupAfterPrinting, một hàm…”

“Anh đúng là hết thuốc chữa” bạn nói.
Mẫu thiết kế Façade tạo ra một giao diện OOP dễ dàng để sử dụng. Nó là một vấn đề thiết kế cơ bản – nếu một đối tượng hay một lớp quá khó để giao tiếp, mẫu Façade tạo ra cho bạn một giao diện để giao tiếp dễ dàng hơn. Đây là định nghĩa chính thức của GoF về mẫu Façade: “Cung cấp một giao tiếp duy nhất cho tập hợp các giao tiếp của hệ thống. Façade định nghĩa một giao tiếp cao hơn để giúp các hệ thống con dễ dàng sử dụng”

Thông thường, bạn sử dụng mẫu Façade khi bạn làm việc với những mã nguồn đã được đóng gói một cách cẩu thả. Không phải ai cũng là một chuyên gia OOP, và bạn cũng nhanh chóng thấy được rằng, khi bạn làm việc trong một môi trường phát triển phần mềm thương mại rộng lớn. Khi bạn trở nên mệt mỏi để giao tiếp với những giao diện được thiết kế rắc rối và nhận thấy rằng, bạn nhận được x,y thay vì một cái z đơn giản hơn. Đó là lúc bạn nên sử dụng một giao diện mới.
(ND: từ “giao diện” trong các đoạn văn trên có nguyên văn tiếng anh là interface - chưa có nghĩa tiếng việt cụ thể, có thể hiểu nó là một nền tảng,cách thức giao tiếp giữa các đối tượng)

Ý tưởng rất đơn giản; một façade làm đơn giản hóa một giao tiếp interface ( sử dụng nghĩa chung của từ “interface”, không phải giao diện interface trong Java ) giữa một lớp hay một đối tượng.



Bạn thường sử dụng mẫu thiết kế Façade khi bạn muốn mã nguồn đơn giản hơn nhưng lại không thể chỉnh sửa mã nguồn cũ. Thông qua việc sử dụng mẫu Façade bạn có thể giải quyết vấn đề, nó thêm vào một lớp khác bên trên, và nếu mã nguồn của lớp bên dưới thay đổi, bạn cũng phải thay đổi luôn mã nguồn của mẫu Façade.

Có một định nghĩa OOP làm việc ở đây, đôi khi còn được gọi là “Nguyên tắc về sự hiểu biết ít nhất” , đôi lúc được gọi là “Luật của Demeter” , đôi khi được gọi là “sự đóng gói hiệu quả”. Đây là ý tưởng nâng cao sự hiệu quả của OOP, bạn không muốn những thực thể riêng biệt (lớp hay đối tượng) phải biết quá nhiều về nhau. Càng ít càng tốt, bạn có thể che dấu chi tiết của từng lớp hay đối tượng và làm cho sự liên kết của chúng lỏng lẻo càng nhiều càng tốt. ( Xem chương bốn để bíêt thêm về “tháo lỏng các mối liên kết” loose coupling) . Nếu một đối tượng cấn phải biết quá nhiều về đối tượng khác, đó chính là lúc cần sử dụng mẫu Façade.

Ghi chú: Hãy tháo lỏng các mối liên kết càng nhiều càng tốt.

Làm việc với một đối tượng khó khăn

Đây là một ví dụ minh họa cách thức mẫu Façade làm việc. Công ty bạn vừa mua một công ty đối thủ, người quản lý đang rất hân hoan.

“Hmm” bạn nói “ Chúng ta đang gặp rắc rối với vấn đề tương thích?. Sản phẩm của họ quá khác biệt với chúng ta”

“Phi lý”, Ông chủ lớn nói “Mọi việc không thể đơn giản hơn”

“Được,” bạn nói “Làm sao ông có thể đặt tên cho nó”

“Không thể đơn giản hơn. Cứ gọi hàm setFirstNameCharacter. Nó sẽ đặt kí tự đầu cho tên”

“OK, vậy kí tự thứ hai của tên thì sao?”

“Chỉ cần gọi hàm setSecondNameCharacter. Không thể đơn giản hơn”

“OK, để tôi tiếp tục.” bạn nói ”Vậy để đặt tên cho một sản phẩm, ông gọi hàm setFirstNameCharacter để thíêt lập kí tự đầu tiên cho tên, sau đó gọi hàm setSecondNameCharacter để thiết lập kí tự thứ hai, và bằng cách đó ông tiếp tục gọi hàm setFiveMillionNameCharacter để thiết lập kí thứ thứ năm triệu cho tên?”

“Không”, Ông chủ lớn nói “Bạn chỉ có thể đặt tên với bảy kí tự”

“À,” bạn nói “Không thể đơn giản hơn”

“Đúng”, ông chủ nói

Đây là đoạn mã, sau khi bạn hợp nhất với sản phẩm của công ty mới, lớp DifficultProduct



Bạn đặt tên cho sản phẩm này từng kí tự một, sử dụng hàm setFirstNameCharacter, setSecondNameCharacter, setThirdNameCharacter, và hàm cho kí tự thứ tư, thứ năm… như sau:



Và để lấy được tên sản phẩm, bạn gọi hàm getName, trả về một chuỗi.



Đây là cách tạo một máy in, đặt tên theo từng kí tự một:



“Thấy không?” ông chủ nói “không thể dễ dàng hơn”

“Quá đủ cho sự phi lý” bạn nói ”Tôi sẽ viết một mẫu Façade”

Tạo một mẫu Façade đơn giản:

Ông chủ đồng ý cách của bạn và bạn bắt đầu viết một façade đơn giản như sau:



Façade này phải bao bọc đối tượng (đối tượng DifficultProduct trong ví dụ). Thông thường, bạn viết một façade mà cho phép façade chỉnh sửa giao diện bên ngoài của đối tượng. Bạn cũng có thể chuyển tham số cấu hình vào hàm khởi dựng của façade, nhưng điều đó không cần thiết trong ví dụ này, ta chỉ cần tạo mới đối tượng DifficultProduct như sau:



Rắc rối với việc sử dụng đối tượng DifficultProduct nguyên thủy là cách mà ta thiết lập tên cho nó, sử dụng vụng về một loạt các hàm setFirtNameCharacter, setSecondNameCharacter, setThirdNameCharacter và vân vân. Để sửa chữa , bạn quyết định cung cấp một façade với một hàm đơn giản là setName để đặt tên cho đối tượng. Mã như sau:



Có một hàm không cần dùng Façade, đó là hàm getName, mã như sau:



Bây giờ bạn đã bao bọc một đối tượng khó giao tiếp và hợp nhất các hàm rắc rối thành hàm dễ sử dụng. Hãy xem cách thức hoạt động của mẫu Façade

Chạy thử mẫu Façade

Đầu tiên tạo một đối tượng SimpleProductFacade, sau đó thiết lập tên đối tượng “printer” với hàm setName và nhận lại với hàm getName.



Và đây là kết quả:



Bạn đã chinh phục được đối tượng khó khăn với giao diện khó sử dụng bằng một façade.

Chương II: Sắp đặt kế hoạch hành động với mẫu STRATEGY

copyright from: diendan.congdongcviet.com


Trong chương này, chúng ta sẽ đi qua các nội dung sau:
-Mở rộng việc lập trình hướng đối tượng
-Làm quen với các khái niệm trừu tượng, đóng gói, đa hình và kế thừa
-Chuyển đổi qua lại giữa 2 khái niệm “is-a” và “has-a”
-Xử lý công việc bằng các thuận toán
-Áp dụng mẫu Strategy vào thực tế
Bạn và các chuyên gia thiết kế mẫu, đi vào phòng họp của công ty MegaGigaCo, giám đốc điều hành và các thành viên ban quản trị đang ăn mừng một hợp đồng mới về thiết kế xe hơi, mọi người vỗ tay và hò reo ăn mừng quang phòng.

“Hợp đồng này sẽ đem đến nguồn thu nhập lớn cho chúng ta”, giám điều hành nói, cùng với tiếng vang bốp bốp của rượu champagne và sự phấn khích của giám đốc. “Việc của chúng ta là phải chắc chắn có được một quy trình thiết kế đúng”. Ông nhấn nút lên chiếc đèn chiếu và hình ảnh các biểu đồ hiện lên tường. Ông nói tiếp: “Đây là ý kiến của tôi…”

“Sai”, bạn nói

Giám đốc thoáng một chút giật mình và nói tiếp, “Nhưng nếu chúng ta…”

“Không,” bạn lắc đầu nói.

“Xin thứ lỗi”, bạn nói với Giám đốc và ban điều hành, “Rõ ràng là chúng ta đang mạo hiểm với hợp đồng này vì đã đi sai hướng. Tôi có thể thấy cả tá vấn đề khi nhìn vào các biểu đồ này”

Ban giám đốc thì thầm với vẻ tập trung và Giám đốc hỏi. “Theo ý kiến anh thì sao…”

“Tôi là chuyên gia thiết kế mẫu, người sẽ giải quyết tất cả các vấn đề về thíêt kế,” Bạn nói. “Dĩ nhiên là cho những hợp đồng lớn”

Giám đốc viết ra một con số dự đoán cho chi phí, một con số khá lớn, tuy nhiên hình như vẫn chưa đủ lớn đối với bạn.

“Lại sai”, bạn nói

Vị giám đốc nhìn bạn nhíu mày.

“Mẫu thiết kế”, bạn giải thích. “Các giải pháp chung để giải quyết cho các vấn đề lập trình thường gặp. Không chỉ vậy, nó còn giúp việc lập trình tốt hơn, bảo dưỡng, và công việc nâng cấp dễ dàng hơn . Ông thấy đó, việc thuê một chuyên gia như tôi có nhiều ý nghĩa, khi tôi thấy một vấn đề trong việc lập trình mà có thể giải quyết theo một mẫu thiết kế nào đó, tôi có thể nói chi tiết về nó cho ông biết.

“Tốt”, các lập trình viên trong công ty nói một cách miễn cưỡng, “ý kiến của anh về mẫu thíêt kế nghe cũng hay đấy. Nhưng chúng ta đã sử dụng phương pháp lập trình hướng đối tượng, điều đó chưa giải quyết được vấn đề à?”

“Không” bạn nói. Thực tế thì nội dung chính của mẫu thiết kế là chúng mở rộng khái niệm lập trình hướng đối tượng.

MỞ RỘNG KHÁI NIỆM LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG?

Chúng ta nhắc lại cuốn sách của Gang Of Four (GOF: Bộ tứ tác giả), tựa đề “Mẫu thiết kế: Sử dụng lại các thành phần trong việc lập trình phần mềm” do nhà xuất bản Addison Wesley, 1995 xuất bản. “Sử dụng lại” là một khía cạnh quan trọng khi làm việc với các mẫu thiết kế, và vì thế nó cũng giải quyết được các vấn đề của lập trình hướng đối tượng(OOP). Tôi sẽ thảo luận trước về OOP trong chương này, và sau đó sẽ cho bạn thấy mối tương quan giữa OOP và mẫu “Strategy” hay còn gọi là mẫu chiến lược

OOP ban đầu được phát triển như là một phương pháp lập trình cho các vấn đề lớn và phức tạp. Ý tưởng chính là đóng gói tất cả chức năng vào trong đối tượng. Nói cách khác, đây là phương pháp chia để trị. Trước khi OOP ra đời, bạn lập trình theo phương pháp thủ tục, bạn có thể chia các tính năng của chương trình thành các thủ tục khác nhau, nhưng điều đó càng ngày càng phức tạp khi kích cỡ chương trình lớn thêm. Khi đó chúng ta cần phải có một phương pháp mới để quản lý các thủ tục một cách dễ dàng, và đó là nguyên nhân ra đời của một phương pháp mới, phương pháp quản lý trên đối tượng.

Ví dụ, bạn hãy nhìn vào nhà bếp và cách hoạt động của nó, thật là cơ man các sự phức tạp. Tủ lạnh phải có các bơm làm mát, bộ phận cảm biến nhiệt, quạt và vân vân. Bếp lò có thể bao gồm nhiều thiết bị như bộ phận nhiệt độ, bộ định thời gian, đèn..Theo cách nhìn nhận này, khi ta xem xét nhà bếp với mọi bộ phận cùng một lúc, ta thấy nhà bếp quá phức tạp.

Nhưng nếu bạn bao bọc từng thành phần, thành các đối tượng, tình hình đã có thể dễ dàng xử lý hơn rất nhiều. Đây là cái tủ lạnh. Đây là cái bếp lò. Đó là cái máy rửa chén và vân vân. Không vấn đề gì lớn cả. Các chi tiết nhỏ làm việc cùng nhau được ta đóng gói thành một đối tượng.

Đó cũng chính là các đối tượng trong lập trình hướng đối tượng. Bạn gộp các chức năng vào trong một đối tượng và chúng dễ dàng được nhận biết, nào là cái tủ lạnh, bếp lò hay máy rửa chén…Và phương pháp lập trình dựa trên các đối tượng được gọi là lập trình hướng đối tượng ( Tất nhiên bạn chẳng nghe ai nói tới lập trình hướng tủ lạnh, hay lập trình hướng bếp lò… )

Ví dụ, trong chương trình của bạn, bạn có một đối tượng tên là “Màn hình”, nó bao gồm các chức năng để hiển thị dữ liệu mà bạn mong muốn. Một đối tượng khác tên “Cơ sở dữ liệu” sẽ làm nhiệm vụ giao tiếp với máy chủ cơ sở dữ liệu và vân vân…Có thể có nhiều phức tạp bên trong từng đối tượng, nhưng khi bạn đóng gói mọi thứ vào đối tượng, mọi thứ đã trở nên dễ dàng hơn rất nhiều. Bạn có thể làm việc với khái niệm đối tượng “Màn hình” và một số chức năng đơn giản của nó như ThiếtLậpHệSốQuét, CanhChỉnhMànHình, ThíêtLậpBộĐệmVideo… và hàng tá chức năng khác. Nó làm cho lập trình trở nên dễ dàng hơn, và đó là lý do tại sao lập trình hướng đối tượng đã trở thành phương pháp mạnh mẽ và phổ biến hơn bao giờ hết.

BỐN KHÁI NIỆM CHÍNH CỦA OOP

OOP bao gồm bốn khái niệm chính là trừu tượng, đóng gói, đa hình và kế thừa. Tôi sẽ thảo luận chúng trong phần sau đây:

Trừu tượng là gì?

Khi bạn làm việc với mẫu thiết kế, bạn sẽ thấy phần lớn đều liên quan đến khái niệm trừu tượng. Trừu tượng là cách thức bạn nghĩ ra để xem xét việc giải quyết một vấn đề nào đó. Trừu tượng không phải là một kỹ thuật lập trình. Thực chất, nó chỉ có nghĩa là bạn phải nhận thức được vấn đề trước khi áp dụng kỹ thuật hướng đối tượng.

Trừu tượng là cách bạn phân chia, cách giải quyết vấn đề thành những phân đoạn nhỏ hơn. Đây là cách thức bạn giải quyết vấn đề bằng cách chia chúng ra thành từng phần nhỏ có thể quản lý được. Nói cách khác, trừu tượng hóa một bài toán, đơn giản chỉ là cách giải quyết bài toán theo kiểu hướng đối tượng. Các dữ liệu cần thiết cho từng đối tượng sẽ trở thành thuộc tính của đối tượng đó, thuộc tính đó có thể là riêng tư cho đối tượng hoặc công cộng cho đối tượng khác sử dụng. Các hành vi mà đối tượng thể hiện trong thế giới thực cũng trở thành một hành động của chúng và được viết thành mã của chương trình.

Khi bạn chắc chắn đã tìm ra hướng giải quyết một bài toán đúng đắn, bạn mới có thể áp dụng các mẫu thiết kế. Thông thường, khi làm việc với mẫu thiết kế bạn sẽ tốn nhiều thời gian hơn cho việc trừu tượng hóa các khái niệm, hơn là làm việc với từng đối tượng cụ thể.

Đóng gói là gì?

Khi bạn đưa tất cả chức năng và dữ liệu vào trong một đối tượng, bạn đã “đóng gói” chúng. Đây là sức mạnh thực sự của việc lập trình hướng đối tượng. Bạn đã gỡ bỏ sự phức tạp của đối tượng khi đóng gói tất cả dữ liệu vào trong đối tượng đó. “Đóng gói” là cách bạn đã đưa hàng tá đường dây điện, ống, cảm biến nhiệt, đèn… vào trong một đối tượng là tủ lạnh.

Khi bạn đóng gói chức năng vào trong một đối tượng, bạn quyết định cách thức mà đối tượng giao tiếp với thế giới bên ngoài. Một cái tủ lạnh có thể một quản lý hàng tá thứ phức tạp bên trong, tuy nhiên người sử dụng chỉ quan tâm là nó có thể làm lạnh thức ăn hay không? Cùng cách thức đó, bạn cũng chỉ định đâu là chức năng, thuộc tính bên trong của tủ lạnh, đâu là chức năng thuộc tính nó giao tiếp với thế giới bên ngoài.

Có một ý tưởng đằng sau việc đóng gói – Bạn che giấu những thứ phức tạp bên trong đối tượng và tạo ra một giao diện đơn giản để đối tượng giao tiếp với phần mã còn lại của bạn.

Mẫu thíêt kế cũng là một trường hợp đặc biệt của sự đóng gói. Bạn phải đóng gói những gì bạn cho là dễ thay đổi nhất Một số mẫu thiết kế xoay quanh ý tưởng là trích ra những phần mã dễ thay đổi nhất hoặc phần cần phải bảo trì nhiều và đóng gói chúng vào một đối tượng riêng để dàng dàng xử lý hơn. Xuyên suốt cuốn sách này, bạn sẽ nhìn thấy nhiều về sự đóng gói và cách thức bất ngờ mà mẫu thiết kế giải quyết các vấn đề thường gặp.

Đa hình là gì?

Một nền tảng khác của lập trình hướng đối tượng là “tính đa hình”. Đó là khả năng khi chúng ta lập trình một chức năng, chức năng đó có thể làm việc với nhiều loại đối tượng khác nhau, tùy thuộc vào kiểu thực sự của đối tượng đó trong thực tế. Ví dụ, bạn có thế viết mã để xử lý tất cả các hình dạng khác nhau như hình tròn, hình chữ nhật, tam giác.. vân vân. Mặc dù chúng có hình dạng khác nhau, chúng có chung một số hành động, ví dụ như chúng có chung chức năng là ĐượcVẽRa.

Sử dụng tính đa hình, bạn có thể viết code để thực hiện nhiều hành động trên loại hình dáng mà bạn định làm việc và sau đó quyết định hình dạng thực tế nào sẽ được sử dụng khi chạy chương trình. Đa hình ( nhiều hình thức) có nghĩa là mã bạn viết ra có thể sử dụng được nhiều kiểu của đối tượng mà bạn không phải viết lại mã.

Sau đây là ví dụ. Bạn tạo ra một lớp (class) Shape với phương thức chung là draw



Sau đó bạn có thể mở rộng một lớp mới, lớp Rectangle, từ lớp Shape, và cho phép nó vẽ một hình chữ nhật như sau:



Bạn muốn vẽ một hình? Không vấn đề gì. Bạn viết một ít mã để tạo một đối tượng tên shape và gọi phương thức draw:



Kết quả khi chạy chương trình:



Muốn vẽ hình chữ nhật sử dụng cùng mã trên? Không vấn đề gì. Đây là sự kỳ diệu của tính “đa hình”, hãy tạo lại biến shape với kiểu rectangle và chạy lại đoạn code trên:



Kết quả là:



Trong trường hợp thứ nhất, bạn đã nạp một đối tượng shape vào biến shape và gọi phương thức draw. Trong trường hợp thứ hai, bạn đã lấy một đối tượng rectangle và nạp nó vào cùng biến shape ( mặc dù là bạn đã khai báo nó như là 1 đối tượng shape) và sau đó gọi phương thức draw.

Vậy là bạn đã cùng sử dụng một biến shape, để lưu giữ một đối tượng shape, một đối tượng rectangle, chương trình vẫn hoạt động vì rectangle được thừa kế từ shape. Đây là cách bạn quyết định kiểu đối tượng nào được nạp vào biến shape khi chạy chương trình và khi đó toàn bộ mã của bạn vẫn không hề thay đổi.

Kế thừa là gì?

Đặc điểm cuối cùng và nổi bật của lập trình hướng đối tượng là tính kế thừa. Là qui trình mà một lớp có thể thừa hưởng toàn bộ phương thức và thuộc tính của một lớp khác. Bạn có thể nhìn thấy sự kế thừa trong ví dụ trước, bắt đầu từ lớp Shape:



Sau đó lớp Rectangle kế thừa từ lớp Shape như sau:



Đa hình thường xuất hiện khi bạn làm việc với mẫu thiết kế bởi vì mẫu thiết kế có xu hướng ủng hộ “kết hợp” hơn là “kế thừa” (Bạn sử dụng “kết hợp” khi đối tượng của bạn chứa đối tượng khác thay vì thừa hưởng từ chúng). Kế thừa chính là mối quan hệ “Is-a” (là một). Ta có thể nói Rectangle “is-a” Shape.

Mẫu thiết kế- lập trình hướng đối tượng thường sử dụng việc “kết hợp” hơn là “kế thừa”. Khi bạn sử dụng “kết hợp”, mã của bạn chứa đựng một đối tượng khác, hơn là thừa hưởng từ chúng. Phương pháp này tỏ ra mềm dẻo, uyển chuyển để thích ứng với nhiều loại đối tượng trong cùng một cách, cùng một đoạn mã. Mẫu thiết kế thường dựa trên tính đa hình.

“Kết hợp” hay “Đa hình”: 

Thử nghiệm đầu tiên khi thiết kế một chiếc xe hơi mới.

Vậy ai đã nói với bạn rằng “Kết hợp” thì tốt hơn “Kế thừa”. Có lẽ để ví dụ sau làm sáng tỏ vấn đề. Các lập trình viên tại MegaGigaCo (phần đầu chương) đều biết về sự “kế thừa” và họ bắt đầu việc thíêt kế xe hơi mới bất chấp lời cảnh báo của bạn cho đến khi bạn có cơ hội nói chuyện với họ. Họ biết họ đang phải thiết kế một loạt xe, vì vậy họ bắt đầu tạo ra một lớp cơ sở tên Vehicle với một phương thức tên là go , phương thức này xuất hiện lên dòng chữ Now I’m driving.



Sau đó họ tao tiếp một lớp mới, như là lớp StreetRacer, sử dụng Vehicle làm lớp cơ sở như sau:



Chương trình tới đây vẫn tốt đẹp. Bạn có thể cho chạy chương trình với lớp StreetRacer như sau:



Kết quả nhận được



Bạn cũng có thể chạy cùng lúc street racer và formula one racer với cùng một cách như sau:



Và kết quả là



“Không tồi”. Giám đốc và ban điều hành nói. “Vậy cần gì phải sử dụng mẫu thiết kế” Họ hỏi mà mắt nhìn chằm chằm vào bạn. Nhưng sau đó họ nhận được một hợp đồng sản xuất máy bay trực thăng Helicopter. Máy bay trực thăng à? Họ lý luận, thì cũng là một phương tiện vận chuyển. Vì vậy họ tạo một lớp Helicopter , được mở rộng ra từ lớp Vehicle :



Nhưng lại xuất hiện một vấn đề. Nếu như bạn sử dụng helicopter trong cùng một điều kiện như xe hơi:



Bạn sẽ nhận được 3 phương tiện như sau: một xe street racer, một xe Formula One, một xe helicopter như sau:



Có gì đó không ổn, Giám đốc nói một cách hồ nghi. Tại sao helicopter (máy bay trực thăng) mà lại đang chạy? Hình như nó đang bay thì mới đúng? Tuy nhiên vấn đề thực sự tồi tệ khi công ty MegaGigaCo nhận được một hợp đồng chế tạo máy bay phản lực Jet, khi đó chúng cũng được kế thừa từ lớp Vehicle :



Khi bạn cho chạy bốn phương tiện trên: một xe street racer, một xe formula one, một máy bay trực thăng helicopter, một máy bay phản lực jet, bạn nhận được kết quả sau:



“Chắc chắn là đã có sai sót ở đây” Vị giám đốc lên tiếng. Máy bay phản lực Jet thì không chạy trên đường, chúng ở trên không. Chúng bay và rất nhanh. Không vấn đề gì, các lập trình viên trong công ty đáp. Chúng tôi sẽ ghi đè(override) lên phương thức go của lớp Helicopter và lớp Jet để sửa chữa chúng. Họ chỉnh sửa lại như sau:



Giờ lớp máy bay trực thăng Helicopter đã bay được.

“OK”. Giám đốc nói “Tuy nhiên vào tuần sau, ban giám đốc họp và quyết định phải chuyển từ “Now I’m flying” sang “Now, I’m flying 200mph” và nhiều sự thay đổi tồi tệ kế tiếp…

Có một vấn đề nảy sinh ở đây, bạn giải thích. Các lập trình viên đã thể hiện một chức năng đơn giản – là lái một chiếc xe hay một chiếc phi cơ – qua nhiều lớp con. Đó có thể chưa là một vấn đề lớn nhưng nếu bạn xử lý các công việc này một cách khá thường xuyên, thì việc phải chỉnh sửa mọi lớp con như vậy sẽ trở thành một vấn đề bảo trì khá nghiêm trọng.

Bạn nói tiếp: có thể là “sự kế thừa” không phải là câu trả lời cho tình huống này. Nơi mà bạn cần phải thay đổi chức năng thường xuyên ở các lớp con. Bạn cần phải chỉnh sửa, bảo trì phần lớn các đoạn mã ở các lớp con khi có sự thay đổi. Và khi có càng nhiều lớp kế thừa liên quan, chúng cũng cần được phải bảo trì khi có sự thay đổi, và khi đó bạn phải cập nhật các phương thức go mãi mãi.

Vấn đề bạn phải giải quyết ở đây là làm sao tránh được việc thay đổi ở các lớp con. Nếu bạn không tránh được điều này, bạn sẽ phải chỉnh sửa rất nhiều file để cập nhật mã của bạn.

Có lẽ có một cách tốt hơn để xử lý vấn đề này hơn là sử dụng sự “kế thừa”.

“Hey” một lập trình viên nói, “Sao anh không sử dụng giao diện interface thay cho sự kế thừa inheritance? Anh có thể cài đặt một giao diện IFly và cho giao diện đó một phương thức go và để cho lớp Helicopter hiện thực giao diện đó như sau:



“Không tốt” bạn nói. Anh vẫn chưa giải quyết ổn thỏa vấn đề. Mỗi lớp và lớp con vẫn phải hiện thực cho riêng nó một giao diện, cũng giống như trường hợp của sự kế thừa. Bởi vì giao diện thì không cài đặt nội dung, bạn vẫn phải viết code cho từng lớp, điều này có nghĩa là chẳng có sử dụng lại được một đoạn code nào cả.

Nắm vững sự thay đổi từ “is-a” sang “has-a”

Mọi việc đều thay đổi. Trong thời buổi thương mại phát triển, mọi thứ thay đổi nhanh chóng. Vì vậy việc lập kế hoạch cho sự thay đổi là rất đáng giá. Nếu bạn có một vấn đề nhỏ cần phải có một giải pháp nhỏ, bạn có thể không cần phải lập một kế hoạch lớn lao cho sự thay đổi. Nhưng nếu bạn làm việc trong một dự án nghiêm túc, với một khối lượng công việc đáng kể, thì đúng là lúc bạn nên nhìn lại về một kế hoạch nghiêm túc khi có sự thay đổi. Các đoạn mã mà bạn viết hôm nay, sẽ phải chỉnh sửa lại để phù hợp với những yêu cầu phát triển trong tương lai. Hầu hết các nhà phát triển không chú ý tới vấn đề này, và sau đó họ luôn luôn hối tiếc. Vậy câu hỏi đặt ra là dự án phải lớn tới đâu, để bạn quan tâm đến vấn đề thay đổi. Đó là sự đánh giá của riêng bạn, một phần của nghệ thuật lập trình. Bằng cách nắm vững phương pháp xử lý sự thay đổi, bạn sẽ biết rõ hơn khi nào thì nên thực hiện nó.

Có một dấu hiệu đáng chú ý ở đây: Phân chia các đoạn mã dễ thay đổi trong chương trình riêng biệt với phần còn lại. Và làm cho chúng càng độc lập càng tốt cho sự bảo trì nâng cấp. Bạn cũng nên cố gắng tái sử dụng những phần này càng nhiều càng tốt.

Điều này có nghĩa là nếu ứng dụng của bạn có một phần bị thay đổi, bạn có thể đem nó riêng ra, sau đó thay đổi từng phần riêng biệt một cách dễ dàng trong khi vẫn không bị ảnh hưởng bởi những tác dụng phụ của nó.

Và đây là cách để lập kế hoạch cho sự thay đổi, và vì sao “kế thừa” lại không thể giải quyết tốt các sự thay đổi này. Với sự kế thừa, lớp cơ sở và các lớp con có một mối quan hệ “is-a”. Ví dụ , lớp Helicopter có quan hệ “is-a” với lớp Vehicle, điều này có nghĩa Helicopter thừa kế mọi thứ từ Vehicle, và nếu bạn phải chỉnh sửa các phương thức này, bạn sẽ gặp phải vấn đề bảo trì nó trong tương lai. Lớp cơ sở xử lý phương thức theo một cách, và lớp kế thừa lại thay đổi nó, và lớp kế tiếp lại thay đổi nó thêm một lần nữa. Và cuối cùng bạn có một lô một lốc các biến thể của cùng 1 phương thức qua các lớp con.

Mặc khác, nếu bạn có thể trích những đoạn code dễ thay đổi và đóng gói chúng vào đối tượng, bạn có thể sử dụng các đối tượng này khi cần. Nhiệm vụ mới là xử lý trên các đối tượng này. Bạn đã không để việc xử lý lây lan qua các lớp con. Làm như vậy sẽ cho phép bạn chỉnh sửa mã của bạn bằng việc tạo ra “sự kết hợp” composites các đối tượng. Với composites “kết hợp” này, bạn có thể dễ dàng chọn ra và sử dụng đối tượng cần thiết. Một quan hệ “has-a” mới được tạo ra. Một chiếc xe street racer sẽ có một “has-a” cách để di chuyển, đã được đóng gói vào đối tượng. Một máy bay trực thăng sẽ có một cách riêng để di chuyển, và cũng được đóng gói vào đối tượng. Từng đối tượng sẽ thực hiện hành động của riêng nó.

Một đối tượng, một nhiệm vụ thường là có ý nghĩa hơn là việc kế thừa các lớp, và tạo ra hàng tá các lớp con. Nói cách khác, chúng ta sắp xếp lại dựa trên nhiệm vụ của lớp, chứ không phải trên sự kế thừa.

Sử dụng kế thừa sẽ tự động cài đặt mọi thuộc tính một cách nghiêm ngặt, bao gồm cả quan hệ “is-a”, là thứ gây ra các rắc rối khi bảo trì cũng như khi mở rộng. Nếu bạn đặt kế hoạch cho sự thay đổi, bạn nên nghĩ tới quan hệ “has-a” , nơi mà mã của bạn bao gồm nhiều đối tượng mà có thể dễ dàng cập nhật khi có sự thay đổi xảy ra.

Gợi ý: Khi có kế hoạch cho sự thay đổi, hãy thay thế quan hệ “is-a” thành quan hệ “has-a” và đặt các đoạn mã dễ thay đổi vào các đối tượng trong ứng dụng này hơn là kế thừa chúng.

KẾ HOẠCH CHỈNH SỬA

Làm thế nào mà ý tưởng phân chia các đoạn mã dễ thay đổi sẽ hoạt động trong ví dụ Vehicle/StreetRacer/Helicopter đã nhắc trước đây. Theo ý kiến của giám đốc điều hành, phần được thay đổi nhiều nhất là phương thức go , do đó chúng ta sẽ tách nó ra. Trong thuật ngữ về thiết kế mẫu, mỗi cách hiện thực một phương thức được gọi là 1 thuật toán(algorithm) hay có thể gọi là 1 chiến lược (strategy). Vì vậy bạn có thể tạo một tập hợp các giải thuật để sử dụng cho các biến của bạn như StreetRacer, FormulaOne, Helicopter, và Jet . Làm như thế để phân chia các đoạn mã dễ thay đổi vào trong thuật toán. Từng thuật toán sẽ hoàn thành 1 nhiệm vụ.

Cách tạo thuật toán

Để chắc chắn mọi thuật toán đều hiện thực cùng một phương thức (phương thức go ở trên). Bạn cần phải tạo một giao diện interface cho nó (ND: Interface là một khái niệm rất hay trong OOP, mà khi có dịp chúng ta sẽ thảo luận về nó) như sau:



Giao diện GoAlgorithm có một phương thức duy nhất go. Để chắc chắn rằng mọi thuận toán có thể được sử dụng bởi bất kì lớp Vehicle nào, ta cần phải hiện thực interface này. Thuật toán đầu tiên GoByDrivingAlgorithm , sẽ hiển thị văn bản “Now I’m driving”. Và đây là mã của thuật toán:



Ngoài ra, thuật toán GoByFlying, sẽ hiển thị văn bản Now I’m flying. Mã như sau:



Và cuối cùng, thuật toán GoByFlyingFast, sẽ được sử dụng bởi máy bay phản lực, hiển thị dòng văn bản Now I’m flying fast



Tuyệt vời. Bạn vừa phân chia các thuật toán của mình ra khỏi phần mã. Bạn đang thực hiện thao tác thực thi quan hệ “has-a” hơn là quan hệ “is-a”. Bây giờ bạn đã có thể đưa các thuật toán này vào sử dụng.

SỬ DỤNG THUẬT TOÁN

Bạn đang có một số thuật toán, bạn có thể tạo các đối tượng và sử dụng quan hệ “has-a” thay cho “is-a”. Sau khi bạn tạo một đối tượng từ một thuật toán, bạn cần phải lưu trữ đối tượng ở đâu đó. Vì vậy hãy thêm vào lớp cơ sở Vehicle, một phương thức mới SetGoAlgorithm. Phương thức này sẽ lưu trữ thuật toán mà bạn muốn sử dụng. Mã như sau:



Bây giờ khi bạn muốn sử dụng một thuật toán cụ thể nào đó ở lớp kế thừa, tất cả việc cần làm là gọi phương thức setGoAlgorithm với một đối tượng thuật toán đúng, theo cách như sau:



Phương thức go của lớp Vehicle có chút thay đổi. Trước đây là:



Tuy nhiên, bây giờ nó phải gọi phương thức đã được định nghĩa ở các lớp thuật toán. Mã mới như sau:



Bây giờ thì tất cả những gì phải làm là chọn đúng thuật toán mà bạn muốn sử dụng cho phương tiện nào đó. Ví dụ với street racer sẽ là thuật toán GoByDrivingAlgorithm:



Xe Formula One cũng sử dụng cùng một thuật toán trên, mã như sau:



Nhưng máy bay trực thăng helicopter sẽ sử dụng thuật toán GoByFlyingAlgorithm:



Và máy bay phản lực Jet sẽ sử dụng thuật toán GoByFlyingFastAlgorithm



OK. Đã đến lúc chạy thử chương trình. Biên dịch và chạy thử chương trình như sau:



Kết quả:



Kết quả đúng như mong đợi. Tuy nhiên bây giờ bạn đã sử dụng mối quan hệ “has-a” thay vì quan hệ kế thừa “is-a”. Từ lúc này bạn có thể sử dụng các thuật toán xuyên suốt chương trình, bất cứ đâu, vì nó đã không còn nằm trong các lớp StreetRacer hay Helicopter nữa.

Kỹ thuật này thay thế cho cách tạo các lớp con và sử dụng kế thừa. Nếu bạn sử dụng một quan hệ kế thừa “is-a”, bạn sẽ bắt đầu sự rắc rối cho việc kiểm soát được các phương thức trong lớp cơ sở và các lớp con – trong ví dụ là bạn phải nạp đè lên phương thức go cho lớp Helicopter và Jet. Nếu bạn sử dụng mô hình “has-a”, bạn có thể tạo ra một dòng họ các thuật toán một cách rõ ràng, và sau đó bạn chọn một thuật toán thích hợp để sử dụng.

Theo cách này, bạn đã có thể khắc phục được vấn đề mà sự kế thừa đã gây ra cho hầu hết các lập trình viên: nếu bạn phải giải quyết một chức năng cụ thể nào đó qua nhiều thế hệ của một lớp, và chức năng này liên tục thay đổi, bạn sẽ phải chỉnh sửa rất nhiều mã của mình. Mặt khác, khi bạn tập trung chức năng đó vào một thuật toán duy nhất, việc thay đổi nó sẽ dễ dàng hơn rất nhiều.

Quay lại ví dụ trên, khi ban giám đốc muốn thay đổi từ “Now I’m flying” sang “Now I’m flying at 20 mph”. Đơn giản, bạn chỉ cần chỉnh sửa thuật toán GoByFlying:



Và bây giờ tất cả mã của bạn đã tự động được cập nhật, bạn không cần thiết phải đi tìm và chỉnh sửa từng lớp con như trước nữa. Theo cách này, bạn đã tập trung sự xử lý một chức năng vào một đối tượng thuật toán duy nhất, bạn sẽ dễ dàng quản lý đối tượng này trong trường hợp yêu cầu chức năng bị thay đổi.

Chọn lựa thuật toán khi thực thi chương trình

“Đợi một chút,” Giám đốc MegaGigaCo nói. “Có việc xảy ra, máy bay phản lực không chỉ bay nhanh, đầu tiên nó chạy trên đường băng một lúc, và khi đáp xuống mặt đất, nó lại tiếp tục chạy trên đường băng nữa. Vì vậy chúng ta phải chỉnh sửa chức năng cho nó lại : đầu tiên là chạy trên đường băng, rồi bay, rồi chạy tiếp?”

“Đó là về mặt lý thuyết,” các lập trình viên rên rỉ “Nhưng điều đó làm chúng ta phải viết thêm nhiều đoạn mã nữa”

“Không sao cả” Bạn nói. “Đó là một trong những điểm kỳ diệu của việc sử dụng một đối tượng thuật toán bên ngoài. Bạn có thể thay đổi nó khi bạn thực thi chương trình”

Khi bạn viết mã cho một chức năng trong một lớp, bạn không thể thay đổi nó khi thực thi chương trình. Tuy nhiên khi bạn sử dụng một đối tượng thuật toán bên ngoài với mối quan hệ “has-a”, bạn dễ dàng thay đổi chức năng đó lúc chương trình hoạt động. Nói cách khác một quan hệ “has-a” cho phép bạn dễ dàng thay đổi hơn một quan hệ “is-a” đặc biệt khi chương trình đang hoạt động.

Và đây là ví dụ cho việc sử dụng linh hoạt các thuật toán, cũng như việc thay đổi nó khi chương trình đang chạy. Bạn có thể tạo một máy ban phản lực, có thể chạy trên đường băng với thuật toán GoByDrivingAlgorithm, như mã sau:



Để máy bay phản lực chạy được trên đường băng, bạn gọi phương thức go:



Bạn có thể cài đặt thuật toán mới setGoAlgorithm cho máy bay phản lực, để thay đổi phương thức go một cách linh động, và sau đó gọi lại phương thức go để thấy sự khác biệt.



Và đây là kết quả: máy bay phản lực, chạy trên đường băng, rồi bay, rồi chạy trên đường, không vấn đề gì cả.



Bạn thấy đó, việc chuyển đổi một thuật toán lúc thực thi chương trình rất dễ dàng. Nói cách khác, nếu bạn để việc xử lý thuật toán vào nội tại một lớp, bạn sẽ không thể thay đổi nó lúc chạy chương trình. Nhưng khi bạn cài đặt một chiến lược “Strategy”, bạn sẽ dễ dàng thay đổi nó khi chạy chương trình. Tất cả những điều trên mang chúng ta đến một mẫu thiết kế “Strategy”, hay được gọi là mẫu “chiến lược”.

MẪU “STRATEGY” – Mẫu chiến lược

Mẫu chiến lược là mẫu thiết kế chúng ta học đầu tiên trong quyển sách này, và thực tế là chúng ta đã cùng nhau đi xuyên suốt qua chương này để hiểu về nó. Ý nghĩa thực sự của mẫu chiến lược là bạn tách rời phần xử lý một chức năng cụ thể ra khỏi đối tượng của bạn. Sau đó tạo ra một tập hợp các thuật toán để xử lý chức năng đó và lựa chọn thuật toán nào mà bạn thấy đúng đắn nhất khi thực thi chương trình. Mẫu thiết kế này thường được sử dụng để thay thế cho sự kế thừa, khi bạn muốn chấm dứt việc theo dõi và chỉnh sửa một chức năng qua nhiều lớp con.
Chúng ta có thể nhìn thấy vấn đề tổng quát như sau. Đầu tiên mọi việc đều ổn, bạn có một đối tượng, một chức năng



Một thời gian sau đó, do yêu cầu đặc biệt, bạn cần có thêm một lớp mới, bạn kế thừa lớp cũ, và ghi đè lên phương thức đã được thừa hưởng. Bạn đang dàn trải việc xử lý chức năng qua nhiều lớp con như hình:



Mẫu “Strategy”, mẫu chiến lược nói rằng: bạn cần phải tách những phần dễ thay đổi và đóng gói chúng vào các đối tượng và bạn có thể sử dụng các đối tượng này khi cần. Bây giờ bạn có thể chỉnh sửa mã của mình thông qua việc tạo sự “kết hợp” các đối tượng. Khi chương trình thực thi, bạn chỉ cần sử dụng đúng đối tượng mà bạn cần. Như hình sau:



Cuốn sách GoF đã nói rằng mẫu Strategy, mẫu chiến lược như sau: “Định nghĩa một tập hợp các thuật toán, đóng gói chúng thành từng loại một, và giúp chúng có thể hoán đổi cho nhau. Mẫu chiến lược giúp các thuật toán độc lập hơn khi được sử dụng.

Mẫu chiến lược chỉ ra rằng, đôi khi, nó sẽ được áp dụng tốt cho mục đích hướng chức năng. Và nó đặc biệt quan trọng khi bạn muốn thực hiện công việc nâng cấp, bảo trì cho các đoạn mã dễ thay đổi của bạn một cách riêng biệt với toàn bộ mã của chương trình, hoặc khi bạn muốn thay đổi thuật toán sử dụng khi chương trình được thực thi.

Gợi ý: Bạn nên sử dụng mẫu Strategy khi có những tình huống sau:
  • Bạn có một đoạn mã dễ thay đổi, và bạn tách chúng ra khỏi chương trình chính để dễ dàng bảo trì
  • Bạn muốn tránh sự rắc rối, khi phải hiện thực một chức năng nào đó qua quá nhiều lớp con.
  • Bạn muốn thay đổi thuật toán sử dụng khi chạy chương trình

Chúng tôi không cung cấp mã nguồn cho bạn. Thay vào đó, bạn hãy làm quen với việc suy nghĩ, và khi ý tưởng tới đó là lúc bạn đã nắm vững mẫu thiết kế này. Điều này cũng giống như một công việc phải thực hiện cho mẫu “Chiến lược”.

Việc hiểu biết cách thức làm việc của các mẫu thiết kế khác nhau cũng giúp bạn có cơ hội thảo luận với đồng nghiệp khác. Hầu hết các lập trình viên chuyên nghiệp đều biết một số mẫu thiết kế cơ bản. Và khi mọi người trong nhóm của bạn nói tới mẫu chiến lược, mọi người gật đầu ra vẻ hiểu biết, thì bạn cũng có thể làm như vậy.