Dùng Maven với project nhiều module

Nếu bạn đã dùng m2eclípe (Maven plugin cho Eclipse) thì sẽ thấy nó khá bất tiện khi làm việc với project nhiều module. Vì vậy, trong trường hợp này, chạy Maven từ dòng lệnh là hợp lí hơn cả. Bài viết này sẽ giới thiệu một số lệnh Maven phù hợp cho tình huống này (chính xác hơn là các option của lệnh mvn). Những option này được thêm vào từ Maven 2.1, bao gồm:

  • –projects: Làm việc với một vài module nhất định, chứ không phải toàn bộ project
  • –also-make: Build các project mà module cần build (được chỉ ra bằng –projects) phụ thuộc
  • –also-make-dependents: Build các project phụ thuộc vào module cần build (được chỉ ra bằng –projects).

Điểm tiện dụng của các option này là ta sẽ làm việc từ thư mục gốc của project, không cần phải chuyển vào từng module.

Giả sử ta có project A với các module như sau:

A
|_B
|_C -> B

(module C phụ thuộc module B).

Nếu bạn chưa install module B, các lệnh compile, test với C sẽ báo lỗi. Để test (hoặc compile) C một cách suôn sẻ, bạn dùng lệnh sau:

mvn –projects C test –also-make

Tất nhiên nếu module B đã ổn định, thì bạn nên install nó trước, bởi vì lệnh trên sẽ chạy test với cả module B. Để install module B, bạn dùng lệnh sau:

mvn –projects B install

Bây giờ khi làm việc với C, bạn không cần –also-make nữa. Không rõ vì sao mà ngay cả khi install B, thì HM vẫn không thể compile C được, vì java không resolve được các dependency(!). Thêm một lí do để dùng Maven từ dòng lệnh.

Và nếu bạn chính sửa gì đó ở module B, và muốn install lại, cùng với module C (để test, để kiểm tra xem bạn có break API của B không, v.v), bạn dùng lệnh sau:

mvn –project B install –also-make-dependents

Cuối cùng, nếu bạn muốn làm việc với nhiều module, chỉ cần phân tách giữa các module với dấu “,”:

mvn –project B,C

Gọn đẹp hơn dùng Eclipse, đúng không nào :D.

Advertisements

Event trong C#

Khai báo

Dưới đây là các bước tạo một event. Xem thêm chi tiết ở đây.

public class CustomEventArgs: EventArgs {…} 
public delegate void CustomEventHandler(object sender, CustomEventArgs a); 
public event CustomEventHandler RaiseCustomEvent;

Nếu bạn dùng Generics, bạn không cần phải tự tạo delegate:

public class CustomEventArgs: EventArgs {…} 
public event EventHandler<CustomEventArgs> RaiseCustomEvent;

Nếu bạn không cần một class EventArgs riêng, bạn không phải tạo CustomEventArgs:

public event EventHandler<EventArgs> RaiseCustomEvent;

Raise event

Cần chú ý là bạn nên tách phần code để raise event ra khỏi phần code kiểm tra điều kiện và tạo EventArgs, vì bạn sẽ thấy tiếp theo đây, raise event đòi hỏi một số thao tác linh tinh có thể làm code của bạn rối rắm hơn:

void OnRaiseCustomEvent(CustomEventArgs e) { 
  if (RaiseCustomEvent != null) { 
    RaiseCustomEvent(this, e); 
  } 
}

Tại sao phải kiểm tra null? Vì nếu RaiseCustomEvent là null, tức là chưa có ai subscribe, lệnh raise event sẽ throw Exception.

Vẫn còn một vấn đề nữa. Trong một chương trình multithread, rất có khả năng một object nào đó sẽ hủy subscribe trước khi raise event, nhưng sau khi phép kiểm tra null xảy ra. Nếu việc hủy subscribe này làm cho event thành null, bạn hiểu điều gì sẽ xảy ra. Do đó, bạn phải tạo một bản copy cho event cần raise:

var handler = RaiseCustomEvent; 
if (handler != null) …

Bây giờ thì dù RaiseCustomEvent có thay đổi thế nào, handler event cũng không bị ảnh hưởng. Tại sao một phép copy lại làm được điều đó? C# copy reference chứ không copy value mà? Nếu bạn quan tâm, xin mời đọc tiếp. Nếu không, hãy an tâm như thế là đủ.

Rườm rà? Bạn không thích lặp lại đoạn mã này với tất cả các event, chẳng hạn khi bạn có quá nhiều event (Custom1, Custom2, v.v)? Có thể dùng extension method để làm cho code gọn đẹp hơn nữa, nhưng hẹn bài khác :D.

Vì sao vừa có event, vừa có delegate

Để ý rằng, với delegate, chúng ta cũng có các toán tử +=, –=, và cũng invoke được các delegate đã add, tương tự như event. Vậy tại sao chúng ta lại phải tạo event mà không dùng luôn delegate? Bởi vì nếu dùng delegate, sẽ có hai tình huống không mong đợi sau:

  • Subscriber override các subscriber khác bằng toán tử =. Chẳng hạn, một cách vô tình, lệnh subscribe được viết là … = … Kết quả là tất cả các delegate đã add vào CustomEventHandler trước đó sẽ bị xóa.
  • Subscriber có thể raise event bằng cách gọi delegate. Trong khi việc này lẽ ra chỉ nên được thực hiện bởi publisher.

Event thực chất được xây dựng dựa trên delegate, nhưng không cho phép các tình huống trên xảy ra. Khi khai báo một event, trình dịch sẽ ngầm tạo cho chúng ta một biến delegate private trong chính class chứa event. Lưu ý là event là một khái niệm ở mức CIL, vì vậy nó không đơn thuần chỉ là một wrapper cho delegate. Bạn có thể tham khảo đoạn mã dưới đây (từ quyển Essential C# 4.0), mô tả một cách sơ lược những gì trình dịch sẽ làm.

public delegate void CustomEventHandler(object sender, CustomEventArgs a);

private CustomEventHandler customEventHandler;

public void add_CustomEventHandler(CustomEventHandler handler) { 
  System.Delegate.Combine(customEventHandler, handler); 
}

public void remove_CustomEventHandler(CustomEventHandler handler) { 
  System.Delegate.Remove(customEventHandler, handler); 
}

public event CustomEventHandler customEventHandler { 
  add { add_customEventHandler(value) } 
  remove { remove_customEventHandler(value) } 
}

Và delegate là một immutable type

Để dễ hiểu, hãy xét kiểu string, cũng immutable, và cũng hỗ trợ toán tử += như delegate:

string s1 = "old"; 
string s2 = s1; 
s1 += “new”;

s2 vẫn sẽ là “old”, vì khi gán s1 += “new”, một chuỗi mới sẽ được tạo ra với giá trị là “oldnew”, và s1 sẽ trỏ sang chuỗi mới đó. Chuỗi s2, tất nhiên vẫn trỏ tới chuỗi cũ. Bây giờ xem lại phần copy trong raise event, bạn sẽ thấy vấn đề hoàn toàn sáng tỏ.

(Xóa phần immutable object, vì HM muốn viết nó thành một bài riêng)

Linh tinh về dynamic trong C#

Dưới đây là vài điểm đáng chú ý liên quan đến kiểu dynamic trong C# được trình bày trong buổi thảo luận với hai developer của Microsoft, theo trình tự thời gian. Ở đây không nói đến hiện thực bên trong, vì HM không nắm rõ.

  • Lí do khiến dynamic được đưa vào C# là để làm việc với COM (chú ý là cả COM cũng phải được “tân trang” thì mới tận dụng được dynamic), và các ngôn ngữ dynamic như Ruby, Python được đơn giản hơn. Các ứng dụng phát sinh thêm như ở đây không phải là lí do chính thức.
  • dynamic là một built-in type như int và float, chứ không phải một syntax mới. Các biến dynamic cũng là object. Với các biến kiểu dynamic, trình biên dịch C# sẽ không thực hiện kiểm tra kiểu lúc biên dịch mà chỉ đóng gói các thông tin cần thiết để phân giải lúc runtime lời gọi các method của dynamic object. Lí do đằng sau thiết kế này là Microsoft không muốn thay đổi tính chất static typing của C#, và cũng không muốn lập trình viên dùng dynamic một cách không có chủ ý.
  • dynamic là một contextual keyword (bạn thử khai báo int dynamic = 1; xem).
  • dynamic dùng các dịch vụ của Dynamic Language Runtime (DLR). DLR là thư viện (chứ không phải runtime như CLR, mặc dù tên gọi có từ “runtime”) cung cấp các dịch vụ cần thiết khi xây dựng các ngôn ngữ dynamic trên nền .NET. Có thể coi dynamic là syntactic sugar để sử dụng DLR trong C#. Khi các ngôn ngữ dynamic trên nền .NET sử dụng DLR, việc cộng tác với nhau sẽ đơn giản hơn.
  • Dynamic object expose các method, property vào lúc runtime, nói cách khác, nó có thể tự dispatch thông điệp gửi đến nó. Khi gán một object cho biến kiểu dynamic, trình dịch wrap các object này vào các dynamic object.
  • Lập trình viên C# có thể lựa chọn dùng hoặc không dùng dynamic khi làm việc với COM và các ngôn ngữ dynamic. Điều này phụ thuộc vào sở thích.

Tham khảo blog của hai lập trình viên này ở đâyở đây nếu muốn biết sâu hơn về dynamic (có thể dùng thay thuốc ngủ :D).