Đón đọc các bài viết kĩ thuật đặc sắc tại
F-Complex, ngày 30 tết
Đã 2 giờ sáng, đội dự án XXX vẫn ngồi miệt mài làm việc. Theo yêu cầu của PM, toàn đội phải ON để hoàn thành sản phẩm cho đợt deliver ngày mai.
Ngày cuối năm, toàn bộ khu nhà không một bóng người, chỉ có tiếng gió rít và tiếng cửa sổ kẽo kẹt nghe đến rợn người.
Chàng coder K gục đầu bên bàn phím, source code đã chạy UT và CT tuy nhiên tester report quá nhiều bug. Dự án áp dụng đủ các process Waterfall, Scrum, Agile rồi cả CMMI nhưng bug vẫn hoàn bug. Chàng cũng không biết vì nguyên nhân từ đâu.
Bỗng trời chuyển mưa , bên ngoài không gian tối sầm lại và gió nổi vùn vụt rờn rợn trong những khóm mây. Lẫn trong tiếng gió rít , chàng bổng nghe văng vẳng có tiếng nói âm vang
Fix … bug cho ta…
Dưới ánh trăng mờ chàng nhìn thấy một bóng trắng vật vờ tiến lại gần, hai con mắt trắng dã, mặt dính đầy đất đá.
Anh vốn là PM của dự án này, các chú thiết kế phần mềm không theo design pattern, dẫn tới sản phẩm khó maintaince, quá nhiều bug. Deliver xong anh phải hứng cả rổ gạch đá từ khách hàng. Tại khoang làm việc này đây, anh đã chết vì OT kiệt sức, hôm nay về báo oán
Chàng coder hoảng sợ quỳ lạy rối rít
Design Pattern là gì anh, xin anh chỉ cho mấy chiêu design pattern để em thoát cảnh OT
Bóng ma há miệng đỏ như máu, cười the thé
Developer mà không biết design pattern thì OT là đúng roài
Software quan trọng là phải có tính bảo trì. Hãy tưởng tượng, software có 100 module có liên kết tới nhau, nếu sai sót hay cần thay đổi ở 1 module, không lẽ phải sửa toàn bộ module còn lại ?? Design Pattern ra đời để tránh điều đó
Design Pattern là một kỹ thuật trong lập trình hướng đối tượng, được các nhà thiết kế nghiên cứu thử nghiệm và đúc kết lại thành những mẫu hình chuẩn, giúp chú có thể giải quyết bài toán phần mềm một cách tối ưu nhất
Trong Design Pattern có 3 nhóm bao gồm:
- Creational Pattern (nhóm khởi tạo) gồm: Abstract Factory, Factory Method, Singleton, Builder, Prototype. Dùng trong việc khởi tạo đối tượng, chú hay sử dụng từ khóa new để tạo đối tượng, nhóm Creational Pattern sẽ sử dụng một số thủ thuật để khởi tạo đối tượng mà không cần dùng đến từ khóa này
- Structural Pattern (nhóm cấu trúc) gồm: Adapter, Bridge, Composite, Decorator, Facade, Proxy và Flyweight… Nó dùng để thiết lập, định nghĩa quan hệ giữa các đối tượng.
-
Behavioral Pattern gồm: Interpreter, Template Method, Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy và Visitor. Nhóm này dùng trong thực hiện các hành vi của đối tượng.
Dưới đây là một số mẫu hình thiết kế thông dụng. Những mẫu hình này, nếu tra Google thì không thiếu, nhưng anh muốn mô tả bằng phong cách độc đáo để chú nắm được dễ dàng
Singleton Pattern
Đây là pattern thuộc nhóm Creational Pattern được sử dụng để khởi tạo đối tượng, khá phổ biến
Hãy phân tích đoạn code mà chú đang code
private void callGhost()
{
Ghost maTroi = new Ghost ("Ma trơi");
Ghost maXo = new Ghost ("Ma xó");
Ghost maMatThoi = new Ghost ("Ma men");
maTroi.doXXX();
///..
}
Mỗi khi hàm callGhost được gọi, các instance của Ghost lại được khởi tạo, nhiều người sẽ lầm tưởng rằng sau khi hàm kết thúc thì các object trên sẽ được xóa ngay (vì đó là local variable)
Tuy nhiên vấn đề không đơn giản như vậy, các ngôn ngữ như Java hay C# dùng Garbage Collection (GC) để thu dọn các vùng nhớ đã được cấp phát. Các instance object trên được tạo ra sẽ lưu giữ trên vùng nhớ Head (khác với loại primitive variable lưu trên vùng Stack). Sau khi hàm chạy xong, chúng sẽ được eligible to be collected . Còn khi nào thực sự được xóa thì do GC xử lý. Developer không thể can thiệp. Điều này khác với ngôn ngữ C/C++ khi developer phải tự quản lý bộ nhớ
Như vậy, việc tạo nhiều instance như trên sẽ dẫn tới chiếm dụng quá nhiều bộ nhớ → tiềm ẩn lỗi memory leak, đặc biệt trong ứng dụng đa luồng, khi có nhiều thread cùng call tới hàm callGhost.
Mẫu hình Singleton đảm bảo tạo duy nhất một đối tượng cho một lớp cụ thể
public class Ghost(){
private static Ghost singleGhost;
private String name;
Ghost(String name)
{
this.name = name;
}
public static Ghost getInstance(String name) {
if (singleGhost == null) {
singleGhost = new Ghost(name);
}
return singleGhost;
}
}
Như vậy duy nhất một đối tượng Ghost được tạo ra ở cùng 1 thời điểm
Và hàm CallGhost chỉ cần gọi
Ghost maTroi = Ghost.getInstance(“Matroi”);
Tuy nhiên, trong trường hợp ứng dụng có xử lý multi-thread, nếu có nhiều thread cùng call tới hàm getInstance cùng 1 lúc và thỏa mãn điều kiện if thì dẫn tới việc nhiều thread cùng khởi tạo instance Ghost
Để giải quyết điều này cần thêm vào từ khóa synchronized ( dùng trong Java)
public static synchronized Ghost getInstance(String name)
Từ khóa synchronized sẽ khóa việc truy cập vào hàm getInstance, trong khi hàm getInstance được chạy. Bất cứ luồng nào muốn gọi hàm getInstance, đều phải đợi hàm này hoạt động xong. Sử dụng kỹ thuật đồng bộ hóa synchronized là cách dễ nhất để thực thi việc đơn luồng trong gọi hàm, và kỹ thuật này đã giải quyết được vấn đề đa luồng
Tương tự trong C# thì sử dụng từ khóa lock ( tính năng tương tự như synchronized trong Java)
Strategy Pattern
Đây là pattern thuộc nhóm Behavioral Pattern được sử dụng để lựa chọn hành vi đối với đối tượng
Mẫu hình này được sử dụng nhiều nhất, yêu cầu mọi developer cần phải nắm vững.
Hãy xem xét bài toán sau
Object NgocTrinh, ThuyTop và LinhMuu đều có khả năng showHang, nhưng behavior khác nhau
public class NgocTrinh {
public void showHang() {
// Show da trắng
}
}
public class ThuyTop() {
public void showHang() {
// Show ngực khủng
}
}
public class LinhMuu () {
public void showHang () {
// Show chân dài
}
}
Object HoangKieu call method showHang của object NgocTrinh để thư giãn
class HoangKieu {
void checkHang(){
(new NgocTrinh()).showHang();
}
}
Object HoangKieu sau 2 tháng call method của NgocTrinh chán muốn chuyển sang method showHang của ThuyTop và LinhMuu để thay đổi khẩu vị
Cách thiết kế trên sẽ gặp vấn đề trong trường hợp này, HoangKieu buộc phải sửa đổi lại method checkHang để chuyển sang ThuyTop và LinhMuu, dẫn tới phải test lại method → thiếu tính maintaince
Mẫu hình Strategy Pattern sẽ giải quyết được bài toán trên
Code như sau
public interface IShow {
public void showHang();
}
public class NgocTrinh implements IShow {
public void showHang() {
//show Da trắng
}
}
public class ThuyTop implements IShow {
public void showHang() {
//show Ngực Khủng
}
}
public class LinhMuu implements IShow {
public void showHang() {
//show Chan dai
}
}
Class DaiLyMoiGioi sẽ cung cấp cho HoangKieu phương thức lựa chọn kiểu ShowHang theo ý muốn
public class DaiLyMoiGioi {
private IShow strategy;
//this can be set at runtime by the application preferences
public void setShowHangStrategy(IShow strategy) {
this.strategy = strategy;
}
//use the strategy
public void checkHang() {
strategy.showHang();
}
}
Như vậy HoangKieu chỉ việc lựa chọn thông qua DaiLyMoiGioi chứ không cần trực tiếp liên lạc tới NgocTrinh hay ThuyTop
public class HoangKieu {
public static void main(String[] args) {
DaiLyMoiGioi daily = new DaiLyMoiGioi ();
//we could assume context is already set by preferences
daily.setShowHangStrategy (new ThuyTop());
daily.checkHang();
}
}
Hoàng Kiều chỉ cần thay thế tham số truyền vào ở setShowHangStrategy là hoàn toàn có thể lựa chọn phương thức showHang với behavior khác nhau
Số lượng mẫu hình design pattern rất nhiều, nhưng trời sắp sáng rồi, ta phải về chầu Diêm Vương đây. Ta khuyên ngươi nên bỏ công đọc cuốn Head First Design Pattern để hiểu sâu hơn về thiết kế phần mềm
Nói rồi hồn ma PM biến mất trong đám khói sương mờ ảo, để lại chàng coder với bản thiết kế lộn xộn đầy bug.
Thế rồi từ đó, hàng năm cứ đến đêm 30 tết, Fsofter lại thấy 2 hồn ma áo trắng đầu tóc bù xù, mắt trắng dã vật vờ đi lại trong khoang làm việc X của tòa nhà F-Complex.
Người ta đồn rằng đó chính là oan hồn của PM và developer dự án X đã chết vì OT quá nhiều khi fix bug.