CÔNG VIỆC CỦA CHÚNG TÔI

+ Đào tạo & hỗ trợ thi & Cấp chứng chỉ Ứng dụng CNTT cơ bản, nâng cao
+ Cung cấp dịch vụ Thiết kế - Lập trình web
+ Các giải pháp triển khai - quản trị hệ thống mạng doanh nghiệp
+ ĐÀO TẠO CHUYÊN NGHIỆP: LẬP TRÌNH (C, .Net, ASP.net, PHP, Thiết kế Đồ họa
Hiển thị các bài đăng có nhãn C#. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn C#. Hiển thị tất cả bài đăng

Kiểu dữ liệu Generics và Iterators trong C#

Generics là các cấu trúc dữ liệu mà cho phép bạn dùng lại các mã giống nhau cho các kiểu khác nhau giống như class, interface, Generics được dử dụng tốt nhất khi làm việc với các mảng và các tập hợp liệt kê. Iterators là các khối của đoạn mã mà có thể được lặp lại thông qua các giá trị của tập hợp.
 1.      Giới thiệu:
Stack stack = new Stack();
for (int i = 0; i < 10; i++) stack.Push(i);
foreach (int i in stack) Console.Write("{0} ", i);
Output: 0 1 2 3 4 5 6 7 8 9
            Trong đoạn code trên, bạn có thể thấy một kiểu khai báo mới: "Stack", đó chính là generics. Nhờ nó, trình dịch sẽ buộc kiểu của dữ liệu bạn đưa vào stack qua hàm Push() phải phù hợp kiểu đã định (ở đây là int). Cái này giống với template trong STL. Generics có thể áp dụng cho class, struct, interface, delegate, và methods.
            Iterator được cải tiến để có thể làm việc được với generics. Trong dòng lệnh cuối cùng ở trên, lệnh foreach đã được viết rất đơn giản vì biến i có kiểu phù hợp với stack.
2. Mục đích:
Các generic trong C# là các cấu trúc mà cho phép bạn dùng lại các tính năng đã được định nghĩa cho các kiểu dữ liêu khác nhau trong C#.
Trong một chương trình C# mà sử dụng một mảng kiểu Object để lưu trữ tên một tập hợp của các sinh viên. Tên được đọc từ người dùng do các kiểu giá trị đều được cho phép chuyển đổi về kiểu object. Trong trường hợp này khi người dùng nhập vào một giá trị kiểu numeric (kiểu số) thì trình biên dịch cũng không có bất kỳ sự kiểm tra nào.
Để đảm bảo đúng kiểu dữ liệu, C# cung cấp generics, nó là một tính năng được tích hợp trong C# mà cho phép bạn định nghĩa được kiểu dữ liệu mẫu gốc trên nó các kiểu dữ liệu được kiểm tra chuẩn kiểu dữ liệu sau này.
3. Các namespace cho Generics.
Các generic là một loại cấu trúc dữ liệu mà có thể làm việc với các kiểu giá trị cũng như các kiểu tham chiếu. Bạn có thể định nghĩa một class, interface, structure and delegate như một kiểu dư liệu trong C#. Namespace System.Collections.ObjectModel cho phép bạn tạo động và tập hợp các generic duy nhất có khả năng đọc. Namespace System.Collections.Generic gồm các class và interface cho phép bạn định nghĩa tập hợp các generic có thể sửa đổi. 
4. Việc tạo các generic:
Một sự khai báo các generic luôn luôn chấp nhận một tham số kiểu. Nó là một phần nắm giữ kiểu dữ liệu được trả về. Kiểu được chỉ định duy nhất một kiểu generic được nói tới hoặc hàm khởi tạo nó là một kiểu trong chương trình.
Cấu trúc:
6. Tác dụng của Generic:
Generic đảm bảo chuẩn kiểu dữ liệu lúc biên dịch. Generics cho phép bạn dùng lại mã trong một nền tảng mà không có khuôn mẫu hoặc chuyển đổi. Một sự định nghĩa kiểu generic là khả năng dùng lai với các kiểu dữ liệu khác nhau nhưng không chấp nhận các giá trị của một kiểu đơn cùng một thời gian.
Một số tính năng của Generic:
Ø  Tăng khả năng thực thi vì không mất thời gian và bộ nhớ cho việc casting và boxing khi tạo một generic.
Ø  Đảm bảo kiểu mẫu lập trình mạnh
Ø  Hạn chế các lỗi run-time mà có thể xảy ra trong quá trình boxing và casting.
6. Các class Generic:
Các class Generic định nghĩa các tính năng mà có thể dùng cho bất cứ kiểu dữ liệu nào.Các class là được khai báo với việc khai báo một class sau đó là tham số kiểu được bao trong hai dấu < >. Lúc khai báo class generic bạn có thể gán thêm một số hạn chế hoặc điều kiện cho tham số kiểu bằng viẹc sử dụng từ khóa where. Hơn nữa việc gán thêm hạn chế và các điều kiện vào tham số kiểu là tùy ý.
Các class Genric có thể được lồng với các class generic hoặc không generic khác, Hơn nữa bất kỳ class được lồng trong một class generic khác và khác tham số kiểu thì tham số kiểu của class bên ngoài sẽ được gán lại cho class lồng bên trong.
7. Các điều kiện của tham số kiểu:
Bạn có thể gán các điều kiện cho tham số kiểu lúc khai báo một kiểu genneric. Một ép buộc là một là một hạn chế được áp đạt lên các kiểu dữ liệu của tham số kiểu. Các ép buộc được chỉ định bằng cách chỉ định từ khóa where. Nó được sử dụng khi người lập trình muốn giới hạn kiểu dữ liệu trên tham số kiểu để đảm bảo sự toàn vẹn và tin cậy của dữ liệu trong một tập hợp.
8. Việc kế thừa các class Generic:
Ø  Một class generic có thể kế thừa từ bất cứ class generic hoặc không phải class generic nào trong C#. Khi đó một class generic đóng vai trò cả hai giống như một class cơ sở hoặc class dẫn xuất.
Ø  Thực hiện các thao tác bình thường như thao tác với các class kế thừa nhau thông thường.
9. Các phương thức Generic.
Ø  Các phương thức generic sử lý các dữ liệu của các kiểu dữ kiệu là duy nhất khi các biến lưu trữ kiểu dữ liệu này, Một generic được khai báo với danh sách tham số kiểu được bao trong  2 dấu < >.
Ø  Việc định nghĩa các phương thức với các tham số kiểu cho phép bạn gọi tới phương thức với các kiểu khác nhau mọi lúc.
Ø  Bạn có thể khi báo một generic class bên trong một class là generic class hoặc class thông thương. Khi bạn khai báo một phương thức được khai báo trong một class generic đươc khai báo thì thân của phương thức thể hiện cả hai tham số kiẻu của phương thức và của class.         
10. Các interface Generic.
Ø  Các interface Generic là được dùng cho tập hợp các generic hoặc các class generic việc hiển thị các thành phần trong tập hợp. Bạn có thể sử dụng các class generic với các interface  ngăn chặn các toán tử boxing và casting trên các kiểu giá trị.
Ø  Các class interface co thể thực thi các giao diện generic bằng việc thông qua sự trả về của các tham số được chỉ định trong interface. Tương tự thế các class generic cũng thực thi sự kế thừa.
11. Mục đích của Iterator.
Ø  Trong một hoàn cảnh mà có một người muốn nhớ một cuốn sách của 100 trang. Nhiệm vụ cuối cùng của người này là nhắc lại mỗi trang trong 100 trang đó.
Ø  Tương tự người này phải nhắc lại số trang, một iterator trong C# hỗ trợ việc đi tắt  thông qua một danh sách các giá trị hoặc một tập hợp. nó là một khối của đoạn code khi mà dùng vòng lặp foreach gọi tới một tập hợp các giá trị theo một khuôn mẫu có tuần tự.
Ø  Ví dụ như một người lập trình lưu trữ các giá trị thông qua sự tuần tự các giá trị sử dụng ỉterator để so sánh các giá trị. 
12. Iterator:
Ø  Một iterator không phải là một thành phần dữ liệu nhưng nó là một cách của việc truy suất đến các phần tử. Nó có thể là một phương thức một khả năng truy suất get hoặc một một toán tử mà cho phép bạn đánh dấu các giá trị trong một tập hợp. Các Iterator là chỉ định cái cách mà các giá trị được sinh ra khi mà vòng lặp foreach truy suất đến các phần tử trong một tập hợp.
Ø  Chúng giữ lại một phần các phần tử trong tập hợp sau đó nó có thể lấy về các giá trị này khi cần đến.
13. Tác dụng:
Ø  Cung cấp một sự đơn giản và nhanh hơn theo cách nhắc lại các giá trị trong tập hợp.
Ø  Giảm bớt sự phức tạp của việc cung cấp một sự liệt kê cho một tập hợp.
Ø  Iterator có thể trả về một lượng lơn các giá trị.
Ø  Iterator có thể được đánh giá và trả về duy nhất các giá trị mà nó cần thiết.
Ø  Iterator có thể trả về các giá trị mà không tốn bộ nhớ bằng việc gọi tới duy nhất một giá trị trong danh sách.
14. Sự thực thi:
Ø  Iterator có thể được thực hiện bằng phương thức GetEnumerator() mà trả về một tham chiếu tới interface IEnumerator.
Ø  Khối iterator sử dụng từ khóa yield. Từ khóa yield return là trả về các giá trị khi đó từ khóa yield break là kết thúc của sử lý iterator.
Ø  Một cách khác để tạo là bằng việc tạo một phương thức kiểu trả về là interface IEnumerable. Đây được gọi là một iterator có tên.Iterator có tên chấp nhận tham số mà được sử dụng cho việc quản lý điểm bắt đầu và điểm kết thúc vủa vòng lặp foreach.  
Bài thực hành số 14
Generic và Iterator
1.      Generic
Tóm tắt lý thuyết: Lập trình generic tiết kiệm được khá khá thời gian lập trình, và tính tái sử dụng code rất cao. Trong C#, bạn có thể lập trình Generic với Class, Struct, Function.
Generics rất hữu ích vì nó cung cấp một khả năng mạnh mẽ để kiểm tra kiểu dữ liệu trong lúc biên dịch, sử dụng ít các phép ép kiểu giữ các kiểu dữ liệu khác nhau, giảm việc ép kiểu lúc chạy.
1.1 Tại sao dùng generics?
Nếu không có generics, để mô tả dữ liệu không xác định (dữ liệu có kiểu tổng quát nào đó), chúng ta phải dùng kiểu object. Ví dụ, lớp Stack chứa dữ liệu là một mảng các object, và nó có hai phương thức Push và Pop, sử dụng object như là kiểu dữ liệu đầu vào.
Mặc dù chúng ta dùng kiểu object để lưu trữ dữ liệu một cách rất mềm dẻo, tuy nhiên lại xuất hiện một số hạn chế. Ví dụ, chúng ta có thể đẩy vào Stack một giá trị thuộc kiểu bất kì, ví dụ Customer. Khi đối tượng này được lấy ra ví dụ bằng lệnh Pop, chúng ta bắt buộc phải ép kiểu trở lại, điều này làm giảm hiệu năng của hệ thống do xuất hiện các phép kiểm tra kiểu dữ liệu.
Nếu giá trị truyền vào là kiểu tham trị (value type), nó sẽ được tự động gọi lại (boxed). Khi lấy lại, giá trị này sẽ được tự động mở gói (unboxed), và đây là phép ép kiểu không an toàn (boxing và unboxing - khái niệm?)
 Tất cả các phép boxing và unboxing, ngoài việc kiểm tra kiểu dữ liệu, còn mất các phép toán khởi tạo bộ nhớ động cho việc box/unbox.
Một vấn đề nữa là chúng ta có thể chuyển kiểu nhầm. Ví dụ, bạn lưu trữ vào Stack một đối tượng Customer, nhưng lại lấy ra một string, khi đó ngoại lệ InvalidCastException sẽ được ném ra, do chúng ta không thể chuyển Customer về string được.
Mọi việc sẽ ổn hơn nếu chúng ta có thể chỉ đích xác kiểu dữ liệu mà Stack lưu trữ. Generics cho phép chúng ta làm điều đó
1.2 Tạo và sử dụng Generics
Ví dụ sau đây mô tả cách tạo một lớp generic Stack nhận kiểu dữ liệu T như là tham số đầu vào. Kiểu này được đặt trong cặp dấu <> đi ngay đăng sau tên lớp. Thay vì chuyện cần có phép ép kiểu từ kiểu object, mỗi thể hiện Stack chỉ làm việc với kiểu dữ liệu được chỉ định ra lúc tạo, và không cần đến phép ép kiểu. T hoạt động như là thành phần tạm, cho đến khi một thể hiện nào đó của Stack được tạo ra
Khi lớp Stack được dùng, một kiểu dữ liệu cụ thể nào đó sẽ thay thế T. Xem ví dụ sau:
Kiểu Stack được gọi là kiểu đã khởi tạo (constructed type). Trong kiểu này, tất cả chỗ nào xuất hiện T đều được thay thế bởi int. Lúc đó mảng sẽ lưu trữ là mảng các số nguyên, chứ không phải là mảng các đối tượng, và sẽ hiệu quả hơn nhiều so với việc lưu trữ và xử lí mảng đối tượng. Tất nhiên, các hàm Push và Pop của Stack cũng hoạt động với kiểu dữ liệu int, cho phép kiểm soát lỗi không tương thích kiểu lúc biên dịch, và tránh được phép ép kiểu lúc chạy chương trình.
Generics cho phép kiểm tra kiểu ngay khi biên dịch, có nghĩa là chúng ta không thể nhầm lẫn trong việc xử lí dữ liệu được,. Tất cả những lỗi này sẽ được thông báo ngay bởi bộ biên dịch. Hãy xem ví dụ sau.
   1:   Stack stack = new Stack();
   2:  stack.Push(new Customer());
   3:  Customer c = stack.Pop();
   4:  stack.Push(3);      // Type mismatch error
   5:  int x = stack.Pop();    // Type mismatch error
Generics có thể sử dụng nhiều kiểu dữ liệu đầu vào. Lớp Stack dùng một kiểu dữ liệu đầu vào, nhưng lớp Dictionary lại có hai kiểu dữ liệu đầu vào, một dành cho kiểu dữ liệu của khóa, và một dành cho kiểu dữ liệu của giá trị khóa.
   1:   public class Dictionary
   2:  {
   3:      public void Add(K key, V value) {...}
   4:      public V this[K key] {...}
   5:}
Tất nhiên, khi sử dụng chúng ta phải cung cấp cả hai kiểu dữ liệu này
Ví dụ


Ràng buộc dữ liệu

Nói chung, các lớp generic sẽ thực hiện nhiều hành động hơn là chỉ lưu trữ dữ liệu. Thực tế là các lớp generic sẽ muốn triệu gọi các phương thức có liên quan đến việc xử lí đối tượng có kiểu với tham số generic truyền vào. Xem ví dụ sau:

Vì kiểu dữ liệu của K có thể là bất cứ kiểu gì, do đó chúng ta chỉ có thể dùng phương thức có trong Object, nghĩa là lớp nào cũng hỗ trợ, ví dụ như các hàm Equals, GetHashCode, và ToString; nếu chúng ta dùng hàm khác (trong ví dụ trên là CompareTo) thì không thể đảm bảo rằng kiểu nào của K cũng hỗ trợ, nên sẽ có lỗi biên dịch. Tất nhiên, nếu muốn thì lúc này phải ép kiểu, ví dụ như sau:
Tới đây, vấn đề cũ lại xảy ra: mất thêm chi phí ép kiểu và có thể có ngoại lại ném ra nếu trong trường hợp chúng ta không ép kiểu được.
Để giải quyết vấn đề này, C# cung cấp 1 cách kiểu tra kiểu tại thời điểm design, đồng thới làm giảm bớt ép kiểu, đó là danh sách các ràng buộc (constraints) đối với mỗi loại tham số. Mỗi kiểu tham số đầu vào sẽ phải thỏa mãn các ràng buộc nào đó để mà có thể trở thành kiểu tham số thực. Tham số này được cung cấp bằng từ khóa where, theo sau bởi tên kiểu và dấu hai chấm, đến kiểu lớp, kiểu giao tiếp ràng buộc, cách nhau bởi dấu phảy
Cú pháp where
Chú ý :
Chúng ta có thể chỉ định nhiều ràng buộc về giao tiếp và kiểu, nhưng chỉ có 1 ràng buộc bề lớp. Các ràng buộc kiểu khác nhau cách nhau bởi mệnh đề where. Ví dụ dưới đây, kiểu K có 2 ràng buộc giao tiếp, trong khi kiểu E có 1 ràng buộc lớp và 1 ràng buộc khởi tạo.
Ràng buộc khởi tạo new() ở ví dụ trên chỉ ra rằng kiểu truyền vào cho E phải có hàm tạo không tham số, public, do đó cho phép lớp generic có thể gọi hàm tạo này để tạo thể hiện cho lớp. phần này tra ở Help.

Phương thức generic

Trong một số trường hợp thì chúng ta không cần kiểu generic cho 1 lớp, mà chỉ cần cho một phương thức nào thôi. Trong trường hợp này thì chúng ta sẽ dùng phương thức generic. Ví dụ, với lớp Stack ở trên, một thao tác hay làm đó là đẩy vào trong stack nhiều phần tử 1 lúc, nghĩa là trong 1 lời gọi hàm sẽ thao tác với nhiều phần tử. Chẳng hạn, để tạo một Stack, chúng ta muốn rằng sẽ đẩy vào 1 mảng các số nguyên, vậy phương thức sẽ như sau:
Lời gọi tương ứng sẽ là :
Tuy nhiên, cách làm này chỉ đúng với Stack. Bây giờ, chúng ta sẽ phải viết một phương thức generic, sử dụng chung kiểu dữ liệu T đối với lớp generic. Đoạn mã như sau:
Lời gọi hàm tương ứng là

Lập trình Generic với Struct.
Nhìn chung, ko có gì khác biệt trong cách lập trình Generic Struct và Generic Class, ví dụ sau là Struct
Couple:
Ví dụ


2. Giới thiệu Iterator
Câu lệnh foreach của C# được dùng để duyệt lặp đi lặp lại qua những phần tử trong một tập liệt kê được. Để trở thành một tập liệt kê được, tập đó phải phải có phương thức GetEnumerator trả về enumerator (toán tử liệt kê). Nói chung, trên thực tế toán tử liệt kê rất khó cài đặt, thế nhưng công việc này sẽ đơn giản hơn với việc dùng iterator.
Một iterator là một khối lệnh cho phép yield (tạo ra, sinh ra) một dãy các giá trị đã được sắp xếp. Một iterator được phân biệt với khối lệnh bình thường bằng một hay nhiều từ khóa yield:
Ø  Lệnh yield return chuyển đến giá trị tiếp theo của iteration
Ø  Lệnh yield break chỉ ra iterator đã hoàn thành.
Một iterator có thể được dùng như là thân hàm hoặc là giá trị trả về của hàm trong lớp có cài đặt giao tiếp enumerator hoặc enumerable.
Ø  Giao tiếp enumerator là System.Collections.IEnumerator và được thiết lập từ System.Collections.Generic.IEnumerator.
Ø  Giao tiếp enumerable là System.Collections.IEnumerable và được thiết lập từ System.Collections.Generic.IEnumerable.
Ø  Một điều quan trọng đó là một iterator không phải là một thành viên của lớp, nhưng đó là một cách cài đặt hàm thành viên. Một thành viên được cài đặt thông qua iterator có thể được viết đề bởi các thành viên khác mà có hay không có sự cài đặt với iterator.
Ø  Lớp Stack dưới đây cài đặt giao tiếp GetEnumerator bằng cách sử dụng iterator. Iterator này cho phép duyệt stack từ đỉnh về đáy.
Khi đó, chúng ta sẽ sử dụng được lệnh foreach đối với Stack. Xem đoạn lệnh minh họa sau:
Kết quả đầu ra là: 9 8 7 6 5 4 3 2 1 0
Lệnh foreach sẽ gọi phương thức GetEnumerator để lấy về một toán tử liệt kê. Tất cả các hàm này đều không có tham số truyền vào, mặc dù có thể có nhiều hàm như vậy. Trong trường hợp đó, chúng ta hiểu như là lớp sẽ cho phép liệt kê các thành phần của nó theo các thứ tự (hướng) liệt kê khác nhau. Xem ví dụ dưới đây:
Phương thức TopToBottom trả về this, vì bản thân lớp Stack đã là một lớp liệt kê được. Phương thức BottomToTop trả về một kiểu liệt kê khác. Ví dụ minh họa sử dụng lớp Stack như dưới đây:

Trả về kiểu IEnumerable hay IList?

Câu hỏi: Tại sao lại trả về IEnumerable mà ko phải là IList ? Hoặc rộng hơn là tại sao lại chọn IEnumerable thay vì các kiểu collection khác hay 1 class cụ thể?
Vấn đề: nhiều bạn viết code thường trả về kiểu List Có lẽ là vì kiểu List có thể làm được mọi thứ nhanh chóng dễ dàng. Nhưng khi dùng tool như Resharper, có thể thấy rằng Reshaper khuyến nghị chúng ta nên trả về IEnumerable. Tại sao lại như vậy?
Giải thích: 1 nguyên tắc khi thiết lập Interface, đó là trả về most restricted type, vì vậy luôn trả về kiểu List là 1 thói quen xấu và tùy tiện. Trả về IEnumerable có 2 lợi ích:
  • Ngăn chặn những thao tác chỉnh sửa lên read-only collection, ví như khi ta trả về tập hợp với mục đích hiển thị, nhưng sau đó lại cố gắng add thêm item vào, trình biên dịch sẽ báo động cho chúng ta về lỗi tư duy này ngay lập tức
  • Không bị giới hạn size như với Array
  •  

Làm dự án web ASP.net thực tế với mô hình phân lớp (3 lớp)

Mô hình 3 lớp (kiến trúc 3 lớp) - 3 layers architecture cần phân biệt với mô hình 3 tầng - 3 tiers. 3 layers là mô hình logic, còn 3 tiers là mô hình vật lí. Trong nội dung bài này, khi nhắc đến 3 lớp tức là 3 layers.
Có một bài viết rất hay trên codeproject nói về kiến trúc 3 layers. Các bạn tham khảo tại đây: Three Layer Architecture in C# .NET

Giới thiệu

Có lẽ mình nên bỏ qua phần giới thiệu, ưu điểm và khuyết điểm của mô hình 3 lớp vì nói dài, nói dai đâm ra nói dại thôi :))

Kiến trúc

Tất nhiên là sẽ có 3 lớp rồi, nhưng 3 lớp đó là gì và có nhiệm vụ như thế nào đây?

Presentation Layer (GUI)

Đây chính là giao diện xử lý của ứng dụng (Windows form, webform, ...). Nhiệm vụ chính của lớp này là nhập liệu và trình bày dữ liệu, có thể bao gồm kiểm tra dữ liệu đầu vào trước khi gọi Business Logic Layer.
Như ví dụ trên, trước khi nhấn button Add, ta phải nhập dữ liệu Name, Address và Email. Ở Presentation Layer có thể kiểm tra các dữ liệu nhập vào có hợp lệ hay không, có cho phép để trống không, email đúng định dạng không...sau đó mới thực hiện việc thêm vào CSDL.

Business Logic Layer (BLL)

Đôi khi bạn thấy BLL, đôi khi là BUS, cái nào cũng là Business Logic Layer, muốn viết kiểu nào thì viết thôi.
Kiểm tra các yêu cầu nghiệp vụ trước khi cập nhật dữ liệu, quản lý các transaction, quản lý các concurrent access...bla bla. Nếu mới tìm hiểu có thể bạn sẽ thắc mắc ở lớp này, và khi code bạn càng thắc mắc hơn. Công việc của BLL là gọi lại các xử lí của lớp Data Access Layer, nếu gặp ngoại lệ cũng "quăng" cho GUI. Vậy nó có tầm quan trọng như thế nào trong khi ta có thể gọi trực tiếp từ DAL?
Cái này là kinh nghiệm của mình thôi, chưa chắc đã đúng nhưng nếu hiểu theo nghĩa này thì rất đơn giản.
Thứ nhất, đừng tự hỏi vì sao mặt trời mọc ở hướng đông. Khi người ta đưa ra mô hình 3 lớp thì cũng có lý do của họ :v
Thứ hai, theo đúng trình tự viết theo mô hình 3 lớp, bạn sẽ viết từ lớp trên xuống lớp dưới. Tức là ở GUI cần xử lý những gì thì bạn sẽ thêm xử lý đó cho BLL, và BLL yêu cầu lớp dưới nó là DAL làm, sau đó kết quả cứ trả ngược lại cho lớp trên.
Cuối cùng, hãy tưởng tượng viễn cảnh như thế này: Có 3 đối tượng đưa ra là khách hàng, giám đốc và đội ngũ kỹ thuật. Khách hàng sẽ làm việc với giám đốc và yêu cầu làm một chương trình Quản lý sinh viên. Ngày đầu tiên, khách hàng nói với giám đốc rằng họ cần chức năng thêm sinh viên trong ứng dụng đó, giám đốc đồng ý cung cấp chức năng đó cho khách hàng, và giám đốc yêu cầu đội ngũ kỹ thuật làm điều đó. Ngày tiếp theo, khách hàng cần chức năng xóa 1 sinh viên, giám đốc cũng đồng ý và tiếp tục yêu cầu đội ngũ kỹ thuật thêm chức năng xóa 1 sinh viên cho khách hàng.
Có thể so sánh sự tương đồng như sau: Khách hàng = GUI, Giám đốc = BLL và Đội ngũ kỹ thuật = DAL. GUI cần gì BLL sẽ cung cấp, và BLL gọi DAL để xử lý yêu cầu đó.
Tiếp tục thế này, sau khi đội ngũ kỹ thuật thực hiện 2 yêu cầu thêm và xóa, họ "tự nghĩ" rằng có thể khách hàng sẽ cần thêm chức năng cập nhật thông tin cho 1 sinh viên, và họ cũng "tự làm" thêm chức năng cập nhật để dự phòng. Thế nhưng "đời không như là mơ", khách hàng không cần chức năng đó, và đội ngũ kỹ thuật đã làm 1 việc vô ích. Trường hợp này cũng giống như khi bạn code lớp DAL trước, rồi mới đưa lên BLL vậy.
Tuy nhiên, nếu bạn dự tính được những việc sẽ làm thì code lớp nào trước cũng không là vấn đề.

Data Access Layer (DAL)

Đôi khi bạn thấy là DAL, đôi khi là DAO, cái nào cũng là Data Access Layer cả. Tùy cách thể hiện thôi.
Chức năng của DAL là kết nối CSDL, tìm kiếm, thêm, xóa, sửa,… trên CSDL/XML. Quá rõ ràng rồi, trong đó bạn có thể sử dụng ADO.NET, Entity Framework để xử lý, và cũng không giới hạn cách thức lưu trữ (Sql, Access, Xml...).

Data Transfer Object (DTO) - anh là ai?

Như ví dụ ở trên, Khách hàng, giám đốc và đội ngũ kỹ thuật giao tiếp bằng cách nào. Nếu ở gần thì họ có thể gặp mặt, họp...nhưng nếu khách hàng ở Mĩ, và công ty ở VN thì sao? Đơn giản, họ có thể liên lạc bằng phone, sms, email, skype...Cũng như vậy, GUI, BLL và DAL trao đổi dữ liệu bằng DTO - đối tượng trao đổi dữ liệu.
Nên nhớ, DTO không phải là 1 lớp mà chỉ là đối tượng trao đổi dữ liệu giữa các lớp với nhau. Vậy DTO ... là gì?
Đơn giản thôi, DTO chính là các bảng có trong CSDL của bạn:
Với mỗi cột trong bảng, bạn tạo một Properties. Chú ý kiểu dữ liệu để khi insert vào bảng cho hợp lý.

Project

Có nhiều cách để tổ chức project theo mô hình 3 lớp, tùy theo độ tiện lợi, thói quen... mà các bạn có thể tổ chức project 1 cách hợp lý. Sau đây là một cách để tham khảo.
Theo cách này, tất cả các lớp BLL bạn sẽ đưa vào thư mục BLL, tương tự đối với DAL, GUI và DTO.

Tổng kết

À...ừm...cảm ơn các bạn đã siêng mà đọc tới đây. Để nắm vững mô hình này, hãy cùng làm một demo nào