Tải bản đầy đủ (.docx) (68 trang)

vài năm về trước tôi đã có đủ khả năng tài chính để chuyển hướng nghề nghiệp bản quyền quyển sách foundation of programming được đăng kí bản quyền với tên gọi attribution noncommercial noderivs 3 0 un

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (513.51 KB, 68 trang )

<span class='text_page_counter'>(1)</span><div class='page_container' data-page=1>

<b>BẢN QUYỀN</b>



Quyển sách Foundation of Programming được đăng kí bản quyền với tên gọi
Attribution-NonCommercial-NoDerivs 3.0 Unported.


Độc giả được phép sao chép, phân phối và sử dụng quyển sách. Tuy nhiên, bạn cần ghi chú tên tác
giả là Karl Seguin và khơng được sử dụng quyển sách với mục đích thương mại cũng như không
được thay đổi nội dung của sách.


Bản quyền đầy đủ của sách được thể hiện tại:


/>


<b>SỰ GHI NHẬN</b>



Có rất nhiều người xứng đáng nhận lời cảm ơn từ tác giả. Quyển sách này chỉ là một sự đóng góp
nhỏ nhoi cho sự phát triển và kho kiến thức khổng lồ của cộng đồng phần mềm. Nếu khơng có
những quyển sách chất lượng cùng các diễn đàn, những bài của các newsgroup, blog, thư viện và
các dự án mã nguồn mở, tơi sẽ vẫn cịn phải tìm hiểu tại sao các đoạn mã ASP của tôi bị
timing-out khi thực hiện vòng lặp trên những tập hợp các bản ghi/đối tượng - recordset(sự phiền tối của
MoveNext).


Khơng ngạc nhiên rằng cộng đồng phần mềm đã sử dụng tính đại chúng của Internet nhiều hơn bất
kì ngành nghề nào khác để phát triển . Điều đáng ngạc nhiên là cái cách mà điều đó đã xảy ra mà
khơng gây nhiều chú ý. Rất tốt!


Dĩ nhiên, có một ngừơi đặc biệt mà nếu khơng có thì sẽ khơng thể có quyển sách này.
Gửi Wendy,


Mọi người bảo anh là may mắn vì được ở cùng một người thơng minh và xinh đẹp như em. Tuy
nhiên họ không biết hết về em. Em khơng chỉ xinh đẹp và thơng minh, em cịn cho phép anh dành
quá nhiều thời gian bên chiếc máy tính, cho dù là để làm việc, học tập, viết code hoặc chơi game.


Em còn rất sẵn lòng đọc lại bản thảo của anh hoặc lắng nghe anh chuyện trò những điều vô nghĩa.
Anh cần phải biết ơn em nhiều hơn nữa.


Mục lục


<b>BẢN QUYỀN...1</b>


<b>SỰ GHI NHẬN...1</b>


<b>VỀ TÁC GIẢ...3</b>


<i>CHƯƠNG 1 ALT.NET...4</i>


<b>Mục tiêu...4</b>


<b>Sự đơn giản...5</b>


<b>YAGNI...5</b>


<b>Thời điểm hồi đáp cuối cùng(Last Responsible Moment)...6</b>


<b>Tránh lặp lại (DRY)...6</b>


<b>Rõ ràng và gắn kết...6</b>


<b>Cặp đôi...6</b>


</div>
<span class='text_page_counter'>(2)</span><div class='page_container' data-page=2>

<b>Trong chương này...6</b>


<b>Chương 2 THIẾT KẾ HƯỚNG LĨNH VỰC...8</b>



<b>Thiết kế hướng dữ liệu/ lĩnh vực...8</b>


<b>Ngừơi dùng, khách hàng và Các bên liên quan...9</b>


<b>Hướng lĩnh vực (The domain object)...9</b>


<b>Giao diện người dùng (UI)...12</b>


<b>Bí quyết và thủ thuật...13</b>


<b>Factory Pattern...13</b>


<b>Chỉ định truy cập – Access Modifier...13</b>


<b>Interface...13</b>


<b>Che dấu thông tin và tính đóng gói...14</b>


<b>Tổng kết chương...15</b>


CHƯƠNG 3 Persistence...16


<b>Gap...16</b>


<b>DataMapper...16</b>


<b>Vấn đề...19</b>


<b>Các hạn chế...20</b>



<b>Tổng kết chương...21</b>


Dependency Injection...22


<b>Sneak Peak trong Unit Testing...23</b>


<b>Don’t avo id Coupling lik e the Plag ue...24</b>


<b>Dependency Injection...24</b>


<b>Constructor Injection...24</b>


<b>Framework...26</b>


<b>Sự cải tiến cuối cùng...28</b>


<b>Tổng kết chương...29</b>


<b>CHƯƠNG 5 Unit Testing...30</b>


<b>Tại sao tôi đã không dùng Unit Test trong 3 năm trước?...30</b>


<b>The Tools...31</b>


<b>nUnit...32</b>


<b>Unit Test là gì?...33</b>


<b>Mocking...33</b>



<b>More on nUnit and RhinoMocks...36</b>


<b>UI and Database Testing...37</b>


<b>Trong chương này...37</b>


<b>CHƯƠNG 6 Object Relational Mappers...38</b>


<b>Infamous Inline SQL vs Stored Procedure Debate...38</b>


<b>Stored Procedures are More Secure...38</b>


<b>Thủ tục lưu trữ nhanh hơn...39</b>


<b>NHibernate...40</b>


<b>Configuration...40</b>


<b>Relationships...42</b>


<b>Querying...43</b>


<b>Lazy Loading...45</b>


<b>Download...46</b>


<b>Trong chương này...46</b>


<b>CHƯƠNG 7 Trở lại căn bản: Bộ nhớ...47</b>



</div>
<span class='text_page_counter'>(3)</span><div class='page_container' data-page=3>

<b>Stack...47</b>


<b>Heap...47</b>


<b>Con trỏ...48</b>


<b>Mơ hình bộ nhớ trong thực tế...50</b>


<b>Đóng hộp (Boxing)...50</b>


<b>ByRef...51</b>


<b>Quản lý thất thốt bộ nhớ...53</b>


<b>Phân mảnh...53</b>


<b>Kết dính bộ nhớ (Pinning)...54</b>


<b>Xác lập giá trị null...54</b>


<b>Kết thúc tiền định...55</b>


<b>Trong chương này...55</b>


<b>CHƯƠNG 8 Trở lại căn bản: Exception...56</b>


<b>Xử lý exception...56</b>


<b>Logging...57</b>



<b>Cleaning (làm sạch)...57</b>


<b>Ném ngoại lệ...58</b>


<b>Cơ chế ném ngoại lệ...58</b>


<b>Khi nào phải ném ngoại lệ...59</b>


<b>Tạo những ngoại lệ riêng...60</b>


<b>Trong chương này...62</b>


<b>CHƯƠNG 9 Basic : Proxy This and Proxy That...63</b>


<b>Proxy Domain Pattern...63</b>


<b>Interception...64</b>


<b>In This Chapter...66</b>


<b>VỀ TÁC GIẢ</b>



Karl Seguin là một nhà phát triển phần mềm tại Fuel Industries, từng là MVP của Microsoft, là
một thành viên có nhiều ảnh hưởng của cộng đồng CodeBetter.com và là một biên tập viên của
DotNetSlackers. Ông đã viết rất nhiều bài báo và là một thành viên tích cực của nhiều cộng đồng
thơng tin Microsoft. Ông hiện sống tại Ottawa, Ontario Canada.


Trang web cá nhân của Karl Seguin là />


Blog của tác giả, cùng với blog của nhiều chuyên gia nổi tiếng khác, ở tại địa chỉ:



</div>
<span class='text_page_counter'>(4)</span><div class='page_container' data-page=4>

<i>CHƯƠNG 1 </i>

ALT.NET



NẾU CĨ SỰ KhƠNG HÀI LỊNG VỚI HIỆN TẠI, TỐT THƠI. NẾU CĨ SỰ KHUẤY ĐỘNG, CÀNG
TỐT HƠN NỮA. NẾU CĨ SỰ KIÊN TRÌ KHƠNG NGỪNG NGHỈ, TƠI HÀI LỊNG. VÀ TIẾP ĐÓ
HÃY ĐỂ NHỮNG Ý TƯỞNG, NHỮNG SUY NGHĨ, VIỆC LÀM TÍCH CỰC ĐƯỢC NẢY NỞ. NẾU
MỘT NGƯỜI CẢM THẤY NHỎ BÉ, HÃY ĐỂ ANH TA TỰ LÀM MÌNH LỚN HƠN - HUBERT H
HUMPHREY.


Vài năm về trước tơi đã có may mắn để chuyển hướng trong nghề lập trình của mình. Một cơ hội
làm hướng dẫn xuất hiện và tơi tận dụng nó một cách tối đa. Trong vịng vài tháng, kĩ năng lập
trình của tơi tăng theo hàm mũ, và vài năm gần đây, tôi tiếp tục tinh lọc kĩ năng của mình. Khơng
nghi ngờ rằng tơi cịn nhiều điều cần học, và 5 năm sau, tơi sẽ xấu hổ khi nhìn lại những đoạn code
mình viết lúc này. Tôi đã từng tự tin ở khả năng lập trình của mình, Nhưng chỉ khi tơi chấp nhận
rằng mình biết rất ít, và thường là tơi ln như vậy, tôi mới bắt đầu thật sự hiểu.


Cuốn sách này là sự bộ sưu tập các bài viết mà mục đích chính là để giúp cho các lập trình viên
nhiệt huyết tự luyện tập. Xuyên suốt cuốn sách này, chúng ta sẽ đề cập những vấn đề thường được
trình bày quá chuyên sâu với mọi người, ngoại trừ những người đã tiếp cận chúng từ trước. Tôi
luôn thấy có 2 phần nổi trội trong thế giới .Net. Một được ủng hộ mạnh mẽ bởi Microsoft như sự
phát triển tự nhiên của VB6 và ASP cổ điển( thường được biết tới như MSDN Way). Cái còn lại
được ủng hộ bởi những xu hướng chính trong hướng đối tượng và bị ảnh hưởng bời những khái
niệm/ dự án lớn của Java (được biết đến như là ALT.NET).


Trong thực tế, hai phần không thể so sánh với nhau được. MSDN Way hướng người sử dụng vào
một phương thức đặc biệt để chia nhỏ chương trình thành từng câu lệnh riêng biệt (sau cùng, chẳng
phải những ghi chú tham khảo của API là lí do duy nhất mà chúng ta phải ghé thăm MSDN?) .
Trong khi đó, ALT.NET chú trọng vào những chủ đề mang tính trừu tượng và cũng đồng thời cung
cấp một sự hiện thực cụ thể. Jeremy Miller đã cho rằng: Cộng đồng .Net đã chú trọng quá nhiều
vào API và framework, trong khi không đủ nhấn mạnh vào nền tảng của thiết kế và lập trình. Để


đưa ví dụ liên quan và cụ thể, MSDN Way ủng hộ mạnh mẽ việc sử dụng Datasets va DataTables
cho việc liên lạc/kết nối cơ sở dữ liệu. ALT.NET, tuy nhiên, chú ý vào những cuộc bàn luận về
những mẫu thiết kế ổn định, những vấn đề khơng tương thích trước mắt giữa các đối tượng cũng
như là những cách hiện thực đặc biệt, như NHibernate (O/R Mapping), MonoRail(ActiveRecord)
cũng như DataSets and DataTables. Nói một cách khác, khác với điều nhiều người nghĩ, ALT.NET
không phải là một sự thay thế cho MSDN Way, những người phát triền phần mềm cần phải biết và
hiểu rằng những phương án thay thế cho những giải pháp và cách tiếp cận mà MSDN Way là một
phần trong đó


Dĩ nhiên, từ những miêu tả trên, ta có thể thấy được rằng để đi theo con đường ALT.NET cần phải
có niềm đam mê lớn hơn cũng như cần một nền tảng kiến thức sâu rộng hơn. Con đường học vấn
nhiều trắc trở và những nguồn tài liệu bổ ích mới bắt đầu được tập hợp lại (đây cũng là lí do tơi
viết cuốn sách này). Tuy nhiên, những gì nhận được thì rất xứng đáng. Với tơi, thành công trong
nghề nghiệp đã đem đến những hạnh phúc to lớn trong đời sống cá nhân.


<b>Mục tiêu </b>



</div>
<span class='text_page_counter'>(5)</span><div class='page_container' data-page=5>

và kinh nghiệm thực tế cho chúng tôi thấy rằng, các hệ thống sự dụng phần lớn thời gian (trên
50%) ở trạng thái bảo trì – thay đổi, sửa lỗi, nâng cấp. Thứ hai là, sự gia tăng của cách phát triển
lặp (iterative) làm cho những thay đổi và tính chất mới liên tục được tiến hành trên những đoạn mã
có sẵn (thậm chí nếu bạn chưa bao giờ trải nghiệm qua một q trình phát triển lặp, ví dụ như
Agile, khách hàng của bạn vẫn có nhiều khả năng sẽ yêu cầu bạn đủ kiểu thay đổi). Tóm lại, giải
pháp bảo trì khơng chỉ hạ thấp chi phí mà còn làm tăng chất lượng cũng như số lượng thành phẩm
mà bạn có thể bán ra.


Thậm chí nếu bạn cịn khá xa lạ với lập trình, rất có thể bạn cũng đã từng định hình những suy
nghĩ về việc những gì có thể được bảo trì và những gì khơng thể, dựa trên kinh nghiệm làm việc
nhóm, sử dụng những ứng dụng của người khác, hay thậm chí là sửa lại những gì mà bạn đã code
vài tháng trước. Một trong số những điều quan trọng nhất mà bạn có thể làm là ghi chú lại tất cả
những gì có vẻ khơng đúng và google (hay MSN cũng được) một giải pháp tốt hơn xung quanh. Ví


dụ, các lập trình viên đã bỏ ra nhiều năm làm việc với ASP cổ điển có thể biết rằng sự tương thích
chặt chẽ giữa code và HTML chưa thật sự lý tưởng.


Viết ra những đoạn mã có thể bảo trì khơng phải là một điều dễ dàng. Khi bạn bắt đầu, bạn sẽ phải
siêng năng hơn cho tới khi mọi việc đi vào quỹ đạo. Bạn có thể nghi ngờ, nhưng chúng tôi không
phải là người đầu tiên đặt suy nghĩ vào việc tạo ra những chương trình có thể bảo trì. Cuối cùng,
có một số ý niệm bạn nên quen dần. Khi chúng ta lướt qua một điều gí đó, hãy bỏ thời gian nghiên
cứu chúng thật kĩ và tìm kiếm thêm thơng tin để có được cái nhìn sâu sắc và nền tảng vững vàng
hơn. Và, quan trọng hơn hết, hãy cố tìm hiểu xem chúng ứng dụng vào dự án hiện tại mà bạn đang
thực hiện như thế nào.


<b>Sự đơn giản</b>



Công cụ tốt nhất để làm cho chương trình của bạn có thể bảo trì được là làm cho nó đơn giản nhất
có thể được. Theo quan điểm thơng thường thì để có thể bảo trì được, trước nhất hệ thống phải
được xây dựng để có thể đáp ứng cho tất cả các yêu cầu thay đổi khả dĩ. Tôi đã thấy các hệ thống
được xây dựng dựa trên metarepositories (những bảng với những cột khóa – key và cột giá trị
-value) hoặc cấu hình XML phức tạp, nhằm mục đích giải quyết các thay đổi mà khách hàng u
cầu. Những hệ thống này khơng chỉ có xu hướng vấp phải những giới hạn kĩ thuật nghiêm trọng
(thể hiện của hệ thống sẽ chậm hơn do sự cồng kềnh) mà chúng cịn hầu như ln khơng đạt được
mục đích (chúng ta sẽ đề cập vấn đề này kĩ hơn khi nói về YAGNI). Theo kinh nghiệm bản thân,
cách đúng đắn để đặt được sự mềm dẻo là giữ cho hệ thống càng đơn giản càng tốt, để cho bạn,
hoặc một lập trình viên khác có thể đọc và hiểu đoạn code một cách dễ dàng cũng như có thể thực
hiện một số thay đổi cần thiết. Vì sao phải xây dựng cả một tập hợp những luật cấu hình trong khi
ta chỉ cần kiểm tra chiều dài của tên người dùng hệ thống đạt độ dài cần thiết hay khơng? Trong
chương sau, ta sẽ xem mơ hình “Phát triển định hướng testcase” - Test Driven Development - có
thể giúp chúng ta đạt tới mức độ cao hơn của sự đơn giản bằng cách đảm bảo rằng ta tập trung vào
những gì ma khách hàng yêu cầu như thế nào.


<b>YAGNI </b>




Bạn sẽ khơng phải cần nó - you aren’t going to need it - là một quan điểm lập trình cực đoan. Theo
đó, bạn khơng nên xây dựng một chương trình nào đó bây giờ bởi vì bạn sẽ khơng cần tới nó trong
tương lai. Kinh nghiệm cho thấy rằng bạn có thể sẽ khơng thực sự cần đến nó hoặc cần phải có đơi
chút thay đổi. Bạn có thể mất cá tháng để xây dựng một hệ thống linh động một cách đáng kinh
ngạc nhưng chỉ cần 2 dòng email đơn giản của khách hàng để làm cho cả hệ thống đó trở nên hồn
tồn vơ dụng. Có một lần, khi tơi đã bắt tay vào thiết kế một máy báo cáo không giới hạn thời gian
(open-ended reporting engine), thì tơi nhận ra rằng mình đã hiểu sai ý khách hàng. Những gì họ
thật sự muốn có là một thiết bị báo cáo hằng ngày (single daily report), và thực ra tôi chỉ cần 15
phút để làm điều đó.


</div>
<span class='text_page_counter'>(6)</span><div class='page_container' data-page=6>

Ý tưởng đằng sau thời điểm hồi đáp cuối cùng là bạn cần trì hỗn việc xây dựng bất cứ điều gì cho
đến khi bạn buộc phải làm như vậy. Thực sự là, trong một số trường hợp, thời điểm này có thể xuất
hiện rất sớm trong quá trình phát triển phần mềm. Ý tưởng này rất gần gũi với YAGNI, với ý
nghĩa rằng cho dù bạn thật sự cần nó, bạn vẫn nên trì hỗn việc xây dựng nó cho đến khi bạn
khơng thể chờ hơn nữa. Điều này sẽ giúp bạn, và khách hàng của bạn, có thời gian để chắc chắn
rằng bạn thật sự cần nó, đồng thời cũng có thể sẽ giúp bạn giảm thiểu số thay đổi mà bạn cần làm
trong khi và sau khi phát triển sản phẩm.


<b>Tránh lặp lại (DRY)</b>



Sự lặp lại code có thể gây đau đầu cho những nhà phát triển phần mềm. Nó khơng chỉ gây ra khó
khăn khi cần thay đổi code (bởi vì bạn phải tìm tất cả những vị trí có cùng chức năng), mà còn tạo
ra nguy cơ xuất hiện những lỗi nghiêm trọng và làm cho chương trình trở nên khó khăn một cách
không cần thiết cho những nhà phát triển mới tham gia. Bằng việc thực hiên nguyên tắc không lặp
lại xuyên suốt quá trình tạo sản phẩm (nắm bắt yêu cầu, thiết kế, hiện thực, kiểm thử và tài liệu
hóa), bạn sẽ hồn thành với một bản code rõ ràng hơn và dễ bảo trì hơn. Cần phải nhớ rằng ý
tưởng này vượt xa hơn việc copy và paste và cịn hướng đến việc khơng lặp lại code dưới mọi hình
thức. Tính đóng gói đối tượng và tính liền lạc của những đoạn code sẽ giúp giảm bớt việc lặp lại.

<b>Rõ ràng và gắn kết</b>




Điều này có vẻ hiển nhiên, nhưng điều rất quan trọng là code của bạn phải làm đúng những gì nó
được u cầu. Điều này có nghĩa là các hàm và các biến cần phải được đặt tên một cách phù hợp,
sử dụng những chuẩn chữ hoa/chữ thường, và nếu cần, phải tài liệu hóa đầy đủ. Một class
Producer phải thực hiện chính xác điều mà bạn, những nhà phát triển khác và khách hàng của bạn
yêu cầu. Hơn thế nữa, các class và câu lệnh của bạn cần phải gắn kết chặt chẽ, có nghĩa là chúng
phải thống nhất về mục đích. Nếu bạn thấy rằng bạn đang viết class Khách hàng có chứa phần
quản lí số liệu về đơn hàng, có nhiều khả năng bạn cần tạo ra 1 class mới tên là Đơn hàng. Các
class chịu trách nhiệm cho nhiều phần riêng biệt sẽ nhanh chóng trở nên khó quản lí. Trong
chương tiếp theo, chúng ta sẽ thấy khả năng của lập trình hướng đối tượng trong việc viết code rõ
ràng và gắn kết.


<b>Cặp đôi</b>



Cặp đôi xảy ra khi có 2 class phụ thuộc lẫn nhau. Bất cứ khi nào có thể, bạn cần phải giảm thiểu
cặp đơi để giảm thiểu ảnh hưởng của những thay đổi và gia tăng khả năng kiểm tra của code. Giảm
thiểu và thậm chí là loại bỏ cặp đơi thực ra dễ dàng hơn nhiều người nghĩ; có những thủ thuật và
cơng cụ giúp bạn hồn thành việc đó. Cái khó là phát hiện ra những cặp đôi không mong muốn.
Chúng ta sẽ bàn đến cặp đôi chi tiết hơn ỡ những chương sau.


<b>Unit Tests và Continuous Integration</b>



Kiểm tra đơn vị và sự tích hợp liên tục, Unit Tests và Continuous Integration, thường được biết
đến như là CI, là một đề tài nữa sẽ được đề cập sau. Có hai điều bạn cần phải nắm trước. Đầu tiên,
cả hai đều hiệu quả trong việc giúp ta có được code có tính bảo trì cao. Unit tests giúp cho người
phát triển có được một sự tự tin lớn lao. Số lượng những thay đổi về việc tái phân rã – refactoring
-và tính năng mà bạn sẵn sàng tạo ra là vô cùng lớn khi bạn có một tấm lưới an tồn là hàng trăm,
hàng ngàn test tự động để chắc rằng bạn chưa hề phá vỡ chương trình. Thứ hai là, nếu bạn khơng
muốn chấp nhận, hoặc ít nhất là thử, việc tiến hành kiểm tra đơn vị, bạn đang lãng phí thời gian
đọc những điều này. Phần lớn những gì sẽ được đề cập sẽ chú trọng về nâng cao khả năng test


được của code.


<b>Trong chương này</b>



</div>
<span class='text_page_counter'>(7)</span><div class='page_container' data-page=7></div>
<span class='text_page_counter'>(8)</span><div class='page_container' data-page=8>



<b>Chương 2 THIẾT KẾ HƯỚNG LĨNH VỰC </b>



Thiết kế là gì? Đó là nơi mà bạn đứng giữa hai thế giới – thế giới của công nghệ và thế
giới của con người và những mục đích của họ - và bạn cố gắng mang hai thế giới lại cạnh
nhau. – Mitchell Kapor


Có thể đoán được rằng chúng ta sẽ bắt đầu bằng Domain driven design (thiết kế định hướng theo
lĩnh vực) và lập trình hướng đối tượng (OOP). Ban đầu tơi nghĩ là tôi nên bỏ qua một số phần
trong chủ đề này, nhưng điều đó có thể gây khó khăn cho cả tơi và các bạn. Có một số hữu hạn
cách thiết thực để thiết kế phần cốt lõi của chương trình. Cách tiếp cận phổ biến với .NET của
những nhà phát triền là dùng mơ hình dữ liệu tập trung(data-centric model). Có nhiều khả năng
bạn là một chuyên gia trong cách tiếp cận này – thành thạo sử dụng những repeater lồng nhau, kiểu
event hữu dụng ItemDataBound và điều chỉnh thuần thục DataRelations. Một giải pháp khác, vốn
rất thông dụng với các nhà phát triển Java và nhanh chóng phổ biến trong cộng đồng .NET, là
thiên về cách tiếp cận tập trung lĩnh vực.


<b>Thiết kế hướng dữ liệu/ lĩnh vực </b>



Cách tiếp cận tập trung vào dữ liệu và hướng lĩnh vực là gì? Tập trung dữ liệu nói chung có nghĩa
là bạn xây dựng một hệ thống dựa trên những hiểu biết của bạn về dữ liệu mà bạn sẽ phải tương
tác. Cách tiếp cận tiêu biểu nhất là trước hết bạn xây dựng mẫu cơ sở dữ liệu bằng cách tạo ra các
bảng quan hệ, cột quan hệ và các khóa ngoại; sau đó mơ phỏng nó trên ngơn ngữ C# hoặc
VB.NET. Lí do phương pháp này trở nên rất thông dụng trong giới phát triền .NET là Microsoft đã
tốn nhiều thời gian để tự động hóa q trình dịch sang ngôn ngữ lập trình với những lớp


DataAdapters, DataSets và DataTables. Chúng ta đều biết rằng với một bảng với dữ
liệu, chúng ta có thể có một website hoặc một ứng dụng windows hoạt động trong chưa đầy năm
phút với chỉ vài dòng code.Điểm mấu chốt là tập trung vào dữ liệu – một ý kiến tốt trong hầu hết
các trường hợp. Cách tiếp cận này đôi khi được gọi là “phát triển hướng dữ liệu”.


Domain-centric, hay thường gọi hơn là thiết kế hướng lĩnh vực (DDD), tập trung chủ yếu về vấn
đề về lĩnh vực. Vấn đề đó khơng chỉ bao gồm dữ liệu mà cịn có cả cách hoạt động. Vì thế, ví dụ,
ta không chỉ chú trọng TênRiêng của một nhân viên mà còn phải chú ý đến khả năng được
TăngLương. Vấn đề về lĩnh vực chỉ là một cách nói tương trưng cho nghiệp vụ mà bạn đang xây
dựng hệ thống để quản lí. Cơng cụ chúng ta sử dụng là lập trình hướng đối tượng (OOP), và việc
bạn chỉ biết sử dụng những ngôn ngữ hướng đối tượng như C# hay VB.NET khơng có nghĩa là bạn
đang lập trình hướng đối tượng.


</div>
<span class='text_page_counter'>(9)</span><div class='page_container' data-page=9>

rằng hướng dữ liệu thực sự là một sự lựa chọn đúng đắn trong một số trường hợp. Tôi cho rằng
phần lớn sự đối lập giữa hai trường phái trên là do Microsoft thiên về thiết kế hướng dữ liệu mặc
dù trên thực tế nó khơng tương thích tốt với những gì mà những nhà phát triển .NET đang làm
(phát triển ứng dụng doanh nghiệp), và khi không sử dụng đúng cách, kết quả tạo ra là những dịng
code khó được bảo trì hơn. Nhiều lập trình viên, cả trong và ngồi cộng đồng .NET đều gãi đầu
suy nghĩ tại sao Microsoft kiên định làm ngược lại với những điều thông thường và phải luôn luôn
cố gắng bắt kịp một cách vụng về. )


<b>Ngừơi dùng, khách hàng và Các bên liên quan</b>



“ Trong quá khứ, tơi thường xun bực mình với khách hàng. Họ khó chịu, khơng biết họ cần gì và
ln ln có những quyết định sai lầm. Nhưng khi tôi thật sự nghĩ lại về điều đó, tơi nhận ra rằng
tơi đã khơng thông minh như tôi nghĩ. Khách hàng biết rõ về công việc của họ hơn tôi rất nhiều.
Không chỉ thế, mà đó cịn là tiền của họ và cơng việc của tơi là giúp họ có được nhiều lợi nhuận
nhất từ đồng vốn ấy. Và khi mối quan hệ với khách hàng trở nên tích cực và có mối cộng tác tốt,
không chỉ kết quả công việc tăng lên đáng kể mà việc lập trình cũng trở nên thú vị trở lại.”



Vài điều tôi rút ra một cách nghiêm túc từ sự phát triển của Agile là sự ảnh hưởng lẫn nhau của đội
phát triển phần mềm, người dùng và khách hàng. Trên thực tế, bất cứ khi nào có thể, tơi khơng
phân biệt nhà phát triển hay khách hàng, tất cả là một thực thể thống nhất : đội phát triển. Bất kể
bạn có đủ may mắn hay khơng trong tình huống này (đơi khi bạn dính đến luật pháp hoặc đôi khi
khách hàng không sẵn sàng hợp tác chặt chẽ), hiểu được chức năng của từng người là rất quan
trọng. Khách hàng là người trả tiền và vì vậy cũng là người đưa ra những quyết định cuối cùng về
những vấn đề chủ chốt và độ ưu tiên. Người dùng là những người sử dụng hệ thống. Khách hàng
thường là người dùng nhưng hiếm khi là người dùng duy nhất. Ví dụ một website có thể có những
người dùng ẩn danh, người dùng chính thức, người điều hành và người quản lý. Cuối cùng, các
bên liên quan là bất cứ ai có liên quan đến hệ thống. Một trang web có thể có những trang “cha” và
trang “anh chị” , các nhà quảng cáo, PR hoặc chuyên gia lĩnh vực.


Khách hàng có một cơng việc rất khó khăn. Họ phải dành ưu tiên một cách khách quan cho điều
mà mọi người muốn, bao gồm cả họ, thu xếp với một ngân quỹ có hạn. Rõ ràng, họ sẽ phạm sai
lầm, có thể vì họ khơng hiểu hồn tồn những nhu cầu của người dùng, có thể vì họ hiểu lầm
những thơng tin mà bạn cung cấp, cũng có thể do họ dành quá ưu tiên cho nhu cầu của họ hơn là
của những người khác. Là một nhà phát triển phần mềm, công việc của bạn là giúp họ tránh sai
lầm càng nhiều càng tốt và đồng thời đáp ứng nhu cầu của họ.


Bất kể bạn có xây dựng một hệ thống vì mục đích thương mại hay khơng, tiêu chí đánh giá cuối
cùng cho sự thành cơng của hệ thống đó là cách mà người dùng cảm nhận về nó. Vì thế, trong lúc
bạn và khách hàng làm việc với nhau, mong rằng cả hai đều hướng tới nhu cầu của người dùng.
Nếu bạn và khách hàng của bạn xem việc xây dựng hệ thống cho người dùng là quan trọng, tôi
thực sự khuyên bạn nên đọc thật kĩ User Stories – một nơi tốt để bắt đầu là Mike Cohn’s excellent
User Stories Applied1<sub>. </sub>


Cuối cùng, lí do chính cho sự có mặt của chương này là các chuyên gia lĩnh vực. Chuyên gia lĩnh
vực là những người biết rõ về đầu vào và đầu ra của cái thế giới mà hệ thống của bạn đang tồn tại.
Cách đây không lâu, tôi là một thành viên của một dự án phát triển rất lớn của tổ hợp tài chính, và
ở đó thật sự đã có hàng trăm chuyên viên lĩnh vựcmà phần lớn trong số đó là nhà kinh tế hoặc nhà


kế tốn. Họ là những người nhiệt tình với cơng việc của mình cũng giống như bạn say mê lập
trình. Bất cứ ai cũng có thể trở thành một chuyên gia lĩnh vực – một khách hàng, một người dùng,
một cổ đông và thậm chí là bạn. Sự phụ thuộc của bạn vào các chuyên gia lĩnh vực tăng cùng với
sự phức tạp của hệ thống.


<b>Hướng lĩnh vực (The domain object)</b>



</div>
<span class='text_page_counter'>(10)</span><div class='page_container' data-page=10>

để bắt đầu – nhiều người có thể đã biết hết những gì đề cập ở đây. Chúng ta sẽ khơng đề cập về
tính bền vững - persistence - bây giờ (có thể xem phần cơ sở dữ liệu). Nếu bạn cịn xa lạ với cách
thiết kế này, bạn có thể luôn cảm thấy thắc mắc về cơ sở dữ liệu và các đoạn code truy xuất dữ
liệu. Hãy cố đừng quá lo lắng. Trong chương tiếp theo ta sẽ đề cập tới phần cơ bản của tính bền
vững, và ở chương tiếp theo nữa, ta sẽ nghiên cứu nó sâu hơn nữa.


Ý tưởng đằng sau thiết kế hướng lĩnh vực là xây dựng một hệ thống phản ánh lại lĩnh vực của vấn
đề mà bạn đang cố giải quyết. Đây sẽ là nơi mà các chuyên gia lĩnh vực nhập cuộc – họ sẽ giúp
bạn hiểu được hệ thống đang hoạt động như thế nào (thậm chí nếu nó chỉ là quá trình thực hiện
bằng tay trên giấy) và hệ thống đúng ra phải hoạt động như thế nào. Ban đầu bạn sẽ bị ngợp trong
sự hiểu biết của họ - họ sẽ nói những thứ mà bạn chưa từng biết đến và họ sẽ phải ngạc nhiên trước
ánh nhìn ngơ ngác của bạn. Họ cũng sẽ dùng những từ viết tắt, những từ đặc biệt và điều đó sẽ làm
cho bạn phải tự hỏi rằng liệu mình có thể đảm đương được công việc này hay không. Cuối cùng,
đây chính là mục đích thực sự của một nhà phát triển ứng dụng doanh nghiệp - hiểu về lĩnh vực
của vấn đề. Bạn đã biết lập trình như thế nào, nhưng bạn có biết lập trình trong một hệ thống kiểm
kê cụ thể mà bạn được yêu cầu? Một ai đó phải hiểu được ngơn ngữ của người khác, và nếu những
chuyên gia lĩnh vực học cách lập trình thì chúng ta sẽ khơng cịn việc làm.


Với tất cả những ai đã đọc qua những phần trên đều hiểu rằng học về một lĩnh vực mới là phần
phức tạp nhất trong cơng việc lập trình. Vì thế, làm cho code của bạn tương đồng nhiều nhất có thể
được với lĩnh vực ấy sẽ rất có ích. Về cơ bản, điều tơi nói đến là sự giao tiếp. Nếu khách hàng của
bạn nói về Mục đích chiến lược, điều mà một tháng trước đây chẳng có ý nghĩa gì với bạn, và code
của bạn là MụcĐíchChiếnLược thì một số điểm mơ hồ, nguy cơ hiểu lầm sẽ được dẹp bỏ. Nhiều


người, kể cả tôi, cho rằng một nơi tốt để bắt đầu công việc là học những cụm danh từ mà những
chuyên gia trong lĩnh vực ấy cũng như người dùng hay sử dụng. Ví dụ nếu bạn xây dựng một hệ
thống cho một công ty mua bán xe và bạn nói chuyện với người bán hàng (người đó có thể vừa là
người dùng vừa là chuyên gia trong ngành), anh ấy chắc chắn sẽ nói về Khách hàng, Xe (car),
Mo-del (moMo-del), Kiện hàng (package) và Nâng Cấp (upgrade), Tiền thanh toán (payment), v v Những
điều ấy là nồng cốt trong công việc của anh ấy, nên tất nhiên cũng sẽ là phần chính trong hệ thống
của bạn. Ngồi những cụm danh từ thì điều cần lưu ý là sự giao hội tụ của ngôn ngữ kinh doanh,
vốn được biết tới như ngôn ngữ thống nhất. Ý tưởng dùng một ngôn ngữ duy nhất cho người dùng
và hệ thống sẽ giúp cho việc bảo trì dễ dàng hơn cũng như giảm thiểu những sai lầm.


Chính xác thì bắt đầu cơng việc như thế nào là hồn tồn phụ thuộc vào bạn. Thiết kế hướng lĩnh
vực không nhất thiết phải bắt đầu bằng việc mơ phong lĩnh vực đó (mặc dù đó lá một ý kiến hay!),
mà nó có nghĩa là bạn nên tập trung vào lĩnh vực đó và để nó điều khiển quyết định của bạn. Đầu
tiên, bạn hồn tồn có thể bắt đầu với mơ hình dữ liệu của bạn, khi chúng ta khám phá mô hình
phát triển định hướng testcase chúng ta sẽ sử dụng một cách tiếp cận khác rất thích hợp với DDD.
Tuy nhiên, bây giờ chúng ta giả sử rằng chúng ta đã nói chuyện với khách hàng và vài người bán
hàng, và nhận ra rằng điều khó khăn chủ yếu là kiểm soát sự phụ thuộc lẫn nhau giữa những khả
năng nâng cấp hệ thống. Công việc đầu tiên mà chúng ta làm là thiết kế bốn class:


public class Car{}
public class Model{}
public class Package{}
public class Upgrade{}


Tiếp theo chúng ta sẽ thêm vào một ít code dựa trên một vài giả thiết khá tin cậy:


public class Car
{


private Model _model;



</div>
<span class='text_page_counter'>(11)</span><div class='page_container' data-page=11>

public void Add(Upgrade upgrade){ //todo }
}


public class Model
{


private int _id;
private int _year;
private string _name;


public ReadOnlyCollection<Upgrade> GetAvailableUpgrades()
{


return null; //todo
}


}


public class Upgrade
{


private int _id;
private string _name;


public ReadOnlyCollection<Upgrade> RequiredUpgrades
{


get
{



return null; //todo
}


}
}


Cơng việc hồn tồn đơn giản. Chúng ta sẽ thêm vào vài thông tin truyền thống ( id, tên), vài tham
chiếu (cả Cars và Models đều có Upgrades), và hàm Add vào class Car. Bây giờ ta có thể thay đối
một ít và bắt đầu viết một ít về những hoạt động thực tế.


public class Car
{


private Model _model;


//todo where to initialize this?
private List<Upgrade> _upgrades;
public void Add(Upgrade upgrade)
{


_upgrades.Add(upgrade);
}


public ReadOnlyCollection<Upgrade>
MissingUpgradeDependencies()


{


List<Upgrade> missingUpgrades = new List<Upgrade>();


foreach (Upgrade upgrade in _upgrades)


{


foreach (Upgrade dependentUpgrade in upgrade.RequiredUpgrades)
{


if (!_upgrades.Contains(dependentUpgrade) && !


missingUpgrades.Contains(dependentUpgrade))
{


missingUpgrades.Add(dependentUpgrade);
}


}
}


return missingUpgrades.AsReadOnly();
}


}


</div>
<span class='text_page_counter'>(12)</span><div class='page_container' data-page=12>

theo có thể là tìm xem nâng cấp nào đã gây ra việc làm mất các nâng cấp khác, nói cách khac, bạn
phải lựa chọn 4 WheelDrive cùng với Traction Control của bạn ; tuy nhiên, chúng ta sẽ dừng lại ở
đây. Mục đích của phần trên chỉ là đưa ra cách chúng ta có thể bắt đầu và phần mở đầu có thể ra
sao.


<b>Giao diện người dùng (UI) </b>




Bạn có thể thấy rằng là chúng ta chưa hề nhắc tới UIs. Bởi vì lĩnh vực của ta độc lập với lớp
presentation – nó có thể dùng để làm mạnh website, ứng dụng windows, dịch vụ windows. Việc
cuối cùng mà bạn muốn làm là trộn lẫn lớp presentation của bạn và logic của lĩnh vực. Làm như
thế không những tạo ra những đoạn code khó đọc và khó kiểm tra mà cịn làm cho ta khơng thể tái
sử dụng logic của chúng ta giữa các UI (có thể đây khơng phải là vấn đề quá quan trọg, nhưng tính
dễ đọc và dễ bảo trì thì khác). Tiếc thay, nhiều nhà phát triển ASP.NET lại trộn lẫn UI và lớp của
lĩnh vực. Thậm chí tơi có thể nói rằng, chúng ta rất thường thấy những hoạt động trơng suốt q
trình của một hàm xử lý sự kiện ấn nút hay sự kiện tải trang – page load – trong ASP.NET. Những
khung trang ASP.NET điều khiển ASP.NET UI – không phải là hiện thực những hoạt động. Nút
Save không nên thông qua những quy tắc kinh doanh rắc rối (hoặc tệ hơn là truy cập cơ sỡ dữ liệu
một cách trực tiếp), mà mục đích của nó là sửa lại trang ASP.NET dựa trên kết quả của lớp lĩnh
vực – có thể nó nên thơng qua một trang khác, đưa ra vài tin báo lỗi hoặc yêu cầu thêm thông tin.
Hãy nhớ rằng bạn muốn viết những đoạn code liền lạc với nhau. Logic của ứng dụng ASP.NET
của bạn nên tập trung vào một việc và làm việc đó cho tốt – Tôi tin rằng sẽ không ai phản đối rằng
nếu một đối tượng cần phải quản lí page, điều đó có nghĩa là nó khơng thể thực thi chức năng liên
quan tới lĩnh vực.Thêm vào đó, đặt logic trongnhững file code behind thường vi phạm nguyên tắc
“Không tự lặp lại”, đơn giản chỉ vì việc tái sử dụng code trong file aspx.cs rất khó.


Chính vì những điều trên, bạn khơng thể chờ quá lâu để bắt đầu UI. Trước hết, ta nên lấy phản hồi
của khách hàng càng sớm và càng thường xuyên càng tốt. Tôi không cho rằng họ sẽ ấn tượng nếu
ta gửi cho họ một loạt file .cs/vb cùng với các class. Thứ hai, khi đưa lớp của lĩnh vực ra thực tế sẽ
giúp ta thấy được vài chỗ hỏng và vài điều khơng hợp lí. Ví dụ, sự ngưng kết nối tự nhiên của
trang web của nghĩa là ta phải chỉnh sửa đôi chút trong thế giới hướng đối tượng thuần khiết của
chúng ta để có được những trải nghiệm tốt hơn của người dùng. Theo kinh nghiệm của tơi thì unit
test cịn q hẹp để thấy được những khuyết tật này.


Bạn sẽ hài lòng khi biết rằng ASP.NET và WinForms xử lý những đoạn mã tập trung lĩnh vực
cũng tốt như những lớp tập trung dữ liệu. Bạn có thể ràng buộc dữ liệu – data binding với bất kì
tập hợp .NET nào và sử dụng các phiên - session và kỹ thuật đệm - cache như những gì bạn thường
làm, và làm bất cứ gì bạn vẫn hay làm. Trên thực tế, ngoài ra, ảnh hưởng đối với UI có thể là ít


nghiêm trọng nhất. Tất nhiên bạn không nên ngạc nhiên khi biết rằng các lập trình viên ALT.NET
cho rằng bạn nên mở rộng đầu óc khi làm việc với bộ cơng cụ hỗ trợ trình bày. ASP.NET Page
Framework khơng nhất thiết phải là công cụ tốt nhất cho công việc của bạn – phần đơng chúng tơi
cho rằng nó phức tạp một cách không cần thiết và cũng không bền vững. Chúng ta sẽ đề cập vấn
đề này kĩ hơn trong chương sau, nhưng nếu bạn muốn tìm hiểu rõ hơn, tơi khun bạn nên đọc về
MonoRails ( nó là khung cho .NET) hoặc khung giá MVC mới được Microsoft tung ra. Việc cuối
cùng tôi muốn là dành cho những người đã chán nản với sự rộng lớn của những thay đổi, vì vậy
bây giờ hãy trở lại đề tái chính.


<b>Bí quyết và thủ thuật</b>



Ta sẽ kết thúc chương này bằng việc lướt qua vài điều có ích mà ta có thể làm với class. Ta chỉ
mới bước trên bề mặt của tảng băng, nhưng hi vọng những thông tin này sẽ giúp bạn bước đi đúng
cách.


</div>
<span class='text_page_counter'>(13)</span><div class='page_container' data-page=13>

Chúng ta sẽ làm gì khi Khách Hàng mua một chiếc Xe mới? Rõ ràng ta cần tạo ra một đối tượng
kiều Xe và chỉ rõ mo-del. Một cách truyền thống để làm việc này là sử dụng hàm tạo – constructor
và đơn giản tạo ra đối tượng mới với từ khóa new. Một cách tiếp cận khác là sử dụng cách tiếp cận


<i>factory pattern để tạo đối tượng:</i>


public class Xe
{


private Model _model;


private List<Upgrade> _upgrades;
private Xe()


{



_upgrades = new List<Upgrade>();
}


public static Xe CreateCar(Model model)


{


Xe xe = new xe();
xe._model = model;
return xe;


}
}


Có hai ưu điểm cho cách tiếp cận này. Thứ nhất, ta có thể trả về một đối tượng rỗng, điều mà ta
không thể làm được với cách dùng constructor – điều này có thể hoặc khơng có ích cho bạn trong
vài trường hợp riêng. Thứ hai, nếu có nhiều cách để tạo ra đối tượng, bạn sẽ có nhiều cơ hội đề tạo
ra những tên hàm có ý nghĩa hơn. Ví dụ đầu tiên xuất hiện trong đầu là khi bạn muốn tạo ra một
đối tượng của class NgườiDùng, bạn có thể có NgườiDùng.XácĐịnhBởiThơngTin (string tên,
string mật-mã), NgườiDùng. XácĐịnhBởiId ( int id) và Người Dùng. XácĐịnhBởiVịTrí (string
vị-trí). Bạn có thể tạo được những hàm giống nhau với cách nạp chồng constructor, nhưng rất hiếm
khi tạo được sự rõ ràng như nhau. Thành thật mà nói, tơi ln phải rất khó khăn để chọn lựa cái
nào để sử dụng, vì thế đó là một vấn đề của lựa chọn và sở thích riêng.


<b>Chỉ định truy cập – Access Modifier</b>


Khi bạn tập trung viết những class đóng gói những hoạt động của nghiệp vụ, một API phong phú
sẽ được tạo ra để UI của bạn sử dụng. Giữ cho API trong sáng và rõ ràng luôn là một ý tưởng tốt.
Một cách đơn giản nhất là làm cho API nhỏ lại bằng cách giấu tất cả trừ những phương thức cần


thiết nhất. Vài phương thức rõ ràng phải public và một vài cái khác phải private, nhưng nếu bạn
không chắc chắn, hãy để quyền truy cập hạn chế và chỉ thay đổi khi cần thiết. Tôi ưa chuộng
phương thức internal trong phần lớn các phương thức và thuộc tính. Thành phần internal chỉ có thể
được thấy bởi các thành viên trong cùng một khối hợp ngữ - vì thế nếu bạn thực sự phân chia các
lớp của bạn giữa nhiều khối hợp ngữ (vốn thường là một ý tưởng hay), bạn sẽ thu nhỏ API đáng
kể.


<b>Interface</b>


Giao diện giữ một vai trò quan trọng trong việc giúp ta tạo ra những đoạn code dễ bảo trì. Ta sử
dụng nó để tách rời code cũng như tạo những class giả để dùng cho kiểm tra đơn vị - unit testing.
Một giao diện là một giao kèo trước mà các class thực thi phải tuân thủ. Giả sử chúng ta muốn tóm
gọn tất cả dữ liệu trong một class tên SqlServerDataAccess:


internal class SqlServerDataAccess
{


internal List<Upgrade> RetrieveAllUpgrades()
{


</div>
<span class='text_page_counter'>(14)</span><div class='page_container' data-page=14>

}


public void ASampleMethod()
{


SqlServerDataAccess da = new SqlServerDataAccess();
List<Upgrade> upgrades = da.RetrieveAllUpgrades();
}


Bạn có thể thấy rằng phần code mẫu ở cuối trong ví dụ trên có tham chiếu trực tiếp tới


SqlServerDataAccess – cũng giống như với những phương thức khác cần phải liên hệ với cơ sở
dữ liệu. Những đoạn code “cặp đôi” chặt chẽ này rất khó thay đổi cũng như khó kiểm tra. (ta
không thể kiểm tra ASampleMethod mà không biết đầy đủ phương thức RetrieveAllUpgrades).
Ta có thể làm giảm bớt sự liên hệ chặt chẽ này bằng cách lập trình một giao diện thay thế:


internal interface IDataAccess
{


List<Upgrade> RetrieveAllUpgrades();
}


internal class DataAccess
{


internal static IDataAccess CreateInstance()
{


return new SqlServerDataAccess();
}


}


internal class SqlServerDataAccess : IDataAccess
{


public List<Upgrade> RetrieveAllUpgrades()
{


return null; //todo implement
}



}


public void ASampleMethod()
{


IDataAccess da = DataAccess.CreateInstance();
List<Upgrade> upgrades = da.RetrieveAllUpgrades();
}


Ta đã giới thiệu giao diện cùng với lớp trợ giúp để đưa ra ví dụ cho giao diện đó. Nếu ta muốn
thay đổi phần thực thi, ví dụ như OracleDataAccess, đơn giản ta chỉ cần tạo ra class Oracle mới,
phải chắc rằng nó hiện thực giao diện, và thay đổilớp trợ giúpđể trả về nó. Thay vì phải thay đổi
hàng loạt (có thể lên tới hàng trăm chỗ), ta đơn giản chỉ cần thay đổi một chỗ.


Đây chỉ là một ví dụ đơn giản cho việc sử dụng giao diện để hỗ trợ chúng ta. Chúng ta có thể làm
cải thiện đoạn code hơn nữa bằng cách tạo ra những ví dụ của các class thơng qua dữ liệu cấu hình
hoặc tạo ra những khung được dành riêng cho cơng việc (đó chính xác là điều chúng ta chuẩn bị
làm). Chúng ta sẽ thường xuyên thiên về việc lập trình với các giao diện hơn là với các lớp thực
sự, vì thế nếu bạn chưa quen với điều đó hãy cố tìm đọc thêm.


<b>Che dấu thơng tin và tính đóng gói</b>


</div>
<span class='text_page_counter'>(15)</span><div class='page_container' data-page=15>

<b>Tổng kết chương</b>



</div>
<span class='text_page_counter'>(16)</span><div class='page_container' data-page=16>

<b>CHƯƠNG 3</b>

<b> Persistence </b>



CODD'S AIM WAS TO FREE PROGRAMMERS FROM HAVING TO
KNOW THE PHYSICAL STRUCTURE OF DATA. OUR AIM IS TO
FREE THEM IN ADDITION FROM HAVING TO



KNOW ITS LOGICAL STRUCTURE. – LAZY SOFTWARE


Trong chương trước, chúng ta đã thảo luận về DDD mà khơng nói nhiều đến cơ sở dữ liệu. Nếu
bạn đã thường lập trình với DataSet, bạn chắc chắn sẽ có nhiều câu hỏi về việc nó làm việc như thế
nào. DataSet rất lớn nên bạn cần phải cực kỳ cẩn thận. Trong chương này, chúng ta sẽ bắt đầu thảo
thuận quanh việc làm thế nào để giải quyết persistence sử dụng DDD. Chúng ta sẽ viết code để bắt
cầu giữa các đối tượng C# và các bảng SQL. Trong phần sau, chúng ta sẽ xem xét về các vấn đề
cao hơn (hai hướng mapping O/R khác nhau), như DataSet- giúp chúng ta làm nhiều việc phức tạp.
Chương này đóng lại vấn đề đã thảo luận từ trước và mở ra vấn đề mới với nhiều mẫu persistence.

<b>Gap</b>



Như chúng ta đã biết, chương trình chạy trên bộ nhớ và u cầu một khơng gian để lưu trữ hay
persist thông tin. Ngày nay, giải pháp lựa chọn là cơ sở dữ liệu quan hê. Persistence thực sự là một
chủ đề khá lớn trong lĩnh vực phá triển phần mềm bởi vì, khơng có sự giúp đỡ của các pattern và
cơng cụ thì đó khơng phải là cách đơn giản nhất để thực hiện thành cơng. Đối với lập trình hướng
đối tượng, thách thức được đưa ra với một cái tên: Object-Relational Impedance Mismatch. Khá
nhiều nghĩa cho rằng dữ liệu quan hệ (relational data) khơng ánh xạ hồn tồn với các đối tượng và
các đối tượng cũng không ánh xạ hoàn toàn. Microsoft cơ bản đã cố gắng lờ đi vấn đề này và tạo
đơn giản một biểu diễn quan hệ (relational representation) với code hướng đối tượng- một cách
tiếp cận khéo léo, nhưng khơng phải khơng có sai sót như hiệu suất kém, các lỗ hổng, khả năng
kiểm tra kém, rắc rối, và bảo trì kém. (Cơ sở dữ liệu hướng đối tượng, cái tốt nhất theo kiến thức
của tôi, cũng không thể làm giảm.)


Tốt hơn cách lờ đi vấn đề này, chúng ta có thể và nên đối mặt với nó. Chúng ta đối mặt với nó để
có thể tận chụng cái tốt nhất giữa hai world – các quy tắc business phức tạp được hiện thực trong
OOP và lưu trữ dữ liệu và lấy ra thong qua cơ sở dữ liệu quan hệ. Dĩ nhiên, chúng bắt cầu cho
chúng ta vượt qua gap. Nhưng gap thực sự là gì? Impedance Mismatch này là gì? Bạn hầu như
đang nghĩa rằng nó khơng thể khó hơn để việc đẩy dữ liệu quan hệ ra đối tượng và đưa chúng trở
lại các bảng. (Hầu hết là đúng trong bất cứ trường hợp nào… và bây giờ giả định nó ln là một


quá trình đơn giản).


<b>DataMapper</b>



</div>
<span class='text_page_counter'>(17)</span><div class='page_container' data-page=17>

<i>public class Upgrade</i>
<i>{</i>


<i>private int _id;</i>
<i>private string _name;</i>


<i>private string _description;</i>
<i>private decimal _price;</i>


<i>private List<Upgrade> _requiredUpgrades;</i>
<i>public int Id</i>


<i>{</i>


<i>get { return _id; }</i>


<i>internal set { _id = value; }</i>
<i>}</i>


<i>public string Name</i>
<i>{</i>


<i>get { return _name; }</i>
<i>set { _name = value; }</i>
<i>}</i>



<i>public string Description</i>
<i>{</i>


<i>get { return _description; }</i>
<i>set { _description = value; }</i>
<i>}</i>


<i>public decimal Price</i>
<i>{</i>


<i>get { return _price; }</i>
<i>set { _price = value; }</i>
<i>}</i>


<i>public List<Upgrade> RequiredUpgrades</i>
<i>{</i>


<i>get { return _requiredUpgrades; }</i>
<i>}</i>


<i>}</i>


Chúng ta vừa thêm vào các trường cơ bản bạn bạn muốn có trong class này. Tiếp theo, chúng ta sẽ
tạo một bảng mà nó nắm, persist hay nâng cấp thơng tin.


<i>CREATE TABLE Upgrades</i>
<i>(</i>


<i>Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,</i>
<i>[Name] VARCHAR(64) NOT NULL, </i>



<i>Description VARCHAR(512) NOT NULL, </i>
<i>Price MONEY NOT NULL,</i>


<i>)</i>


</div>
<span class='text_page_counter'>(18)</span><div class='page_container' data-page=18>

<i>internal class SqlServerDataAccess</i>
<i>{</i>


<i>private readonly static string _connectionString = "FROM_CONFIG"</i>
<i>internal List<Upgrade> RetrieveAllUpgrades()</i>


<i>{</i>


<i>//use a sproc if you prefer</i>


<i>string sql = "SELECT Id, Name, Description, Price FROM Upgrades";</i>
<i>using (SqlCommand command = new SqlCommand(sql))</i>


<i>using (SqlDataReader dataReader = ExecuteReader(command))</i>
<i>{</i>


<i>List<Upgrade> upgrades = new List<Upgrade>();</i>
<i>while (dataReader.Read())</i>


<i>{</i>


<i>upgrades.Add(DataMapper.CreateUpgrade(dataReader));</i>
<i>}</i>



<i>return upgrades;</i>
<i>}</i>


<i>}</i>


<i>private SqlDataReader ExecuteReader(SqlCommand command)</i>
<i>{</i>


<i>SqlConnection connection = new SqlConnection(_connectionString);</i>
<i>command.Connection = connection;</i>


<i>connection.Open();</i>


<i>return command.ExecuteReader(CommandBehavior.CloseConnection)</i>
<i>}</i>


<i>}</i>


ExecuteReader là một phương thức giúp giảm nhẹ các đoạn mã thừa của chúng ta.
RetrieveAllUpgrades chọn tất cả các upgrade và lưu chúng vào một danh sách thông qua


DataMapper. Hàm CreateUpgrade (xem ở dưới) là một đoạn code có thể tái sử dụng mà chúng ta
dùng để ánh xạ (map) thông tin upgrade vào cơ sở dữ liệu trong domain của chúng ta. Nó khơng
phức tạp vì mơ hình Domain và mơ hình dữ liệu là tương tự nhau.


<i>internal static class DataMapper</i>
<i>{</i>


<i>internal static Upgrade CreateUpgrade(IDataReader dataReader)</i>
<i>{</i>



<i>Upgrade upgrade = new Upgrade();</i>


<i>upgrade.Id = Convert.ToInt32(dataReader["Id"]); upgrade.Name = </i>
<i>Convert.ToString(dataReader["Name"]); upgrade.Description = </i>
<i>Convert.ToString(dataReader["Description"]); upgrade.Price = </i>
<i>Convert.ToDecimal(dataReader["Price"]);</i>


<i>return upgrade;</i>
<i>}</i>


</div>
<span class='text_page_counter'>(19)</span><div class='page_container' data-page=19>

Nếu chúng ta cần làm, chúng ta có thể tái sử dụng hàm CreateUpgrade lúc cần thiết. Ví dụ nếu
cần lấy thông tin upgrade bằng id hay price– cả hai là các phương thức mới trong lớp


SqlServerDataAccess.


Rõ ràng là chúng ta có thể ứng dụng logic giống như khi chúng ta muốn lưu trữ Upgrade objects
ngược trở lại.


Đây là một giải pháp có thể :


<i>internal static SqlParameter[] ConvertUpgradeToParameters(Upgrade upgrade)</i>
<i>{</i>


<i>SqlParameter[] parameters = new SqlParameter[4]; </i>


<i>parameters[0] = new SqlParameter("Id", SqlDbType.Int); </i>
<i>parameters[0].Value = upgrade.Id;</i>


<i>parameters[1] = new SqlParameter("Name", SqlDbType.VarChar, 64);</i>


<i>parameters[1].Value = upgrade.Name;</i>


<i>parameters[2] = new SqlParameter("Description", SqlDbType.VarChar, 512);</i>
<i>parameters[2].Value = upgrade.Description;</i>


<i>parameters[3] = new SqlParameter("Price", SqlDbType.Money);</i>
<i>parameters[3].Value = upgrade.Price;</i>


<i>return parameters;</i>
<i>}</i>


<b>Vấn đề</b>


Mặc dù thực tế chúng ta đã lấy những ví dụ rất đơn giản và phổ biến, chúng ta vấn chạy trong dreaded
impedance mismatch. Chú ý cách là tầng Data Access ( SqlServerDataAccess hay DataMapper)
không nắm nhiều tập hợp các RequiredUpgrades cần thiết. Bời vì một trong các điều nắm giữ
(handle) phức tạp nhất là các quan hệ. Trong domain world là các tham khảo ( hay tập các tham khảo)
đến các đối tượng khác; nơi mà relational world sử dụng các khóa ngoại. Sự khác nhau này là một trở
ngại bất biến. Việc sửa đổi khơng phải là q khó. Đầu tiên, chúng ta thêm một bảng join
many-to-many mà nó liên kết tới một upgrade với các upgrade khác mà chúng cần ( có thể là 0,1 hay nhiều
hơn nữa)


<i>CREATE TABLE UpgradeDepencies</i>
<i>(</i>


<i>UpgradeId INT NOT NULL, </i>


<i>RequiredUpgradeId INT NOT NULL,</i>
<i>)</i>



</div>
<span class='text_page_counter'>(20)</span><div class='page_container' data-page=20>

<i>internal List<Upgrade> RetrieveAllUpgrades()</i>
<i>{</i>


<i>string sql = @"SELECT Id, Name, Description, Price FROM Upgrades;</i>


<i>SELECT UpgradeId, RequiredUpgradeId FROM UpgradeDepencies";</i>
<i>using (SqlCommand command = new SqlCommand(sql))</i>


<i>using (SqlDataReader dataReader = ExecuteReader(command))</i>
<i>{</i>


<i>List<Upgrade> upgrades = new List<Upgrade>();</i>


<i>Dictionary<int, Upgrade> localCache = new Dictionary<int, Upgrade>();</i>
<i>while (dataReader.Read())</i>


<i>{</i>


<i>Upgrade upgrade = DataMapper.CreateUpgrade(dataReader);</i>
<i>upgrades.Add(upgrade);</i>


<i>localCache.Add(upgrade.Id, upgrade);</i>
<i>}</i>


<i>dataReader.NextResult();</i>
<i>while (dataReader.Read())</i>
<i>{</i>


<i>int upgradeId = dataReader.GetInt32(0);</i>



<i>int requiredUpgradeId = dataReader.GetInt32(1); </i>
<i>Upgrade upgrade;</i>


<i>Upgrade required;</i>


<i>if (!localCache.TryGetValue(upgradeId, out upgrade)</i>


<i>|| !localCache.TryGetValue(requiredUpgradeId, out required))</i>
<i>{</i>


<i>//probably should throw an exception</i>
<i>//since our db is in a weird state </i>
<i>continue;</i>


<i>}</i>


<i>upgrade.RequiredUpgrades.Add(requiredUpgrade);</i>
<i>}</i>


<i>return upgrades;</i>
<i>}</i>


<i>}</i>


Chúng ta kéo các thông tin extra join table cùng với câu query ban đầu và tạo một từ điển tìm kiếm
để truy cập nhanh chóng các upgrade bằng id của chúng. Tiếp thep là lặp thông qua join table, lấy
các upgrade thích hợp từ từ điển tìm kiếm và thêm chúng vào các tập hợp


Đó khơng phải là giải pháp tốt nhất nhưng làm việc tốt. Chúng ta có thể refactor hàm một ít để làm
nó dễ đọc hơn, nhưng bây giờ và trong trường hợp đơn giản này, chúng ta sẽ do the job.



<b>Các hạn chế</b>


Mặc dù chúng ta chỉ đang bắt đầu tìm kiếm việc ánh xạ (mapping), đáng để tìm những hạn chế mà
chúng ta tự đặt ra cho chính mình. Bạn một lần đi xuống còn đường tự viết loại code này mà nó có
thể thốt khỏi sự kiểm sốt nhanh chóng. Nếu muốn thêm các phương thức sắp xếp hay lọc chúng
ta phải viết SQL động hoặc phải viết rất nhiều phương thức. Chúng ta sẽ kết thúc việc viết một
nhóm các phương thức RetrieveUpgradeByX mà nó tương tự nhau.


</div>
<span class='text_page_counter'>(21)</span><div class='page_container' data-page=21>

Vấn đề quan trọng nhất là phải làm việc với id. Nếu chúng ta gọi RetrieveAllUpgrades hai lần,
chúng ta sẽ nhận được một phiên bản riêng biệt của mọi upgrade. Điều này có thể cho kết quả
mâu thuẫn:


<i>SqlServerDataAccess da = new SqlServerDataAccess();</i>
<i>Upgrade upgrade1a = da.RetrieveAllUpgrades()[0]; </i>
<i>Upgrade upgrade1b = da.RetrieveAllUpgrades()[0];</i>
<i>upgrade1b.Price = 2000;</i>


<i>upgrade1b.Save();</i>


Giá cả thay đổi để việc upgrade đầu tiên sẽ không bị phản chiếu đến một thể hiện trỏ tới bởi upgrade1a.
Trong một số trường hợp đó khơng phải là một vấn đề. Tuy nhiên, trong nhiều trường hợp, bạn sẽ
muốn tầng Data Acess theo dõi id của các thể hiện (instance) mà nó tạo ra và thực hiện một số điều
khiển (bạn có thể đọc thêm bằng cách tìm kiếm mẫu Identify Map).


Chắc chắn là có thêm một số hạn chế, nhưng điều cuối chúng ta sẽ nói về việc phải làm việc với các
Unit of work (một lần nữa bạn có thể đọc thêm bằng cách tra Google với cụm Unit of Work). Khi bạn
viết code trên tầng Data Access, bạn cần chắc chắn rằng khi bạn persist một đối tượng, bạn cũng persist
tất cả các đối tượng tham khảo được cập nhật, nếu cần thiết. Nếu bạn đang làm việc trên phần admin
của hệ thống bán xe hơi, bạn có thể tạo một Model mới và tạo một Upgrade. Nếu bạn gọi Save trên


Model của bạn, bạn cần chắc chắn là Updare của bạn cũng được lưu. Giải pháp đơn giản nhất là gọi
Save thường xuyên với mỗi hành động - nhưng cảThe simplest solution is to call save often for each
individual action – nhưng điều này vừa khó (các quan hệ có thể sâu vài cấp) và không hiệu quả. Một
cách tương tự bạn có thể chỉ thay đổi một ít các thuộc tính và sau đó phải quyết định giữa việc lưu lại
tất cả các trường hay theo dõi sự thay đổi các thuộc tính và chỉ cập nhật chúng. Một lần nữa, đối với các
hệ thống nhỏ, đây không phải là vấn đề lớn. Đối với các hệ thống lớn hơn, gần như là bất khả thi để
thực hiện bằng tay (ngoài ra, tốt hơn là việc tốn thời gian xây dựng Unit of work của bạn, có thể bạn
nên viết các chức năng yêu cầu của client).


<b>Tổng kết chương</b>



</div>
<span class='text_page_counter'>(22)</span><div class='page_container' data-page=22>

<b>Dependency Injection </b>



I WOULD SAY THAT MODERN SOFTWARE ENGINEERING IS THE
ONGOING REF INEMENT OF THE EVER-INCREASING DEGREES OF
DECOUPLING. YET, WHILE THE HISTORY OF SOFTWARE SHOWS
THAT COUPLING IS BAD, IT ALSO SUGGESTS THAT COUPLING IS
UNAVOIDABLE. A N ABSOLUTELY DECOUPLED APPLICATION IS
US ELESS BECAUSE IT ADDS NO VALUE. DEVELOPERS CAN ONLY
ADD VALUE BY COUPLING THINGS TOGETHER. THE VERY ACT
OF WRITING CODE IS COUPLING ONE THING TO ANOTHER. THE
REAL QUESTION IS HOW TO WISELY CHOOSE WHAT TO BE
COUPLED TO. - JUVAL LÖWY


t’s common to hear developers promote layering as a means to provide
extensibility. The most common example, and one I used in Chapter 2 when
we looked at interfaces, is the ability to switch out your data access layer in
order to connect to a different database. If your projects are anything


like mine, you know upfront what database you’re going to use and you know you aren’t


going to have to change it. Sure, you could build that flexibility upfront - just in case - but
what about keeping things simple and You Aren’t Going To Need IT (YAGNI)?


I used to write about the importance of domain layers in order to have re-use across multiple
presentation layers: website, windows applications and web services. Ironically, I’ve rarely
<i><b>had to write multiple front-ends for a given domain layer. I still think layering is important, </b></i>
but my reasoning has changed. I now see layering as a natural by-product of highly cohesive
code with at least some thought put into coupling. That is, if you build things right, it should
automatically come out layered.


The real reason we’re spending a whole chapter on decoupling (which layering is a high-level
implementation of) is because it’s a key ingredient in writing testable code. It w asn’t until I
started unit testing that I realized how tangled and fragile my code was. I quickly became
frustrated because method X relied on a function in class Y which needed a database up and
running. In order to avoid the headaches I went through, we’ll first cover coupling and then
look at unit testing in the next chapter.


(A point about YAGNI. While many developers consider it a hard rule, I rather think of it as a
general guideline. There are good reasons why you want to ignore YAGNI, the most obvi ous
is your own experience. If you know that something will be hard to implement later, it might
be a good idea to build it now, or at least put hooks in place. This is something I frequently do
with caching, building an ICacheProvider and a NullCacheProvider implementation that does
nothing, except provide the necessary hooks for a real implementation later on. That said, of
the numerous guidelines out there, AY G NI , RD Y a nd S u s t a i n a b l e P a c e a re easily the three I
consider the most important.)


</div>
<span class='text_page_counter'>(23)</span><div class='page_container' data-page=23>

<b>Sneak Peak trong Unit Testing</b>



Nói về việc bắt cặp có liên quan đến kiểm tra đơn vị (unit testing) thì cũng tương tự như vấn đề con gà
và quả trứng – cái nào có trước. Tơi nghĩ tốt nhất là nên tiếp tục với kiểm tra đơn vị. Điều quan trọng


nhất đó là kiểm tra đơn vị thì chỉ bao gồm về đơn vị. Bạn khơng nên tập trung kiểm tra từ đầu đến cuối,
chỉ nên tập trung vào thuộc tính độc lập. Ý tưởng là nếu bạn kiểm tra mỗi thuộc tính qua mỗi phương
pháp và kiểm tra tính tương tác của chúng với những cái khác, toàn bộ hệ thống của bạn sẽ cứng nhắc.
Phương pháp mà bạn muốn dùng để kiểm tra đơn vị có thể có sự độc lập với những lớp khác, những cái
mà không thể dễ dàng thực thi trong vòng nội dung của một sự kiểm tra (ví dụ như cơ sở dữ liệu, hay
một thành phần của trình duyệt web). Vì lý do này, kiểm tra đơn vị sử dụng những lớp giả (mock) – hay
lớp tưởng tượng.


Xem ví dụ sau, lưu trạng thái của xe hơi:


<i>public class Car</i>
<i>{</i>


<i>private int _id;</i>
<i>public void Save()</i>
<i>{</i>


<i>if (!IsValid())</i>
<i>{</i>


<i>//todo: come up with a better exception</i>


<i>throw new InvalidOperationException("The car must be in a valid </i>
<i>state");</i>


<i>}</i>


<i>if (_id == 0)</i>
<i>{</i>



<i>_id = DataAccess.CreateInstance().Save(this);</i>
<i>}</i>


<i>else</i>
<i>{</i>


<i>DataAccess.CreateInstance().Update(this);</i>
<i>}</i>


<i>}</i>


<i>private bool IsValid()</i>
<i>{</i>


<i>//todo: make sure the object is in a valid state </i>
<i>return true;</i>


<i>}</i>
<i>}</i>


Để kiểm tra hiệu quả phương thức Save, có 3 điều chúng ta phải làm:


1. Chắc chắn rằng exception được ném ra khi cố lưu một chiếc xe là trạng thái hợp lệ


2. Chắc chắn rằng phương thức Save của tầng Data Access được gọi khi có một chiếc xe mới
3. Chắc chắn rằng Updatemethod đuợc gọi khi có một chiếc xe tồn tại.


</div>
<span class='text_page_counter'>(24)</span><div class='page_container' data-page=24>

khái niệm về Save và Update, chắc chắn rằng những argument chính xác phải được kiểm tra và
đạt, và trả về bất cứ giá trị nào chúng ta mong muốn. Mocking framework thì khá thú vị và hữu
ích bởi vì đoạn code của bạn sẽ chặt chẽ theo từng cặp.



<b>Don’t avo id Coupling lik e the Plag ue</b>



Trong trường hợp bạn quên những gì đã nói ở chương 1, bắt cặp (coupling) thì đơn giản là những
gì những ta gọi khi một lớp địi hỏi một lớp khác để hoạt động. Điều quan trọng nhất của đoạn code
là sự độc lập với những lớp khác. Nếu bạn viết string site = “CodeBetter”, bạn đã thực hiện việc
bắt cặp với lớp System.String – nếu nó thay đổi, đoạn code của bạn có khả năng break rất cao. Dĩ
nhiên, điều đầu tiên bạn nên biết đó là trong những trường hợp đa số, ví dụ như những ví dụ với
chuỗi, bắt cặp không phải là một điều không tốt.


Chúng ta không muốn tạo các interface và provider cho mỗi và mọi lớp. Có thể chấp nhận
cho lớp Car giữ một tham khảo trực tiếp của lớp Upgrade. Điều không thể được là bất cứ sự bắt cặp
nào tới thành phần bên ngoài ( dữ liệu, state server, cache server, dịch vụ web), bất cứ đoạn code
nào đòi hỏi thiết lập mở rộng ( khung dữ liệu) và, như tơi đã nói ở project trước, bất cứ đoạn code
nào phát triển output ngẫu nhiên (password, key). Điều đó có thể là một sự miên tả gây nhầm lẫn,
nhưng sau chapter này và chapter sau,l và một khi bạn đã sử dụng unit testing, bạn sẽ cảm thấy
rằng điều gì nên làm và điều gì nên tránh.


Sẽ là một ý kiến hay nếu bạn tách cặp dữ liệu từ domain, chúng ta sẽ có một ví dụ trong chương
này.


<b>Dependency Injection</b>



Trong chương 2, chúng ta đã thấy được giao diện có thể giúp cho trườngause của chúng ta như
thế nào – tuy nhiên, đoạn code được cúng cấp khơng cho phép chúng ta có thể cung cấp động
một hiện thực giả của IdataAccess cho lớp DataAccessfactory để trả về.Nói cách khác để giải
quyết điều này, chúng ta sẽ dựa vào một pattern gọi là Dependency Injection (DI). DI là một
giải pháp cụ thể cho tình huống này, bởi vì, như tên gọi, đó là một pattern chuyển một


dependency được code cứng thành được đưau vào trong lúc chạy. Chúng ta sẽ xem xét 2 mẫu DI,


một cái chúng ta có thể tự làm, một cái dựa vào một thư viện bên thứ ba third party library.


<b>Constructor Injection</b>


</div>
<span class='text_page_counter'>(25)</span><div class='page_container' data-page=25>

<i>internal interface IDataAccess</i>
<i>{</i>


<i>int Save(Car car);</i>
<i>void Update(Car car);</i>
<i>}</i>


<i>internal class MockDataAccess : IDataAccess</i>
<i>{</i>


<i>private readonly List<Car> _cars = new List<Car>();</i>
<i>public int Save(Car car)</i>


<i>{</i>


<i>_cars.Add(car);</i>
<i>return _cars.Count;</i>
<i>}</i>


<i>public void Update(Car car)</i>
<i>{</i>


<i>_cars[_cars.IndexOf(car)] = car;</i>
<i>}</i>


<i>}</i>



Mặc dù chức năng upgrade mock của chúng có thể được cải thiện, nó sẽ được làm sau. Chỉ
có một sự thay đổi nhỏ so với lớp Car nguyên bản:


<i>public class Car</i>
<i>{</i>


<i>private int _id;</i>


<i>private IDataAccess _dataProvider;</i>


<i>public Car() : this(new SqlServerDataAccess())</i>
<i>{</i>


<i>}</i>


<i>internal Car(IDataAccess dataProvider)</i>
<i>{</i>


<i>_dataProvider = dataProvider;</i>
<i>}</i>


<i>public void Save()</i>
<i>{</i>


<i>if (!IsValid())</i>
<i>{</i>


<i>//todo: come up with a better exception</i>



<i>throw new InvalidOperationException("The car must be in a valid </i>
<i>state");</i>


<i>}</i>


</div>
<span class='text_page_counter'>(26)</span><div class='page_container' data-page=26>

<i>_id = _dataProvider.Save(this);</i>
<i>}</i>


<i>else</i>
<i>{</i>


<i>_dataProvider.Update(this);</i>
<i>}</i>


<i>}</i>
<i>}</i>


Hãy nhìn vào đoạn code trên và cả bên dưới. Chú ý rằng việc sử dụng con trỏ có nghĩa là DI khơng
có ảnh hưởng gì đến đoạn code hiện hành – Nếu bạn chọn không thêm vào IdataAccess. Nếu bạn
muốn thêm vào một thuộc tính, ví dụ như trong MockDataAccess, ta có thể:


public void AlmostATest()
{


Car car = new Car(new MockDataAccess());
car.Save();


if (car.Id != 1)
{



//something went wrong
}


}


Có những sự khác nhau nho nhỏ, chúng ta có thể thêm vào IdataAccess trực tiếp từ phương pháp
SAVE hay có thể thiết lập private _dataAccess thơng qua một thuộc tính nội.


<b>Framework</b>


Doing DI manually works great in simple cases, but can become unruly in more complex
situations. A recent project I worked on had a number of core components that needed to be
injected – one for caching, one for logging, one for a database acce ss and another for a web
service. Classes got polluted with multiple constructor overloads and too much thought had
to go into setting up classes for unit testing. Since DI is so critical to unit testing, and most
unit testers love their open-source tools, it should come as no surprise that a number of
frameworks exist to help automate DI. The rest of this chapter will focus on StructureMap,
an open source Dependency Injection framework created by fellow CodeBetter blogger
Jeremy Miller. ( h t t p : / / s t r u c tu r e m a p. s ou r c e f o r g e . n e t / )


Trước khi sử dụng StructureMap bạn phải cấu hình nó sử dụng một file XML (được gọi là


</div>
<span class='text_page_counter'>(27)</span><div class='page_container' data-page=27>

<i><StructureMap></i>
<i><DefaultInstance</i>


<i>PluginType="CodeBetter.Foundations.IDataAccess, CodeBetter.Foundations" </i>
<i>PluggedType="CodeBetter.Foundations.SqlDataAccess, CodeBetter.Foundations"/></i>
<i></StructureMap></i>


Trong khi tôi không muốn dành quá nhiều thời gian để nói về configuration, có một điều quan trọng cần


lưu ý là XML file phải được sử dụng trong thư mục bin của ứng dụng của bạn. Bạn có thể tự động để
VS.NET làm việc này bằng cách chọn file, mục properties và thiết lập Copy to Output Directory thuộc
tính Copy Always. ( Có một loạt option Advanced configuraion. Nếu bạn thấy hứng thú học hỏi hơn, tôi
đề nghị bạn hãy vào StructureMap website)


Một khi đã configurate, bạn có thể undo tất cả những thay đổi thực hiện ở lớp Car để cho phép
việc thêm vào con trỏ (gỡ bỏ the_dataProvider và tất cả những con trỏ). Để có thể có được việc
thêm vào IDataAccess chính xác, chúng ta chỉ cần yêu cầu StrutureMap, phương pháp Save bây
giờ trông như thế này::


<i>public class Car</i>
<i>{</i>


<i>private int _id;</i>
<i>public void Save()</i>
<i>{</i>


<i>if (!IsValid())</i>
<i>{</i>


<i>//todo: come up with a better exception</i>


<i>throw new InvalidOperationException("The car must be in a valid </i>
<i>state");</i>


<i>}</i>


<i>IDataAccess dataAccess = ObjectFactory.GetInstance<IDataAccess>();</i>
<i>if (_id == 0)</i>



<i>{</i>


<i>_id = dataAccess.Save(this);</i>
<i>}</i>


<i>else</i>
<i>{</i>


<i>dataAccess.Update(this);</i>
<i>}</i>


</div>
<span class='text_page_counter'>(28)</span><div class='page_container' data-page=28>

Để sử dụng mock hơn là việc thêm vào chuẩn, chúng ta chỉ cần thêm mock vào StructureMap:


<i>public void AlmostATest()</i>
<i>{</i>


<i>ObjectFactory.InjectStub(typeof(IDataAccess), new MockDataAccess()); Car </i>
<i>car = new Car();</i>


<i>car.Save();</i>
<i>if (car.Id != 1)</i>
<i>{</i>


<i>//something went wrong</i>
<i>}</i>


<i>ObjectFactory.ResetDefaults();</i>
<i>}</i>


Chúng ta sử dụng InjectStub để mà những lệnh gọi sau tới GetInstance trả về mock, và hãy chắc chắn


rằng reset tất cả mọi thứ trở về bình thường thơng qua ResetDefaults..


DI frameworks như StructureMap thì rất dễ sử dụng và cũng rất hữu ích. Với một cặp lệnh
configuration và một vài thay đổi nho nhỏ trong đoạn code của chúng ta, chúng ta có thể giảm
đáng kể việc bắt cặp và tăng khả năng kiểm tra. Trước đây, tôi đã từng giới thiệu StructureMap
với một luợng lớn codebase, tuy nhiên tầm ảnh hưởng thì khơng đáng kể..


<b>Sự cải tiến cuối cùng</b>


Bằng việc giới thiệu lớp IDataAccess cũng như việc sử dụng DI Framework, chúng ta đã giải quyết
việc gỡ bỏ nhiều việc bắt cặp dở trong đoạn code ví dụ. Chúng ta có thể thực hiện tiếp vài bước nữa,
thậm chí tới một điểm mà có thể mang lại hại nhiều hơn lợi. Tuy nhiên, có một sự phụ thuộc mà tôi
muốn tránh đi - những object bussiness thì thường khơng biết về việc thêm vào DI. Thay vì gọi
StructureMap's ObjectFactory trực tiếp, chúng ta sẽ thêm vào nhiều cấp độ gián tiếp:


<i>public static class DataFactory</i>
<i>{</i>


<i>public static IDataAccess CreateInstance</i>
<i>{</i>


get
{
}
}


</div>
<span class='text_page_counter'>(29)</span><div class='page_container' data-page=29>

return ObjectFactory.GetInstance<IDataAccess>();


</div>
<span class='text_page_counter'>(30)</span><div class='page_container' data-page=30>

<b>Tổng kết chương</b>




</div>
<span class='text_page_counter'>(31)</span><div class='page_container' data-page=31>

<b>CHƯƠNG 5</b>

<b> Unit Testing </b>



Chúng ta sẽ không chọn unit testing nếu nó khơng cho chúng ta những lợi ích
hơn.


- (RANDOM CONSULTANCY COMPANY)


Trong suốt quyển sách này, chúng ta đã nói về tầm quan trọng của khả năng kiểm
tra(testability ) và chúng ta cũng đã xem qua nhiều kĩ thuật để làm đơn giản hóa việc
kiểm tra hệ thống. Khơng cần phải nói rằng lợi ích lớn của việc viết test cho hệ thống của
chúng ta là khả năng mang tới sản phẩm tốt hơn cho khách hàng. mặc dù điều này thì
cũng đúng cho unit testing, nhưng lý do mà tôi viết unit test là khơng có gì có thể tới gần
hơn tới việc cải thiện khả năng quản lý của hệ thống nhiều hơn việc phét triển suite cho
unit test, Bạn sẽ thường nghe rằng unit testing hỗ trợ speak của unit test - và đó thực sự là
cái nó bao gồm. Dựa trên 1 dự án mà tôi đang làm, chúng ta sẽ tiếp tục thực hiện thay đổi
đẻ cải thiện hệ thống ( thay đổi về chức năng, performance, refactoring). Nếu đó là một
hệ thống lớn, chúng ta thỉnh thoảng sẽ được yêu cầu tạo ra những sự thay đổi có thể làm
chúng ta phát hoảng. Liệu có thể làm được? Liệu nó sẽ tạo ra những hiệu ứng dữ dội?
Những lỗi gì sẽ được đưa ra? Nếu khơng có unit test, chúng ta sẽ từ chối tạo ra những sự
thay đổi cao hơn. Nhưng chúng ta biết rằng, và khách hàng của chúng ta cũng biết, rằng
những sự thay đổi với nguy cơ cao sẽ là những cái mang lại thành cơng lớn. Nếu có hơn
100 unit test chạy trong vịng vài phút, chúng ta có thể tách rời component, sắp xếp code
và xây dựng tính năng mà chúng ta không bao giờ nghĩ đến 1 năm trước, và cũng khơng
cần phải lo lắng gí về nó. Bởi vì chúng ta tự tin rằng với sự hồn chỉnh của unit test,
chúng ta biết rằng chúng nó khơng đưa ra lỗi.


Kiểm tra đơn vị không chỉ làm nhẹ bớt các thay dổi rủi ro cao. Trong cuộc đời lập trình,
tơi đã từng nhiều lần xử lý các lỗi quan trọng gây nên bởi những sự thay đổi có vẻ mang
rủi ro thấp. Một điểm là tơi cần tạo một thay đổi cơ bản cho hệ thống của chúng ta, click
chuột phải lên solution, chọn “Run Test” và trong 2 phút sẽ biết chúng ta đang ở đâu. Tôi


không thể đơn giản nhấn mạnh kiểm tra đơn vị quan trọng như thế nào. Chắc chắn rằng
chúng hữu ích để tìm kiếm lỗi và kiểm tra những code của tơi có nên vậy hay khơng,
nhưng quan trọng hơn là khả năng ma thuật để phát hiện lỗi nghiêm trọng trong thiết kế
của một hệ thống. Tơi phấn khích bất cứ khi nào lướt ngang một phương thức hay hành
vi mà khó để kiểm tra. Nghĩa là tơi tìm thấy một thiếu sót trong một phần cơ bản của hệ
thống. Một cách tương tự, bất cứ khi nào tôi đặt cùng 1 test vào 1 cặp giây cho một điều
nào đó mà tơi khá chắc chắn là sẽ khó, tơi biết một số người trong team viết code có lẽ sử
dụng lại từ dự án khác.


<b>Tại sao tôi đã không dùng Unit Test trong 3 năm trước?</b>



Với những người đã tìm thấy được tiện lợi của unit testing, thật là khó hiểu tại sao mọi
người lại khơng sử dụng nó. Với những người chưa chấp nhận nó, bạn chắc chắn muốn
chúng ta sẽ chấm dứt nói về điều đó. Trong nhiều năm tơi đã đọc blog và nói chuyện với
những đồng nghiệp đã từng làm việc trên unit test, nhưng chưa từng tự tay thực hành lần
nào.


</div>
<span class='text_page_counter'>(32)</span><div class='page_container' data-page=32>

1. Tơi đã có quan niệm sai về mục đích của unit testing. Như tơi đã từng nói, unit testing
cải thiện chất lượng của hệ thống, nhưng nó thật sự làm dễ dàng để chuyển hoặc bảo trì
hệ thống sau này. Hơn thế nữa, nếu bạn tiến đến bước tiếp theo và adopt Test Driven
Development (TDD), unit testing thật sự trở thành phần thiết kế. Theo diễn giải của Scott
Bellware, TDD không phải là testing bởi vè bạn không phải suy nghĩ như một tester khi
thực hiện TDD mà bạn suy nghĩ như một designer.


2. Giống như nhiều người, tôi thường nghĩ những người phát triển phần mềm thì khơng
nên viết những phần test. Tôi không biết câu chuyện đằng sau sự tin tưởng này, nhưng
hiện tại tơi nghĩ nó là một cái cớ của những lập trình viên kém. Testing là một quá trình
của cả tìm lỗi trong một hệ thống cũng như xác thực rằng hệ thống làm việc như mong
đợi. Có lẽ các nhà phát triển phần mềm khơng đươc giỏi trong việc tìm lỗi trong code
của họ, nhưng họ là những người thích hợp nhất để chắc chắn rằng hệ thống làm việc


theo hướng mà họ mong muốn và người sử dụng là người thích hợp để kiểm tra rằng nó
là việc như nó nên làm.Thậm chí nếu unit testing khơng thật sự là testing đi chăng nữa,
thì những người phát triển phần mềm nào mà không tin rằng họ nên test code thì thật sự
là người khơng có trách nhiệm.


3. Testing chẳng có gì vui vẻ. Ngồi trước màn hình, nhập dữ liệu và chắc chắn mọi thứ
đều ok. Nhưng unit testing lại là coding, cũng có nghĩa là có rất nhiều trở ngại để thành
cơng. Đơi khi, cũng như coding, testing cũng rất bình thường, nhưng nhìn chung nó
chẳng khác gì việc lập trình bạn làm hằng ngày.


4. Nó tốn thời gian. Vài người sẽ nói rằng unit testing khơng hề tốn thời gian, mà nó tiết
kiệm thời gian cho bạn. Đó là sự thật đúng vì thời gian bạn bỏ ra để viết unit test chỉ là
một phần nhỏ so với thời gian bạn giành để đổi các yêu cầu và chỉnh lỗi. Thành thật mà
nói, unit testing cũng tốn khá nhiều thời gian (đặc biệt khi bạn chỉ mới bắt đầu). Bạn có
thể khơng có đủ thời gian cho unit test hoặc khách hang của bạn có thể cảm thấy khơng
được thoả mãn. Trong những trường hợp này, tôi khuyên bạn xác định phần code then
chốt nhất và test nó, thậm chí có phải trải qua hằng giờ để viết unit test cũng có thể có
một ảnh hưởng lớn.


Rốt cuộc, unit testing trơng giống như một vật phức tạp và huyền bí, chỉ được sử dụng
trong những trường hợp cần thiết. Những lợi ích được xem là khơng thể đạt tới đươc và
thời gian dường như không cho phép cho nó. Nó cũng phải luyện tập rất nhiều (ti cũng
đã có thời gian khó khăn để học về unit test và cách để dung nó), nhưng những lợi ích
hầu như có thể thấy được trưc tiếp.


<b>The Tools </b>



Với StructureMap đã được giới thiệu ở chương trước, chúng ta chỉ cần phải thêm 2
frameworks và 1 tool vào unit testing framework: nUnit, RhinoMocks và



TestDriven.NET.


TestDriven.NET là 1 add-on của Visual Studio, nó đã thêm sẵn tuỳ chọn “Run Test” vào
menu ngữ cảnh (context menu) của chúng ta nhưng chúng ta sẽ khơng tốn thời gian để
nói về nó. Personal License của TestDriven.NET chỉ có hiệu lực cho mã nguồn mở và
người dung thử. Tuy nhiên, khơng cần phải lo lằng về license nếu nó khơng thích hợp cho
bạn, nUnit có cơng cụ chạy test của riêng nó, chỉ là nó khơng được tích hợp vào VS.NET
(Resharper - người dung cũng có thể sử dụng chức năng built-in của nó).


</div>
<span class='text_page_counter'>(33)</span><div class='page_container' data-page=33>

RhinoMocks là mocking framework chúng ta sẽ sử dụng. Trong phần trước, chúng ta tạo
mock 1 cách thủ cơng – nó vừa có nhiều giới hạn, vừa tốn thời gian. RhinoMocks sẽ tự
động tạo lớp mock từ một giao diện và cho phép chúng ta thẩm tra và điều khiển tương
tác với nó.


<b>nUnit </b>


Điều đầu tiên phải làm là thêm liên kết vào nunit.framework.dll va Rhino.Mocks.dll.
Theo sở thích của riêng tôi là đặt các unit tests vào bộ phận riêng của chúng. Ví dụ, nếu
lớp domain của tôi được đặt trong CodeBetter.Foundations, tôi sẽ tạo 1 bộ phận mới gọi
là CodeBetter.Foundations.Tests. Nó có nghĩa là chúng ta sẽ khơng có thể test những
phương thức private. Trong .NET 2.0+, chúng ta có thể sử dụng


InternalsVisibleToAttribute để cho phép để cho phép bộ phận Test có thể truy cập vào
các phương thức bên trong (mở Properties/AssemblyInfo.cs và thêm


<i>[assembly:InternalsVisibleTo(“CodeBetter.Foundations.Test”)]</i>


đó là 1 vài thứ mà tơi phải làm.)


Có 2 việc bạn cần phải biết về nUnit. Đầu tiên, bạn điều chỉnh việc test băng cách sử


dụng các thuộc tính. TestFixtureAttribute được áp dụng vào class chứa việc kiểm tra của
bạn, thiết lập và teardown phương thức. SetupAttribute được áp dụng vào các phương
thức mà bạn muốn thực hiện trước mỗi lần test - bạn sẽ không thường xuyên cần chúng.
Tương tự, TearDownAttribute được áp dụng vào các phương thức bạn muốn thực thi sau
khi test. Cuối cùng, TestAttribute được áp dụng vào các unit test của bạn (cịn có các
thuộc tính khác, nhưng đây là 4 thuộc tính quan trọng nhất). Nó sẽ trơng giống như:


<i>using NUnit.Framework; </i>
<i>[TestFixture] </i>


<i>public class CarTests </i>
<i>{ </i>


<i> [SetUp] </i>


<i> public void SetUp() { //todo } </i>
<i> [TearDown] </i>


<i> public void TearDown(){ //todo } </i>
<i> [Test]</i>


<i>public void SaveThrowsExceptionWhenInvalid(){ //todo } </i>
<i> [Test] </i>


<i> public void SaveCallsDataAccessAndSetsId(){ //todo } </i>
<i> //more tests </i>


<i>} </i>


Chú ý rằng mỗi unit test có một tên rõ ràng - nó rất quan trọng để phát biểu chính xác


việc test sẽ làm gì, và vì thế mỗi test của bạn khơng nên làm q nhiều việc, bạn sẽ hiếm
khi có những cái tên dài.


Điều thứ hai cần phải biết về nUnit là bạn xác thực việc kiểm tra của bạn thực thi như
mong muốn thông qua việc sử dụng lớp Assert và rất nhiều phương thức của nó. Tơi biết
điều này là khơng thoả đáng, nhưng nếu chúng ta có một phương thức


nhận đối số int[] và trả về tổng của nó, unit test của chúng ta sẽ trơng như sau:


<i>[Test] </i>


<i>public void MathUtilityReturnsZeroWhenNoParameters() </i>
<i>{ </i>


<i> Assert.AreEqual(0, MathUtility.Add()); </i>
<i>} </i>


</div>
<span class='text_page_counter'>(34)</span><div class='page_container' data-page=34>

<i>public void MathUtilityReturnsValueWhenPassedOneValue() </i>
<i>{ </i>


<i> Assert.AreEqual(10, MathUtility.Add(10)); </i>
<i>} </i>


<i>[Test] </i>


<i>public void MathUtilityReturnsValueWhenPassedMultipleValues() </i>
<i>{ </i>


<i> Assert.AreEqual(29, MathUtility.Add(10,2,17)); </i>
<i>} </i>



<i>[Test] </i>


<i>public void MathUtilityWrapsOnOverflow() </i>
<i>{ </i>


<i> Assert.AreEqual(-2, MathUtility.Add(int.MaxValue, int.MaxValue)); </i>
<i>}</i>


Bạn sẽ không thể nào biết về chúng từ ví dụ trên, nhưng lớp Assert có nhiều hơn 1
<i>hàm,như là: Assert.IsFalse, Assert.IsTrue, Assert.IsNull, Assert.IsNotNull, </i>


<i>Assert.AreSame, Assert.AreNotEqual, Assert.Greater, Assert.IsInstanceOfType, ....</i>


<b>Unit Test là gì?</b>



Unit tests là phương thức dùng để kiểm tra cách hoạt động ở múc rất chi tiết. Người phát
triển mới với việc unit test thường để phạm vi kiểm tra của họ tăng khá lớn. Hầu hết unit
test cho phép các thành phần giống nhau: thực thi vài code từ hệ thống của bạn và xác
nhận nó hoạt động như mong muốn. Mục đích của unit test là để kiểm chứng các hoạt
động đặc biệt. Nếu chúng ta viết test cho phương thức Save của lớp Car, chúng ta sẽ
không viết test chứa hết tất cả mà chúng ta sẽ viết một test cho mỗi tác vụ mà nó chứa -
thất bại khi đối tượng trong trạng thái invalid, gọi đến phương thức Save của lớp Data
Access , thiết lập ID và gọi phương thức Update của lớp Data Access. Một điều quan
trọng là unit test của chúng ta có thể chỉ ra lỗi. Tôi chắc rằng một vài người trong số các
bạn sẽ thấy 4 tests được dung để cover phương thức MathUtility.Add là dư thừa. Bạn có
thể nghĩ rằng 4 tests đó có thể nhóm lại thành một – và trong trường hợp nhỏ này, tơi sẽ
nói rằng bất cứ thứ gì bạn thích hơn. Tuy nhiên, khi tôi bắt đầu sử dụng unit test, tôi đã
rơi vào thói quen xấu là để phạm vi test khá lớn. Test của tôi sẽ tạo một đối tượng, thực
thi vài thành phần và xác nhận chức năng. Nhưng như tơi vẫn thường nói, khi tơi vẫn cịn


ở đây, tôi sẽ qua 1 cách tốt đẹp trong việc xác nhận thêm để chắc rằng các field này được
thiết đặt đúng theo cách mà chúng phải được làm. Nó rất nguy hiểm bởi vì một vài thay
đổi trong code của bạn có thể phá vỡ hang loạt các test khơng lien quan với nhau - dứt
khốt rằng dấu hiệu mà bạn đưa cho test của bạn phải tập trung trong một phạm vi rất
nhỏ.


Nó mang chúng ta quay lại với việc kiểm tra với các phương thức private. Nếu bạn
google, bạn sẽ tìm thấy khá nhiều thảo luận về chủ đề này, nhưng nhất trí chung là bạn
khơng nên kiểm tra các phương thức private. Tôi nghĩ 1 lý do thuyết phục để khơng test
các phương thức private là mục đích của chúng ta không phải là để kiểm tra phương thức
hay từng dòng code, mà là để kiểm tra cách hoạt động. Đó là điều mà các bạn phải ln
luôn nhớ. Nếu bạn đã test code của bạn thông qua public interface, thì các phương thức
private cũng đã tự động được test. Một lý lẽ khác chống lại việc kiểm tra các phương
thức private là nó phá vỡ tính đóng gói. Chúng ta đã nói về sự quan trong của việc che
giấu thông tin. Các phương tức private chứa các chi tiết hiện thực mà chúng ta muốn có
thể thay đổi mà khơng cần phá vỡ việc gọi trong code. Nếu chúng ta kiểm tra các phương
thức private 1 cách trực tiếp, những thay đổi hiện thực sẽ phá hỏng các test của chúng ta,
mà điều này không báo trước tốt cho khả năng bảo trì cao hơn.


</div>
<span class='text_page_counter'>(35)</span><div class='page_container' data-page=35>

Để bắt đầu, sẽ là một ý kiến hay để kiểm tra các chức năng đơn giản. Trước kia khá lâu,
bạn sẽ muốn kiểm tra một phương thức mà có sự phụ thuộc vào các thành phần bên ngoài
- chẳng hạn như database. Ví dụ, bạn sẽ muốn hồn chỉnh việc kiểm tra lớp Car bằng
cách kiểm tra phương thức Save. Vì chúng ta muốn giữ cho test của chúng ta càng chi tiết
càng tốt (và càng nhỏ càng tốt- các test cần nên chạy nhanh thì chúng ta mới thể thực thi
chúng thường xuyên và có thể lấy feedback ngay lập tức) chúng ta thực sự khơng muốn
tính tốn ra bằng cách nào để tạo ra 1 kiểm tra database mà làm giả dữ liệu và chắc rằng
nó giữ được trạng thái ban đầu qua các test. Theo tinh thần đó, tất cả điều chúng ta muốn
làm chắc chắn rằng phương thức Save tương tác đúng với DAL. Sau đó chúng ta có thể
dùng unit test để kiểm DAL. Nếu phương thức Save và DAL hoạt động như mong đợi và
chúng tương tác đúng với nhau, thì chúng ta có một cơ sở tốt để chuyển sang các kiểm tra


khác. Trong chương trước, chúng ta đã thấy sự bắt đầu của việc kiểm tra với mock.
Chúng ta đã tạo lớp mock 1 cách thủ cơng, mà điều này thì có 1 vài giới hạn lớn. Sự khác
biệt lớn nhất của nó là chúng ta không thể xác thực việc gọi đến đối tượng mock xuất
hiện như mong muốn. Đó thật sự là điều mà RhinoMock muốn giải quyết. Sử dụng
RhinoMock không phải đơn giản, cần chỉ cho nó cái mà bạn muốn mock (giao diện hoặc
lớp - thường là giao diện) chỉ ra phương thức nào bạn muốn gọi, theo đó với các thông
số, thực thi và kiểm tra đã thoả mãn mong muốn của bạn chưa.


Trước khi bắt đầu, chúng ta cần cho RhinoMock truy cập vào các kiểu nội tại của chúng
ta. Nó có thể thực hiện nhanh chóng bằng cách thêm


<i>[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] </i>


vào file Properties/AssemblyInfo.cs.


Giờ chúng ta có thể bắt đầu code bằng cách viết một test để cover nhánh update của
phương thức Save:


<i>[TestFixture] </i>


<i>public class CarTest </i>
<i>{ </i>


<i> [Test] </i>


<i> public void SaveCarCallsUpdateWhenAlreadyExistingCar() </i>
<i> { </i>


<i> MockRepository mocks = new MockRepository(); </i>



<i> IDataAccess dataAccess = mocks.CreateMock<IDataAccess>(); </i>
<i> ObjectFactory.InjectStub(typeof(IDataAccess), dataAccess); </i>
<i> Car car = new Car(); </i>


<i> dataAccess.Update(car); </i>
<i> mocks.ReplayAll(); </i>
<i> car.Id = 32; </i>
<i> car.Save(); </i>


<i> mocks.VerifyAll(); </i>


<i> ObjectFactory.ResetDefaults(); </i>
<i> } </i>


<i>}</i>


Mỗi khi đối tượng mock được tạo ra, nó sẽ thực hiện 1 dịng code, chúng ta xen nó vào
trong dependency injecttion framework của chúng ta (trong trường hợp này là


</div>
<span class='text_page_counter'>(36)</span><div class='page_container' data-page=36>

bởi RhinoMocks (khơng có gì thật sự xày ra vì dataAccess chỉ đơn giản là đối tượng
mock mà khơng có hiện thực thật sự). Chúng ta thoát khỏi record-mode bằng cách gọi
ReplayAll, có nghĩa là chúng ta đã sẵn sàng để thực thi các code thật và được kiểm tra
ngược lại với tiến trình đã được định sẵn. Khi chúng ta gọi VerifyAll sau khi gọi Save
trong đối tượng Car, RhinoMocks sẽ đảm bảo rằng việc gọi thật sự của chúng ta diễn ra
giống như là mong đợi. Nói 1 cách khác, bạn có thể nghĩ mọi thứ trước khi ReplayAll là
trạng thái chúng ta mong muốn, mọi thứ sau đó mà code kiểm tra thật sự của chúng ta với
VerifyAll thực hiện là những kiểm tra cuối cùng.


Chúng ta có thể kiểm tra tất cả bằng cách buộc cho nó sai (chú ý việc gọi
dataAccess.Update):



<i>[Test] </i>


<i>public void SaveCarCallsUpdateWhenAlreadyExistingCar() </i>
<i>{ </i>


<i> MockRepository mocks = new MockRepository(); </i>


<i> IDataAccess dataAccess = mocks.CreateMock<IDataAccess>(); </i>
<i> ObjectFactory.InjectStub(typeof(IDataAccess), dataAccess); </i>
<i> Car car = new Car(); </i>


<i> dataAccess.Update(car); </i>
<i> dataAccess.Update(car); </i>
<i> mocks.ReplayAll(); </i>
<i> car.Id = 32; </i>
<i> car.Save(); </i>


<i> mocks.VerifyAll(); </i>


<i> ObjectFactory.ResetDefaults(); </i>
<i>}</i>


Việc kiểm tra của cúng ta sẽ thất bại với 1 tin nhắn từ RhinoMocks báo rằng có 2 việc gọi
đến Update được mong đợi, nhưng chỉ có 1 thật sự diễn ra.


Cho hành động Save, sự tương tác phức tạp hơn - chúng ta phải đảm bảo giá trị trả về đã
được sử lý đúng bằng phương thức Save. Dưới đây là test:


<i>[Test] </i>



<i>public void SaveCarCallsSaveWhenNew() </i>
<i>{ </i>


<i> MockRepository mocks = new MockRepository(); </i>


<i> IDataAccess dataAccess = mocks.CreateMock<IDataAccess>(); </i>
<i> ObjectFactory.InjectStub(typeof(IDataAccess), dataAccess); </i>
<i> Car car = new Car(); </i>


<i> Expect.Call(dataAccess.Save(car)).Return(389); </i>
<i> mocks.ReplayAll(); </i>


<i> car.Save(); </i>


<i> mocks.VerifyAll(); </i>


<i> Assert.AreEqual(389, car.Id); </i>
<i> ObjectFactory.ResetDefaults(); </i>
<i>}</i>


</div>
<span class='text_page_counter'>(37)</span><div class='page_container' data-page=37>

Nếu chúng ta thay đổi hàm Save để đưa ra ngoại lệ nếu id trả về không hợp lệ, test của
chúng ta sẽ trông thế này:


<i>[TestFixture] </i>


<i>public class CarTest </i>
<i>{ </i>


<i> private MockRepository _mocks; </i>


<i> private IDataAccess _dataAccess; </i>
<i> [SetUp] </i>


<i> public void SetUp() </i>
<i> { </i>


<i> _mocks = new MockRepository(); </i>


<i> _dataAccess = _mocks.CreateMock<IDataAccess>(); </i>


<i> ObjectFactory.InjectStub(typeof(IDataAccess), _dataAccess); </i>
<i> } </i>


<i> [TearDown] </i>


<i> public void TearDown() </i>
<i> { </i>


<i> _mocks.VerifyAll(); </i>
<i> } </i>


<i> [Test, ExpectedException("CodeBetter.Foundations.PersistenceException")] </i>
<i> public void SaveCarCallsSaveWhenNew() </i>


<i> { </i>
<i> Car car = new Car(); </i>


<i> Expect.Call(_dataAccess.Save(car)).Return(0); </i>
<i> _mocks.ReplayAll(); </i>



<i> car.Save(); </i>
<i> } </i>


<i>}</i>


Để cho các bạn thấy bằng cách nào có thể kiểm tra ngoại lệ (thơng qua thuộc tính
ExpectedException), chúng ta cũng đã trích ra đoạn code tạo, thiết lập và xác nhận đối
tượng mock được lặp lại vào trong phương thức Setup và TearDown.


<b>More on nUnit and RhinoMocks</b>



Cho đến nay chúng tôi đã chỉ nhìn những tính năng cơ bản được cung cấp bởi nUnit và
RhinoMocks, nhưng có nhiều hơn việc mà thực sự có thể làm với chúng. Ví dụ,


RhinoMocks có thể được thiết lập để bỏ qua thứ tự gọi của các các phương thức, nhanh
chóng mocks nhiều nhưng chỉ replay/xác minh những cái cụ thể, hoặc mock một số
nhưng không phải các phương thức khác của lớp (partial mock).


Kết hợp với một tiện ích như NCover, bạn cũng có thể nhận được báo cáo về khả
năng bao phủ của kiểm tra. Khả năng bao phủ cơ bản cho bạn biết những gì tỷ lệ phần
trăm của một assembly/namespace/class/phương thức đã được thực hiện bởi các test của
bạn. NCover có một trình duyệt mã trực quan, nó sẽ đánh dấu bất kỳ dịng mã khơng thực
thi bằng màu đỏ. Nói chung, tơi khơng thích dùng mức độ bao phủ như là một phương
tiện để đo tính hồn chỉnh của unit test. Sau hết, bạn thực thi một dịng code khơng có
nghĩa là bạn đã thực sự kiểm tra nó. Những gì tôi làm như NCover cho là để làm nổi bật
code mà khơng được bao phủ. Nói cách khác, chỉ vì một dịng mã hoặc các phương pháp
đã được thực hiện bởi một test, khơng có nghĩa là việc bạn kiểm tra là tốt. Nhưng nếu
một dòng mã hoặc các phương pháp đã không được thực thi, sau đó bạn cần phải xem xét
để thêm một số test.



</div>
<span class='text_page_counter'>(38)</span><div class='page_container' data-page=38>

của bạn. Trong TDD chúng ta sẽ viết Save test trước khi có bất kì chức năng nào trong
phương thức Save. Tất nhiên, test của chúng ta sẽ thất bại. Sau đó chúng sẽ viết các hành
vi cụ thể và kiểm tra lại. Các thần chú chung cho các nhà phát triển là red - green →
refactor. Có nghĩa là bước đầu tiên là có một unit test thất bại, sau đó làm cho nó pass,
sau đó đến Refactor code như được yêu cầu.


Theo kinh nghiệm của tôi, TDD đi rất tốt với Domain Driven Design, bởi vì nó thực sự
cho phép chúng ta tập trung vào các nguyên tắc hoạt động của hệ thống. Nếu khách hàng
của chúng ta nói việc theo dõi sự phụ thuộc giữa các nâng cấp là một major pain-point
cho họ (If our client says tracking dependencies between upgrades has been a major
pain-point for them), thì chúng ta đặt ra đường đi đúng đắn với các test sẽ xác định các
hành vi và API của tính năng cụ thể. Tơi khun bạn nên tự làm quen với unit test, nói
chung trước khi áp dụng TDD.


<b>UI and Database Testing </b>



Việc dùng Unit Test với trang ASP.NET thật sự không xứng đáng với công sức bỏ ra.
ASP.NET framework khá phức tạp và bị khớp nối rất chặt chẽ. Thường xuyên hơn bạn
sẽ không yêu cầu một HTTPContext thực tế, mà địi hỏi khá nhiều cơng việc để thiết lập.
Nếu bạn đang sử dụng nặng nề tuỳ chỉnh của HttpHandlers, bạn nên test chúng giống như
bất kỳ lớp khác (dĩ nhiên là tuỳ thuộc vào những gì bạn đang làm). Mặt khác, kiểm tra
Data Access Layer là có thể, và tơi muốn recommend nó. Có thể có phương pháp tốt hơn,
nhưng phương pháp tiếp cận của tôi đã duy trì tất cả các CREATE Tables / CREATE
Sprocs của tôi trong text files cùng với dự án của tôi, tạo ra một cơ sở dữ liệu thử nghiệm
on the fly, và sử dụng phương thức Setup và Teardown để giữ cho cơ sở dữ liệu trong
một trạng thái đã được biết. Chủ đề này có thể có giá trị của một bài đăng blog trong
tương lai, nhưng đối với bây giờ, tơi sẽ để nó tuỳ vào sự sáng tạo của bạn.


<b>Trong chương này</b>




</div>
<span class='text_page_counter'>(39)</span><div class='page_container' data-page=39>

<b>CHƯƠNG 6 </b>

Object Relational Mappers


THE OTHER OPTION INVOLVED WRITING WAY TOO MUCH SQL. -


CHRIS KOCH



Trong chương 3 chúng ta đã tìm hiểu về hàn gắn dữ liệu và đối tượng thế giới bằng cách
viêt tay lớp data access và mapper. Hướng tiếp cận này khá hạn chế và yêu cầu khá nhiều
mã lặp đi lặp lại (mặc dù nó rất hữu ích trong chứng minh các vấn đề cơ bản). Thêm
nhiều đối tượng và chức năng hơn sẽ bloat DAL vào một trạng thái vi phạm khơng thể
bảo trì được của DRY (don't repeat yourself). Trong chương này, chúng ta lấy một O / R
Mapping framework thực tế để làm tất cả những thứ nặng nhọc cho chúng ta. Đặc biệt,
chúng ta sẽ nhìn qua framework mã nguồn mở khá phổ biến NHibernate. Các hàng rào
ngăn cản lớn nhất của người dân từ việc áp dụng tên miền hướng thiết kế là vấn đề của
persistence. Việc áp dụng của riêng tôi về O / R mappers đến với sự rung chuyển và nghi
ngờ lớn. Bạn về cơ bản sẽ được yêu cầu trao đổi kiến thức của bạn về phương pháp thử
và đúng cho một cái gì đó có vẻ ít q huyền diệu. Một bước nhảy vọt của đức tin có thể
được yêu cầu.


Việc đầu tiên nói đến là các O / R mapper sẽ tạo SQL cho bạn. Tơi biết, nó nghe như sẽ
chậm, khơng an tồn và thiếu thuyết phục, đặc biệt vì bạn có thể cấu hình để nó sử dụng
inline SQL. Nhưng nếu bạn có thể đẩy những nỗi sợ ra khỏi tâm trí của bạn, bạn phải
thừa nhận rằng nó có thể tiết kiệm rất nhiều thời gian và kết quả là ít lỗi hơn. Hãy nhớ
rằng, chúng ta muốn tập trung vào xây dựng hành vi, không phải lo lắng về plumbing (và
nếu nó làm cho bạn cảm thấy tốt hơn, một O / R mapper tốt sẽ cung cấp cách đơn giản để
bạn có thể phá vỡ hệ mã tự động và thực thi SQL của riêng bạn hoặc các thủ tục lưu trữ).

<b>Infamous Inline SQL vs Stored Procedure Debate</b>



Trong những năm qua, có một số cuộc tranh luận giữa Inline SQL và thủ tục lưu trữ. Các
cuộc tranh luận này khơng được sơi nổi lắm, bởi vì khi người ta nghe thấy Inline SQL, họ
nghĩ code không tốt kiểu như:



<i>string sql = @"SELECT UserId FROM Users </i>


<i> WHERE UserName = '" + userName + "' </i>
<i> AND Password = '" + password + "'"; </i>
<i> using (SqlCommand command = new SqlCommand(sql)) </i>
<i> { </i>


<i> return 0; //todo </i>
<i> }</i>


Tất nhiên, diễn đạt theo cách này, Inline SQL thực sự suck. Tuy nhiên, nếu bạn dừng lại
và suy nghĩ về nó và thực sự so sánh apples to apples, sự thật là khơng cái gì là tốt hơn
cái khác. Hãy kiểm tra một số luận điểm chung.


<b>Stored Procedures are More Secure</b>


Inline SQL cần được viết bằng cách sử dụng các truy vấn tham số giống như bạn làm với
thủ tục được lưu giữ. Ví dụ, cách chính xác viết mã ở trên để loại trừ khả năng xảy ra một
tấn công SQL injection là:


<i>string sql = @"SELECT UserId FROM Users </i>


<i> WHERE UserName = @UserName AND Password = @Password"; </i>
<i> using (SqlCommand command = new SqlCommand(sql)) </i>


<i> { </i>


</div>
<span class='text_page_counter'>(40)</span><div class='page_container' data-page=40>

<i> }</i>



Từ đó trở đi, khơng có nhiều khác biệt - view có thể được sử dụng hoặc database role /
người sử dụng có thể được thiết lập với các điều khoản thích hợp. Thủ tục lưu giữ cung
cấp một sự trừu tượng cho underlying schema. Dù bạn có sử dụng Inline SQL hay thủ tục
lưu giữ, một chút sự trưu tượng bạn đặt trong câu lệnh SELECT đều giống nhau. Nếu có
thay đổi được thực hiện, thủ tục lưu trữ của bạn sẽ bị phá vỡ và có 1 một cơ hội tốt bạn sẽ
cần để thay đổi code gọi đến để đối phó với vấn đề này. Về bản chất, nó cùng 1 code, đơn
giản là nằm ở những nơi khác nhau, do đó nó khơng thể cung cấp sự trừu tượng hơn nữa.
O/R mappers lại ở một mặt khác, nhìn chung cung cấp sự trừu tượng tốt hơn bằng khả
năng cấu hình và sự hiện thực bằng ngôn ngữ truy vấn riêng.


Nếu tôi tạo ra sự thay đổi, tôi không phải biên dịch lại code. Ở một vài nơi, bằng một
cách nào đó, người ta ln nghĩ cần phải tránh việc biên dịch code bằng mọi giá (có thể
nó đến từ thời gian mà mỗi project cần tốn cả ngày để biên dịch). Nếu bạn thay đổi thủ
tục lưu trữ, bạn vẫn cần chạy lại unit và integration test của bạn và triển khai thay đổi vào
sản phẩm. Nó thật sự đáng sợ và làm tôi bối rối rằng các nhà phát triển cho rằng sự thay
đổi 1 thủ tục lưu trữ hay XML tầm thường được so sánh là một thay đỏi tương tự nhau
trong code.


Stored Procedure làm giảm lưu lượng trong mạng. Có ai quan tâm? Trong nhiều trường
hợp, cơ sở dữ liệu của bạn đặt trong kết nối GigE với server của bạn và bạn khơng phải
trả cho bandwidth đó. Bạn thật sự chỉ tốn có vài nano giây. Hơn thế nữa, một cấu hình tốt
O/R mapper có thể tiết kiệm hành trình thơng qua sự hiện thực hiện thực identify map,
caching và lazy loading.


<b>Thủ tục lưu trữ nhanh hơn</b>


Đó là lời biện hộ tôi đã giữ trong thời gian lâu nhất. Viết một câu lệnh SQL thơng thường
và sau đó viết cùng 1 thứ trong thủ tục lưu trữ rồi tính thời gian chúng. Trong hầu hết các
trường hợp, chỉ có rất ít hoặc khơng hề có khác biệt. Trong 1 vài trường hợp, thủ tục lưu
trữ lại chậm hơn bởi vì kế hoạch cached execution plan sẽ khơng hiệu quả đối với thơng


số đó. Jeff Atwood gọi việc sử dụng thủ tục lưu trữ vì mục đích nâng cao hiệu suất là
“trường hợp khó khăn của phương pháp tối ưu chưa hồn thiện”. Ơng ta đã đúng. Hướng
đúng đắn là chọn hướng đơn giản nhất có thể (để 1 cơng cụ tạo ra SQL cho bạn) , và tối
ưu các truy vấn đặc trưng khi/nếu hiện tượng thắt cổ chai được chỉ ra.


Nó tốn một thời gian, sau khoảng 2 năm, tôi đã nhận ra rằng tranh cãi giữa Inline SQL và
thủ tục lưu trữ thật tầm thường như là tranh cãi giữa C# và VB.NET. Nếu nó là vấn đề
của cái này hay cái kia, hãy chọn cái nào mà bạn thích hơn và bước tiếp vào thử thách kế
tiếp. Nếu khơng cịn có gì thêm trong chủ đề này, tôi sẽ chọn thủ tục lưu trữ. Tuy nhiên,
khi tơi thêm 1 O/R mapper


vào trong trong đống đó, bạn sẽ nhận thấy được các thuận lợi đặc trưng. Bạn kết thúc
trong ngọn lửa đấu tranh ngu ngốc và đơn giản nói rằng:"I want that".


Một cách cụ thể, có 3 lợi ích quan trọng có được với O/R mapper:


1. Bạn sẽ viết ít code hơn - điều này thật sự có kết quả trong nhiều hệ thống
có khả năng bảo trì


</div>
<span class='text_page_counter'>(41)</span><div class='page_container' data-page=41>

3. Code của bạn trở nên đơn giản hơn - nếu impedance mismatch của bạn
thấp, bạn sẽ viết rất ít mã lặp lại. Nếu impedance mismatch cao bạn sẽ
không phải kết hợp thiết kế database với thiết kế domain - bạn có thể xây
dựng cả hai theo cách tối ưu và để O/R mapper quản lý mismatch.


Cuối cùng, nó thật sự đi xuống để xây dựng giải pháp đơn giản nhất. Sự tối ưu hố có thẻ
bỏ đi cho đến sau khi bạn đã sơ lược được code của bạn và xác định được bottleneck thật
sự. Giống như hầu hết mọi thứ, nó khơng nên thấy đơn giản bởi vì nó thật sự phức tạp để
học, nhưng nó là thực tế nghề nghiệp của chúng ta.


<b>NHibernate </b>




Trong các framework và tool mà chúng ta đã từng nói qua, NHibernate là phức tạp nhất.
Sự phức tạp này là thứ dĩ nhiên bạn kể đến khi quyết định trong 1 giải pháp bền bỉ, nhưng
khi bạn thấy rằng một dự án cho phép một vài thời gian R&D, nó sẽ đáng giá trong tương
lai. Điều tốt nhất của NHibernate, và mục đích thiết kế của framework là nó hồn tồn
trong sáng - domain object của bạn khơng buộc phải thừa kế một lớp nền đặc trưng nào
và bạn không phải sử dụng hàng đống các thuộc tính trang trí. Nó làm cho unit test lớp
dơmain được khả thi - nếu bạn sử dụng một kỹ thuật bền bỉ khác, kiểu như dataset, sự
móc nối chặt chẽ giữa domain và data tạo cho nó khó/khơng thể để unit test một cách
chính xác được. Ở mức độ rất cao, bạn cấu hình NHibernate bằng cách chỉ cho nó cách
mà database ánh xạ vào trong domain object, sừ dụng NHibernate API và NHibernate
Query Language để nói cho database của bạn và để nó thực hiện các công việc cấp thấp
ADO.NET và SQL. Đây không chỉ cung cấp sự tách biệt giữa cấu trúc bảng và domain
object mà còn tách rời code với hiện thực database đặc trưng.


Trong chương trước, chúng ta tập trung vào hệ thống cho một đại lý phân phối xe - chi
tiết hơn là tập trung vào xe và nâng cấp. Trong chương này, chúng ta sẽ thay đổi các nhìn
một tí và xem qua việc bán xe (sales, models và Sales people). Domain model khá đơn
giản - một SalesPeson có khơng hoặc nhiều Sales, mà mỗi Sales liên kết với một Model
cụ thể.


Cũng được kể đến là giải pháp VS.NET, bao gồm các code mẫu và các chú giải. Tất cả
những gì bạn cần để làm cho nó chạy được là tạo một database mới, thực thi các đoạn mã
SQL được cung cấp và cấu hình cho chuỗi kết nối. Các ví dụ, cùng với phần còn lại trong
chương này, là cách để giúp bạ bắt đầu với NHibernate - một chủ đề rất hay bị lờ đi.
<i>Cuối cùng, bạn sẽ tìm thấy cuốn NHibernate reference manual có chất lượng khá tốt, đây</i>
là những tool hữu ích để bắt đầu và cũng là tham khảo để tìm kiếm các chủ đề cụ thể.
Cũng có một cuốn sách đã được xuất bản bởi Manning, NHibernate in Action, nó sẽ xuất
hiện vào tháng 6.



<b>Configuration </b>


Bí mật về sự linh hoạt đáng ngạc nhiên của NHibernate nằm trong khả năng cấu hình của
nó. Ban đầu nó có thể làm nản lịng để thiết lập được nó, nhưng sau khoảng 2 dự án thì
nó trở nên khá tự nhiên. Bước đầu tiên là cấu hình chính NHibernate. Việc cấu hình đơn
giản nhất, là phải thêm vào app.config hay web.config, như là:


<i><configuration> </i>
<i> <configSections> </i>


<i> <section name="hibernate-configuration" </i>


<i> type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> </i>
<i> </configSections> </i>


</div>
<span class='text_page_counter'>(42)</span><div class='page_container' data-page=42>

<i> <property name="hibernate.dialect"> </i>
<i> NHibernate.Dialect.MsSql2005Dialect </i>
<i> </property> </i>


<i> <property name="hibernate.connection.provider"> </i>
<i> NHibernate.Connection.DriverConnectionProvider </i>
<i> </property> </i>


<i> <property name="hibernate.connection.connection_string"> </i>


<i> Server=SERVER;Initial Catalog=DB;User Id=USER;Password=PASSWORD; </i>
<i> </property> </i>


<i> <mapping assembly="CodeBetter.Foundations" /> </i>
<i> </session-factory> </i>



<i> </hibernate-configuration> </i>
<i></configuration></i>


Trong bốn giá trị, phương ngữ là thú vị nhất. Nó nói cho NHibernate ngơn ngữ cụ thể mà
database chúng ta sử dụng. Nếu, trong code của chúng ta, chúng ta yêu cầu NHibernate
trả về trang kết quả của Cars và phương ngữ được thiết lập là SQL Server 2005,


NHibernate sẽ phát ra một câu SQL SELECT sử dụng hàm ROW_NUMBER(). Tuy
nhiên, nếu phương ngữ được thiết lập là MySQL, NHibernate sẽ phát ra câu SELECT với
LIMIT. Trong hầu hết các trường hợp, chúng ta sẽ thiết lập nó chỉ một lần và sau đó quên
nó đi, nhưng nó cung cấp một vài cách nhìn vào trong khả năng cung cấp bởi một lớp mà
tạo ra tất cả các code để truy cập dữ liệu.


Trong việc cấu hình, chúng ta cũng chỉ cho NHibernate rằng file ánh xạ nằm ở đâu trong
bộ phận CodeBetter.Foundations. File ánh xạ được nhứng và trong file XML, nó sẽ nói
cho NHibernate cách mỗi lớp được tiếp tục tồn tại. Với thơng tin này, NHibernate có khả
năng trả về đối tượng Car khi bạn yêu cầu cũng như lưu nó. Quy tắc phổ biến là có một
file ánh xạ cho mỗi domain object, và đặt chúng bên trong thư mục Mapping. File ánh xạ
cho đối tượng Model, được đặt tên Model.hbm.xml:


<i><hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" </i>
<i> assembly="CodeBetter.Foundations" </i>


<i> namespace="CodeBetter.Foundations"> </i>


<i> <class name="Model" table="Models" lazy="true" proxy="Model"> </i>


<i> <id name="Id" column="Id" type="int" access="field.lowercase-underscore"> </i>
<i> <generator class="native" /> </i>



<i> </id> </i>


<i> <property name="Name" column="Name" </i>


<i> type="string" not-null="true" length="64" /> </i>
<i> <property name="Description" column="Description" </i>
<i> type="string" not-null="true" /> </i>


<i> <property name="Price" column="Price" </i>
<i> type="double" not-null="true" /> </i>
<i> </class></i>


<i></hibernate-mapping></i>


(nó khá quan trọng phải đảm bảo rằng Build Action cho tất cả các file ánh xạ được set
đến Embedded Resources). File này nói cho NHibernate lớp Model ánh xạ đến hàng
trong bảng Model, và 4 thuộc tính Id, Name, Description và Price được ánh xạ đến cột Id,
Name, Description và Price. Những thơng tin thêm xung quanh thuộc tính Id chỉ rằng giá
trị được tạo ra bởi database (trái ngược với chính NHibernate (chẳng hạn như giải pháp
clustered) hay giải thuật riêng của chúng ta) và nó khơng có setter, vì thế nó nên được
truy cập bằng field với quy tắc đặt tên cụ thể (chúng ta cung cấp Id như là tên và
lowercase-underscore là quy tắc đặt tên, vì thế nó sẽ sử dụng field được đặt tên là _id).
Với file ánh xạ được thiết lập, chúng ta có thể bắt đầu tương tác với database:


<i>private static ISessionFactory _sessionFactory; </i>
<i>public void Sample() </i>


<i>{ </i>



</div>
<span class='text_page_counter'>(43)</span><div class='page_container' data-page=43>

<i> Model model = new Model(); </i>
<i> model.Name = "Hummbee"; </i>


<i> model.Description = "Great handling, built-in GPS to always find your </i>
<i> way back home, Hummbee2Hummbe(tm) communication"; </i>
<i> model.Price = 50000.00; </i>


<i> ISession session = _sessionFactory.OpenSession(); </i>
<i> session.Save(model); </i>


<i> //Let's discount the x149 model </i>


<i> IQuery query = session.CreateQuery("from Model model where model.Name = ?"); </i>
<i> Model model = query.SetString(0, "X149").UniqueResult<Model>(); </i>


<i> model.Price -= 5000; </i>


<i> ISession session = _sessionFactory.OpenSession(); </i>
<i> session.Update(model); </i>


<i>}</i>


Ví dụ trên chỉ ra rằng nó thật là đơn giản để persist một đối tượng mới vào database, khôi
phục chúng và cập nhật chúng - tất cả chẳng cần đến ADO.NET hay SQL. Bạn sẽ ngạc
nhiên, đối tượng _sessionFactory đến từ đâu, và chính xác ISession là cái gì.


_sessionFactory là một đối tượng thread-safe có tầm vực trong tồn bộ hệ thống, nó được
tạo ra khi ứng dụng bắt đầu. Bạn thường sẽ cần một đối tượng cho mỗi database mà ứng
dụng của bạn sử dụng (có nghĩa là bạ thường chỉ cần một cái), và công việc của nó, cũng
giống như hầu hết các factory, nó tạo một đối tượng preconfigure: một ISession. ISession


không giống như ADO.NET, nhưng nó ánh xạ một cách lỏng lẻo đến kết nối database.
Tuy nhiên, việc tạo một ISession không cần thiết để mở một kết nối. Thay vào đó,
ISession khôn khéo quản lý kết nối và làm chủ đối tượng cho bạn. Không giống như kết
nối nên được mở sau và đóng trước, bạn khơng cần phải lo lắng về việc để cho ISession
lưu lại một thời gian (mặc dù nó khơng phải là thread-safe).


Nếu bạn đang xây dựng một ứng dụng ASP.NET, bạn có thể mở một cách an tồn một
ISession trong BeginRequest và đóng nó trong EndRequest (hay tốt hơn hết là sử dụng
lazyload để trong trường hợp cụ thể không yêu cầu ISession). ITransaction là một phần
khác của vấn đề, nó được tạo bởi việc gọi BeginTransaction trong một ISession. Nó
thường cho các nhà phát triển .NET bỏ qua sự cần thiết cho giao dịch trong ứng dụng của
họ. Nó thật khơng may vì nó có thể dẫn đến trạng thái dễ đổ vỡ và thậm chí khơng thể
khơi phục được trong dữ liệu. Một ITransaction được sử dụng để đảm bảm hoạt động của
từng unit - theo dõi cái gì thay đổi, được thêm hay xố đi, cấu hình cái gì và cách để cập
nhật vào database, và cung cấp khả năng rollback khi có thất bại.


<b>Relationships </b>


Trong hệ thống của chúng ta, nó rất quan trọng khi chúng ta kiểm soát sales - đặc biệt với
salespeople, vì thế chúng ta có cung cấp một vài báo cáo cơ bản. Chúng ta đã được nói
rằng một sale chi có thể thuộc về một salesperson, và vì thế thiết lập mối quan hệ 1-nhiều
- có nghĩa là một salesperson có nhiều sales, và mỗi sales chỉ có thể thuộc về một


salesperson. Trong database của chúng ta, mối quan hệ này được thể hiển là cột
SalesPersonId trong bảng Sales (khố ngoại). Trong domain, lớp SalesPerson có 1 tập
hợp Sales và lớp Sales có một thuộc tính SalesPerson (tham chiếu). Cả 2 sự kết thúc của
mối quan hệ cần để thiết lập file ánh xạ tương ứng. Trong kết thúc Sales, nó sẽ ánh xạ
một thuộc tính đơn, chúng ta sử dụng một yếu tố thuộc tính được gọi là many-to-one:


<i>... </i>



</div>
<span class='text_page_counter'>(44)</span><div class='page_container' data-page=44>

<i> not-null="true"/> </i>
<i>...</i>


Chúng ta đã chỉ định tên của thuộc tính, kiểu/lớp, và tên cột khoá ngoại. Chúng ta cũng
đã chỉ ra các ràng buộc mở rộng, như là khi thêm một đối tượng Sales mới, thuộc tính
SalesPerson khơng thể là null.


Mặt khác của mối quan hệ, tập hợp Sales mà SalesPerson có thì đặt tên khá là phức tạp
hơn bởi vì các thuật ngữ của NHibernate khơng phải là chuẩn .NET. Để thiết lập một tập
hợp, chúng ta sử dụng set, list, map, bag hay là mảng các phần tử. Xu hướng chung sẽ là
sử dụng list, nhưng NHibernate yêu caauf bạn có một cột chỉ rõ chỉ mục. Nói cách khác,
NHibernate team thấy một list như một tập hợp mà chỉ mục là phần quan trọng và vì thế
mà nó cần phải xác định rõ. Cái mà hầu hết các nhà phát triển .NET nghĩ đến là list thì
NHibernate gọi là bag. Khá rắc rối là dù khi bạn sử dụng list hay bag element, kiểu của
domain phải là môt IList (hay là tương đương với IList<T>). Đó là bởi vì .NET khơng có
một đối tượng IBag. Một cách ngắn gọn, để việc sử dụng tập hợp hằng ngày của bạn, bạn
sử dụng bag element và tạo kiểu thuộc tính là một IList.


Một tuỳ chọn khác về tập hợp là set. Set là một tập hợp mà khơng có chứa trùng lặp - một
ngữ cảnh chung cho các ứng dụng kinh doanh (mặc dù nó rất ít khi xác định rõ ràng). Kì
quặc là, .NET lại khơng có tập hợp kiểu set, vì thế NHibernate sử dụng giao diện


Iesi.Collection.ISet. Có 4 hiện thực cụ thể: ListSet, thật sự nhanh cho những tập hợp rất
nhỏ (10 hoặc ít hơn 10 phần tử); SortedSet, có thể được sắp xếp; HashSet, khá nhanh cho
các tập hợp lớn; và HybridSet, được khởi tạo sử dụng là ListSet và được tự động chuyển
đổi sang HashSet khi tập hợp của bạn tăng lên.


Cho hệ thống của chúng ta, chúng ta sẽ sử dụng bag (thậm chí chúng ta khơng có những
Sales trùng lặp, nó cịn khá dễ hiểu ngay lúc này), chúng ta sẽ khai báo tập hợp Sales là


một IList:


<i>private IList<Sale> _sales; </i>
<i>public IList<Sale> Sales </i>
<i>{ </i>


<i> get { return _sales;} </i>
<i>}</i>


Và thêm các phần tử <bag> vào file ánh xạ SalesPerson:


<bag name="Sales" access="field.lowercase-underscore"
table="Sales" inverse="true"


cascade="all">


<key column="SalesPersonId" />
<one-to-many class="Sale" />
</bag>


Nhắc lại, nếu bạn nhìn vào mỗi phần tử/ thuộc tính, nó thật sự không quá phức tạp.
Chúng ta chỉ định tên của các thuộc tính, xác định quy tắc truy cập (chúng ta khơng có
một setter, vì thế chỉ ra cho nó sử dụng field với quy ước đặt tên của chúng ta), bảng và
cột giữ những khoá ngoại, và kiểu/class của mỗi thành phần trong tập hợp. Chúng ta cũng
chỉ ra thuộc tính Cascade cho tất cả, có nghĩa là khi chúng ta gọi Update trên một đối
tượng SalesPerson, bất kì thay đổi được tạo ra cho tập hợp Sales của nó (thêm vào, bỏ đi,
thay đổi vào các Sales có sẵn) sẽ được tự động được chỉnh theo đó. Cascade có thể giúp
tiết kiệm thời gian thật sự hữu hiều khi mà hệ thống của bạn tăng sự phức tạp lên.


</div>
<span class='text_page_counter'>(45)</span><div class='page_container' data-page=45>

NHibernate hỗ trợ 2 hướng truy vấn khác nhau: Hibernate Query Language (HQL) ang


Criteria Queries (bạn có thể cũng truy vấn thật sự bằng SQL, nhưng nó mất đi khả năng
di chuyển khi làm thế). HQL đơn giản hơn vì nó rất giống với SQL - sử dụng : from,
where, aggregates, order by, group by,.... Tuy nhiên, thay vì truy vấn trong các bảng thì
bạn viết truy vấn trong domain -có nghĩa HQL hỗ trợ nguyên lý hướng đối tượng, giống
như thừa kế và đa hình. Các phương thức truy vấn cũng ở mức trừu tượng cao của SQL,
có nghĩa là bạn có thể di chuyển đươc - tất cả gì bạn cần làm để thực hiện trên một
database khác là thay đổi cấu hình phương ngữ.


HQL bỏ đi giao diện IQuery, nó được tạo bằng cách gọi CreateQuery trong phiên làm
việc của bạn. Với IQuery bạn có thể trả về các thực thể độc lập, tập hợp, các tham số thay
thế hoặc nhiều hơn nữa. Đây là một vài ví dụ:


<i>string lastName = "allen"; </i>


<i>ISession session = _sessionFactory.OpenSession(); </i>
<i>//retrieve a salesperson by last name </i>


<i>IQuery query = s.CreateQuery("from SalesPerson p where p.LastName = </i>
<i> 'allen'"); </i>


<i>SalesPerson p = query.UniqueResult<SalesPerson>(); </i>


<i>//same as above but in 1 line, and with the last name as a variable </i>
<i>SalesPerson p = session.CreateQuery("from SalesPerson p where p.LastName </i>
<i>= ?").SetString(0, lastName).UniqueResult<SalesPerson>(); </i>


<i>//people with few sales </i>


<i>IList<SalesPerson> slackers = session.CreateQuery("from SalesPerson person </i>
<i>where size(person.Sales) < 5").List<SalesPerson>();</i>



Nó chỉ là một phần khá hồn hảo với HQL (các ví dụ có thể download có thể phức tạp
hơn).


Set là 1 lựa chọn thú vị khác. Set là 1 tập hợp mà không chứa các phần tử giống
nhau – 1 trường hợp thường thấy cho ứng dụng doanh nghiệp (tuy nhiên trường hợp này
thường được ngầm hiểu). Tuy nhiên, .NET khơng hỗ trợ set, vì vậy Nhibernate sử dụng


<i>Iesi.Collection.Iset interface. Bốn cách hiện thực là: ListSet phù hợp với các tập hợp nhỏ </i>


<i>(ít hơn 10 phần tử) vì nó được xử lý nhanh, SortedSet có thể sắp xếp được, HashSet phù </i>
<i>hợp với các tập hợp lớn, HybridSet : lúc đầu là ListSet sau đó sẽ chuyển sang HashSet khi</i>
kích thước tập hợp tăng.


<i>Đối với trường hợp này, chúng ta sử dụng bag (mặc dù chúng ta khơng thể có các </i>sale


giống nhau nhưng sử …) <i>vì vậy chúng ta tạo 1 tập hợp dang Ilist:</i>


<i>private IList<Sale> _sales; </i>
<i>public IList<Sale> Sales </i>
<i>{ </i>


<i> get { return _sales;} </i>
<i>}</i>


Và thêm các phần tử <bag> vào file được ánh xạ


<i>SalesPerson </i>


<i><bag name="Sales" </i>


<i>access="field.lowercase-underscore" </i>


<i> table="Sales" inverse="true" </i>
<i>cascade="all"> </i>


<i> <key column="SalesPersonId" /> </i>
<i> <one-to-many class="Sale" /> </i>
<i></bag></i>


Khi .NET 3.5 ra đời, tập
HashSet cuối cùng cũng
được thêm vào


</div>
<span class='text_page_counter'>(46)</span><div class='page_container' data-page=46></div>
<span class='text_page_counter'>(47)</span><div class='page_container' data-page=47>

Một lần nữa, nếu bạn xem xét mỗi phần tử/ thuộc tính, chúng khơng quá phức tạp như
chúng ta đã thấy. Chúng ta xác định các thuộc tính và …


<b>Querying</b>


Nhibernate hỗ trợ 2 kiếu truy vấn khác nhau: Hibernate Query Language (HQL)
và Criteria Queries ( bạn cũng có thể sử dụng SQL nhưng sẽ mất tính khả chuyển khi làm
vậy). HQL dường như dễ hơn vì cú pháp gần giống SQL – bạn dùng from,where,


aggregates, order by, group by v.v. Tuy nhiên, thay vì truy xuất với các bảng bạn sẽ truy
xuất các domain - điều đó có nghĩa là HQL hỗ trợ mơ hình hướng đối tượng như thừa kế
và đa hình. Các phương pháp truy vấn đều là sự trừu tượng hóa trên nền SQL có nghĩa là
tính khả chuyển được bảo tồn – những việc bạn cần làm khi truy vấn 1 CSDL khác là
thay đổi cú pháp của mình.


HQL bỏ đi Iquery interface được tạo ra khi gọi hàm CreateQuery. Với IQuery bạn có thể
trả về các thực thể riêng biệt, các tập hợp, tham số phụ … Đây là ví dụ:



<i>string lastName = "allen"; </i>


<i>ISession session = _sessionFactory.OpenSession(); </i>
<i>//retrieve a salesperson by last name </i>


<i>IQuery query = s.CreateQuery("from SalesPerson p where p.LastName = </i>
<i> 'allen'"); </i>


<i>SalesPerson p = query.UniqueResult<SalesPerson>(); </i>


<i>//same as above but in 1 line, and with the last name as a variable </i>


<i>SalesPerson p = session.CreateQuery("from SalesPerson p where p.LastName = </i>
<i>?").SetString(0, lastName).UniqueResult<SalesPerson>(); </i>


<i>//people with few sales </i>


<i>IList<SalesPerson> slackers = session.CreateQuery("from SalesPerson person </i>
<i>where size(person.Sales) < 5").List<SalesPerson>();</i>


Đây chỉ là 1 phần nhỏ của HQL (các ví dụ mẫu ở phần download có những ví dụ phức
tạp hơn).


<b>Lazy Loading</b>


Khi chúng ta nạp 1 nhân viên bán hàng bằng dòng lệnh sau:


<i>SalesPerson person = session.Get<SalesPerson>(1)</i>



<i>Tập hợp Sales sẽ khơng được nạp. Đó là vì mặc định các tập hợp được nạp 1 cách thụ </i>
động. Điều đó có nghĩa là chúng ta sẽ khơng truy cập được CSDL cho đến khi chúng ta
<i>chỉ ra thông tin cần truy cập ( vd: truy cập thuộc tính Sales). Chúng ta có thể sửa bằng </i>
<i>cách đặt lazy=”false”.</i>


Một cách khác thú vị hơn để hiện thực thụ động nạp là trên các thực thể. Chúng ta
thường muốn thêm 1 tham khảo vào 1 đối tượng mà khơng phải nạp đối tượng đó từ
CSDL. Ví dụ như, khi chúng ta thêm 1 Sales vào 1 SalesPerson, chúng ta phải chỉ ra
Model nhưng chúng ta không muốn nạp tất cả các thuộc tính của nó. – chúng ta chỉ muốn
lấy giá trị Id để có thể lưu nó vào cột ModelId của bảng Sales. Khi gọi hàm


</div>
<span class='text_page_counter'>(48)</span><div class='page_container' data-page=48>

CSDL cho đến khi bạn yêu cầu nó. Điều này giúp ta viết được đoạn mã sau mà khơng
cần phải truy cập để nạp tồn bộ Model:


<i>Sale sale = new Sale(session.Load<Model>(1), DateTime.Now, 46000.00); </i>
<i>salesPerson.AddSales(sale);</i>


<b>Download</b>



Bạn có thể tải cũng 1 project với các cách sử dụng Nhibernate tại


/>


<b>Trong chương này</b>



Chúng ta chỉ tìm hiểu qua cách sử dụng Nhibernate mà khơng tìm hiểu Criteria
Queries (là các truy xuất API được gắn chặt với domain), khả năng cache, phân loại tập
hợp, tối ưu hiệu năng, log hay các khả năng SQL. Hi vọng bạn khơng chỉ tìm hiểu


</div>
<span class='text_page_counter'>(49)</span><div class='page_container' data-page=49>

<b>CHƯƠNG 7</b>

<b> Trở lại căn bản: Bộ nhớ</b>




Dù cố gắng, các ngơn ngữ lập trình hiện đại khơng thể hồn tồn che dấu các khái
niệm cơ bản của hệ thống máy tính bằng chứng là các exception khác nhau xuất hiện
trong các ngôn ngữ cấp cao. Ví dụ như, bạn đã từng bắt gặp các .NET exception sau :
NullReferenceException, OutOfMemoryException, SteckOverflowException và


ThreadAbortException. Sẽ rất quan trọng đối với lập trình viên để hiểu được các cấu trúc
và kĩ năng lập trình cấp cao, tuy nhiên lập trình viên cũng cần phải hiểu hệ sinh thái mà
chương trình của họ chạy trên đó. Nhìn vào các lớp được cung cấp bởi trình biên dịch C#
hay VB.NET, CLR và hệ điều hành, chúng ta thấy bộ nhớ. Mọi chương trình đều sử dụng
bộ nhớ hệ thống và giao tiếp với nó bằng nhiều cách, sẽ rất khó để trở thành 1 người lập
trình tốt nếu khơng hiểu các giao tiếp căn bản này.


Các sự nhập nhằng về bộ nhớ xuất phát từ việc C# và VB.NET là các ngôn ngữ
đã được chỉnh sửa và CLR cung cấp cơ chế dọn rác. Điều này đã tạo cho các lập trình
viên hiểu nhầm rằng họ không cần lo lắng về bộ nhớ.


<b>Cấp phát bộ nhớ:</b>



Ở .NET, cũng như đa số ngôn ngữ, mỗi biến bạn định nghĩa đều được lưu ở stack
hoặc heap. Hai không gian lưu trữ tồn tại riêng biệt trong bộ nhớ hệ thống phục vụ mục
đích khác nhau. Mục đích này đã được xác định trước: kiểu giá trị nằm trên stack trong
khi các kiểu tham khảo nằm trên heap. Nói cách khác, đối với các kiểu như


char,int,long,byte, enum và bất kỳ cấu trúc nào (được định nghĩa bởi .NET hay người lập
trình) nằm trên stack. Chỉ có 1 trường hợp ngoại lệ là các kiểu giá trị thuộc các kiểu tham
khảo – ví dụ như thuộc tính ID của class User nằm trên heap cùng với thực thể User.


<b>Stack</b>


Mặc dù chúng ta quen với việc xử lí rác tự động, các giá trị trên stack cũng được


xử lí tự động trong mơi trường lập trình khơng có cơ chế xử lí rác (như C). Điều này xảy
ra vì khi thêm vào 1 giá trị (như 1 phương thức hay 1 câu lệnh if) các giá trị được đẩy vào
đầu stack và khi thoát ra các giá trị của stack sẽ được lấy ra. Đây là lí do mà stack là 1
LIFO. Khi chương trình chạy đến 1 khối như 1 phương thức, 1 con trỏ sẽ đánh dấu đầu
của stack và giá trị sẽ được thêm vào nếu cần. Khi bạn thốt khỏi khối đó, các giá trị sẽ
được lấy ra cùng với con trỏ đánh dấu. Việc này xảy ra dù có bao nhiêu khối lồng vào
nhau.


Trừ khi bạn xem xét việc giao tiếp giữa heap và stack, vấn đề duy nhất bạn sẽ gặp
với stack là StackOverflowException. Nó có nghĩa là bạn đã sử dụng hết khơng gian
dùng có trên stack. 99.9% trường hợp là do 1 lời gọi recursive không có điểm dừng. Trên
lý thuyết, tràn stack có thể xảy ra do 1 hệ thống thiết kế rất tồi nhưng tôi chưa bao giờ
thấy 1 lời gọi không recursive mà có thể dùng hết khơng gian trên stack.


<b>Heap</b>


</div>
<span class='text_page_counter'>(50)</span><div class='page_container' data-page=50>

nhau), tạo 1 khối nhớ và trả về 1 con trỏ tới vị trí được cấp phát. Ví dụ đơn giản nhất là
1chuỗi: mỗi kí tự trong chuỗi chiếm 2 byte và khi tạo 1 chuỗi với giá trị “Hello World”
CLR sẽ cần phải cấp phát 22 byte (11x2) cộng thêm các phần overhead.


Chuỗi khi đã được tạo không thể thay đổi – nghĩa là khi bạn khởi tạo 1 chuỗi và
gán 1 giá trị, nếu bạn chỉnh sửa chuổi đó (bằng cách thay đổi giá trị hay nối chuỗi khác
vào nó) khi đó 1 chuỗi mới được tạo ra. Việc này có thể dẫn đến giảm hiệu năng ví vậy
cách tốt nhất là sử dụng StringBuilder cho việc xử lí các chuỗi. Sự thật là các đối tượng
lưu trên heap là khơng thể chỉnh sửa kích thước và bất cứ thay đổi nào về kích thước cần
phải có cấp phát mới. StringBuilder cùng với vài tập hợp khác giảm thiểu phần nào đó
bằng cách sử dụng các buffer. Khi buffer đã đầy, 1 cấp phát tương tự sẽ xảy ra và 1 số
thuật toán tăng trưởng sẽ được dùng để xác định kích thước mới (đơn giản nhất là bằng
oldSize*2). Khi có thể thì chúng ta nên xác định giá trị ban đầu của 1 đối tượng như vậy
để tránh việc cấp phát lại (constructor của StringBuilder và ArrayList (cũng như các


collection khác) đều cho bạn xác định kích thước ban đầu)


Dọn rác trên heap là 1 việc không tầm thường. Không như stack ta chỉ cần loại
khối cuối cùng ra khỏi stack, các đối tượng trên heap không nằm cục bộ trong 1 khối cụ
thể. Thay vào đó, đa số là các tham khảo lồng nhau rất sâu của các đối tượng được tham
khảo khác. Trong ngôn ngữ C, bất cứ khi nào người lập trình cấp phát 1 vùng nhớ, anh/cơ
ta phải chắc chắn là xóa nó khỏi heap khi kết thúc sử dụng nó. Trong các ngơn ngữ được
chỉnh sửa, môi trường runtime sẽ chịu trách nhiệm dọn dẹp các tài nguyên (.NET sử dụng
Generational Garbage Collector – tìm hiểu thêm tại Wikipedia)


Lập trình viên có thể gặp nhiều vấn đề phức tạp khi làm việc với heap. Thất thốt
bộ nhớ có thể xảy ra thường xun, phân mảnh bộ nhớ có thể gây nhiều tác hại và các
vấn đề về hiệu suất có thể xuất hiện do cấp phát khơng được quản lí.


<b>Con trỏ</b>


Đối với nhiều lập trình viên, học về con trỏ là 1 trải nghiệm đau lòng. Chúng thể
hiện giao tiếp thật sự giữa phần mã và phần cứng. Nhiều lập trình viên chưa bao giờ tìm
hiểu chúng – bằng cách sự dụng những ngôn ngữ không diễn đạt chúng một cách trực
tiếp. Người ta cho rằng C# hay Java là những ngơn ngữ khơng có con trỏ là sai lầm. Con
trỏ là cơ chế mà mọi ngôn ngữ dùng để quản lí các giá trị trên heap, thật ngờ nghệch khi
khơng tìm hiểu cách hoạt động của chúng.


Con trỏ diễn tả các mối quan hệ của mơ hình bộ nhớ hệ thống – trong đó, con trỏ
là cơ chế để stack và heap làm việc với nhau nhằm cung cấp hệ thống bộ nhớ được u
cầu bởi chương trình. Như đã nói ở trên, khi chúng ta khởi tạo 1 đối tượng với new, .NET
sẽ cấp phát 1 vùng nhớ trên heap và trả về con trỏ chỉ đến đầu vùng nhớ này. Con trỏ đơn
giản là: đĩa chỉ bắt đầu của khối nhớ chứa 1 đối tượng. Địa chỉ này khơng có gì khác hơn
là 1 con số duy nhất được biểu diễn trong hệ thập lục phần. Vì vậy, con trỏ khơng gì hơn
là 1 con số duy nhất nói cho .NET nơi lưu trữ đối tượng trong bộ nhớ. Khi bạn gán 1


tham khảo cho 1 biến, biến của bạn thật ra là 1 con trỏ tới đối tượng. Sự giao tiếp này
được che đi trong Java hay .NET nhưng ở C hay C++ nơi mà bạn có thể điều khiển địa
chỉ bộ nhớ 1 cách trực tiếp thơng qua các phép tốn trên con trỏ. Trong C hay C++ bạn có
thể lấy 1 con trỏ và thêm 1 vào đó, bằng cách này bạn có thể tùy ý thay đổi vị trí mà con
trỏ trỏ tới (và có thể dẫn đến crash vì nó)


</div>
<span class='text_page_counter'>(51)</span><div class='page_container' data-page=51>

tham khảo. Điều này có thể chưa rõ ràng nhưng nó có nghĩa là mọi đối tượng nằm trên
heap có gốc nằm trên stack (có thể thơng qua nhiều tầng tham khảo). Chúng ta hãy xem
xét ví dụ sau :


<i>static void Main(string[] args) </i>
<i>{ </i>


<i> int x = 5; </i>


<i> string y = "codebetter.com"; </i>
<i>}</i>


Từ đoạn mã ở trên chúng ta sẽ có 2 giá trị trên stack, số nguyên 5 và con trỏ tới 1 chuỗi
và chuỗi này nằm trên heap. Đây là hình minh họa


Khi chúng ta thốt khỏi hàm main, các giá trị địa phương sẽ được loại ra khỏi stack, có
nghĩa là giá trị x và y đều bị mất. Điều này là đáng kể vì vùng nhớ trên heap chứa chuỗi
của chúng ta vẫn còn nhưng chúng ta đã mất tất cả các tham khảo đến nó (khơng có con
trỏ nào chỉ đến nó). Trong C hay C++, điều này sẽ dẫn đến thất thoát bộ nhớ - nếu khơng
có tham khảo đến địa chỉ trên vùng heap chúng ta khơng thể giải phóng bộ nhớ. Trong C#
hay Java, trình dọn rác sẽ tìm đối tượng khơng được tham khảo và giải phóng nó.


Chúng ta sẽ tìm hiểu đoạn mã sau, ngồi việc nó có nhiều tham khảo hơn thì nó cũng
tương tự đoạn mã ở trên.



<i>public class Employee </i>
<i>{ </i>


<i> private int _employeeId; </i>
<i> private Employee _manager; </i>
<i> public int EmployeeId </i>
<i> { </i>


<i> get { return _employeeId; } </i>
<i> set { _employeeId = value; } </i>
<i> } </i>


<i> public Employee Manager </i>
<i> { </i>


<i> get { return _manager; } </i>
<i> set { _manager = value; } </i>
<i> } </i>


<i> public Employee(int employeeId) </i>
<i> { </i>


<i> _employeeId = employeeId; </i>
<i> } </i>


<i>} </i>


<i>public class Test </i>
<i>{ </i>



<i> private Employee _subordinate; </i>
<i> void DoSomething() </i>


</div>
<span class='text_page_counter'>(52)</span><div class='page_container' data-page=52>

<i> Employee boss = new Employee(1); </i>
<i> _subordinate = new Employee(2); </i>
<i> _subordinate.Manager = _boss; </i>
<i> } </i>


<i>}</i>


Thú vị thay, khi chúng ta kết thúc phương thức, biến boss không được đưa ra khỏi stack
nhưng biến subordinate được định nghĩa ở lớp ngồi thì khơng. Điều này có nghĩa là trình
dọn rác khơng có gì để làm vì cả 2 giá trị trên hreap đều đang được tham khảo (1 trực tiếp
từ stack, 1 gián tiếp từ stack thông qua 1 đối tượng được tham khảo).


Như bạn có thể thấy, con trỏ chắc chắn đóng 1 vai trò quan trọng trong C# và VB.NET.
Mặc dù phép tốn trên con trỏ khơng có trong 2 ngơn ngữ trên ,con trỏ rất đơn giản và dễ
hiểu.


<b>Mơ hình bộ nhớ trong thực tế</b>



Chúng ta sẽ tìm hiểu ảnh hưởng thực tế của chúng trên chương trình. Dù hiểu biết mơ
hình bộ nhớ khơng giúp chúng ta tránh các lỗi nhưng nó sẽ giúp chúng ta viết chương
trình tốt hơn.


<b>Đóng hộp (Boxing)</b>


Boxing xảy ra khi 1 giá trị (được lưu trên stack) buộc phải lưu trên heap. Unboxing xảy
ra khi các giá trị này được lưu lại trên stack. Cách đơn giản nhất để buộc 1 giá trị như


kiểu nguyên lưu trên heap bằng cách cast nó.


<i>Int x=5;</i>
<i>Object y=x;</i>


1 trường hợp thường thấy của boxing là khi bạn truyền 1 giá trị cho 1 phương thức có
tham số là đối tượng. Trường hợp này rất thường gặp ở .NET 1.x trước khi có sự ra đời
của generic. Những class non-generic hầu như làm việc hiệu quả với kiểu đối tượng và
đoạn mã sau cho kết quả boxing và unboxing


<i>ArrayList userIds = new ArrayList(2); </i>
<i>userIds.Add(1); </i>


<i>userIds.Add(2);; </i>


<i>int firstId = (int)userIds[0];</i>


</div>
<span class='text_page_counter'>(53)</span><div class='page_container' data-page=53>

hay khơng thì boxing cũng là 1 ví dụ chính về cách mà bộ nhớ hệ thống có thể ảnh hưởng
đến chương trình của bạn


<b>ByRef</b>


Nếu khơng hiểu rõ con trỏ, hầu như là bạn không thể hiểu giữa truyền giá trị và truyền
tham khảo. Các nhà phát triển nói chung hiểu được tại sao phải truyền 1 giá trị bằng tham
khảo nhưng ít người hiểu tại sao phải truyền 1 tham khảo bằng 1 tham khảo. ByRef và
ByVal ảnh hưởng đến kiểu tham khảo và kiểu giá trị như nhau. Sử dụng BeRef là 1
trường hợp thường gặp khi .NET không tự động giải quyết mối quan hệ của con trỏ
(truyền tham khảo hay trả về dạng tham khảo không tồn tại trong Java)


Trước hết chung ta sẽ tìm hiểu ByVal/Byref ảnh hưởng đến các kiểu giá trị:



<i>public static void Main() </i>
<i>{ </i>


<i> int counter1 = 0; </i>
<i> SeedCounter(counter1); </i>
<i> Console.WriteLine(counter1); </i>
<i> </i>


<i> int counter2 = 0; </i>


<i> SeedCounter(ref counter2); </i>
<i> Console.WriteLine(counter2); </i>
<i>} </i>


<i>private static void SeedCounter(int counter) </i>
<i>{ </i>


<i> counter = 1; </i>
<i>} </i>


<i>private static void SeedCounter(ref int counter) </i>
<i>{ </i>


<i> counter = 1; </i>
<i>}</i>


Chúng ta nghĩ là sẽ ra kết quả 0 rồi 1. Lời gọi hàm đầu tiên khơng truyền counter1 bằng
tham khảo có nghĩa là 1 bản sao của counter1 sẽ được truyền cho SeedCounter và những
thay đổi xảy ra chỉ là cục bộ đối với hàm này. Nói cách khác, chúng ta lấy giá trị ở trên


stack và sao chép nó vào 1 vị trí khác trên stack.


Trong trường hợp thứ 2, chúng ta thực sự truyền giá trị bởi tham khảo có nghĩa là khơng
có bản sao nào được tạo ra và những thay đổi không cục bộ đối với hàm SeedCounter.
Cách hoạt động của kiểu tham khảo là hoàn tồn tương tự mặc dù lúc đầu thì có vẻ khác.
Chúng ta sẽ xem xét 2 ví dụ sau: Ví dụ đầu tiên sử dụng class PayManagement để thay
đổi thuộc tính của 1 Employee. Trong đoạn mã sau, chúng ta sẽ thấy 2 nhân viên trong
cùng trường hợp đều được nâng lương $2000. Sự khác nhau ở chỗ 1 người được truyền
tham khảo 1 được truyền theo giá trị.


<i>public class Employee </i>
<i>{ </i>


<i> private int _salary; </i>
<i> public int Salary </i>
<i> { </i>


<i> get {return _salary;} </i>
<i> set {_salary = value;} </i>
<i> } </i>


<i> public Employee(int startingSalary) </i>
<i> { </i>


<i> _salary = startingSalary; </i>
<i> } </i>


</div>
<span class='text_page_counter'>(54)</span><div class='page_container' data-page=54>

<i>public class PayManagement </i>
<i>{ </i>



<i> public static void GiveRaise(Employee employee, int raise) </i>
<i> { </i>


<i> employee.Salary += raise; </i>
<i> } </i>


<i> public static void GiveRaise(ref Employee employee, int raise) </i>
<i> { </i>


<i> employee.Salary += raise; </i>
<i> } </i>


<i>} </i>


<i>public static void Main() </i>
<i>{ </i>


<i> Employee employee1 = new Employee(10000); </i>
<i> PayManagement.GiveRaise(employee1, 2000); </i>
<i> Console.WriteLine(employee1.Salary); </i>


<i> </i>


<i> Employee employee2 = new Employee(10000); </i>


<i> PayManagement.GiveRaise(ref employee2, 2000); </i>
<i> Console.WriteLine(employee2.Salary); </i>


<i>}</i>



Trong cả 2 trường hợp, kết quả xuất ra đều là 12000. Trước hết, có vẻ nó khác với điều
mà ta đã thấy với kiểu giá trị. Điều xảy ra khi chúng ta truyền 1 kiểu tham khảo như kiểu
giá trị là truyền 1 bản sao của giá trị mà không phải giá trị trên heap. Thay vào đó, chúng
ta truyền 1 bản sao của con trỏ. Và vì con trỏ và bản sao của con trỏ đều chỉ đến cùng 1 vị
trí trên heap nên thay đổi diễn ra ở con trỏ cũng sẽ được thấy ở bản sao và ngược lại.
Khi chúng ta truyền 1 tham khảo theo kiểu tham khảo, chúng ta thật sự đã truyền con trỏ
trong trường hợp của bản sao của con trỏ đó. Điều này làm xuất hiện 1 câu hỏi, khi nào
chúng ta cần truyền 1 tham khảo bằng tham khảo ? Lí do duy nhất để làm vậy là khi bạn
muốn chỉnh sửa con trỏ - chỉnh sửa nơi mà nó trỏ tới. Điều này sẽ dẫn đến 1 số tác dụng
phụ không mong muốn. Chúng ta hãy xem xét ví dụ sau:


<i>public class Employee </i>
<i>{ </i>


<i> private int _salary; </i>
<i> public int Salary </i>
<i> { </i>


<i> get {return _salary;} </i>
<i> set {_salary = value;} </i>
<i> } </i>


<i> public Employee(int startingSalary) </i>
<i> { </i>


<i> _salary = startingSalary; </i>
<i> } </i>


<i>} </i>



<i>public class PayManagement </i>
<i>{ </i>


<i> public static void Terminate(Employee employee) </i>
<i> { </i>


<i> employee = null; </i>
<i> } </i>


<i> public static void Terminate(ref Employee employee) </i>
<i> { </i>


<i> employee = null; </i>
<i> } </i>


</div>
<span class='text_page_counter'>(55)</span><div class='page_container' data-page=55>

<i>public static void Main() </i>
<i>{ </i>


<i> Employee employee1 = new Employee(10000); </i>
<i> PayManagement.Terminate(employee1); </i>
<i> Console.WriteLine(employee1.Salary); </i>
<i> </i>


<i> Employee employee2 = new Employee(10000); </i>
<i> PayManagement.Terminate(ref employee2); </i>
<i> Console.WriteLine(employee2.Salary); </i>
<i>}</i>


Cố tìm hiểu điều gỉ sẽ xảy ra và tại sao. Tôi sẽ cho bạn 1 gợi ý là 1 exception sẽ xuất
hiện. Nếu bạn đoán rằng lời gọi employee1.Salary sẽ xuất ra 10000 trong khi kết quả thứ


2 sẽ trả về NullReferenceException thì bạn đã đúng. Trong trường hợp 1. chúng ta chỉ
sao chép giá trị của con trỏ về Null – nó khơng ảnh hưởng đến cái mà employee1 trỏ tới.
Trong trường hợp 2, chúng ta không không truyền 1 bản sao mà là 1 giá trị trên stack
được dùng bởi employee2. Vì vậy khi xác lập employee thành null tương đương với viết
employee2 = null.


Việc thay đổi địa chỉ trỏ bởi 1 con trỏ bằng 1 biến ở phương thức khác là khơng thường
gặp – đó là lí do tại sao bạn chỉ thấy việc truyền 1 tham khảo bằng 1 tham khảo khi bạn
muốn trả về nhiều giá trị từ 1 lời gọi hàm ( trong trường hợp đó tốt hơn là sử dụng thơng
số out hoặc các cách hồn tồn hướng đối tượng). Ví dụ trên chỉ ra những nguy hiểm khi
làm việc ở môi trường mà bạn không hiểu rõ cách hoạt động của nó.


<b>Quản lý thất thốt bộ nhớ</b>


Chúng ta đã thấy ví dụ về thất thoát bộ nhớ ở C. Đối với C# nếu khơng có trình dọn rác
thì đoạn mã sau sẽ gây ra thất thoát:


<i>private void DoSomething() </i>
<i>{ </i>


<i> string name = "dune"; </i>
<i>}</i>


Giá trị trên stack (1 con trỏ) sẽ được lấy ra khỏi và khi mất nó chúng ta mất đi cách duy
nhất để tham khảo đến vùng nhớ chứa chuỗi đó đồng thời khơng có cách nào để giải
phóng nó. Đây khơng phải là 1 vấn đề ở .NET vì nó đã có trình dọn rác để tìm kiếm
những vùng nhớ khơng được tham khảo và giải phóng nó. Tuy nhiên 1 loại thất thốt bộ
nhớ vẫn có thể xảy ra nếu bạn sử dụng tham khảo vô tận. Đây là 1 lỗi thường gặp trong
các phần mềm lớn với các tham khảo lồng nhau. Chúng rất khó để phát hiện vì có thể
vùng thất thốt rất nhỏ và chương trình của bạn chạy khơng đủ lâu để phát hiện nó.


Khi chương trình của bạn kết thúc và OS sẽ lấy lại tồn bộ bộ nhớ dù có bị thất thốt hay
khơng. Tuy nhiên, nếu bạn thấy OutOfMemoryException và không đang làm việc với các
dữ liệu lớn, có thể bạn sẽ gặp thất thốt bộ nhớ. .NET có cung cấp các cơng cụ để giúp
bạn tuy nhiên bạn có thể dùng các chương trình thương mại như dotTrace hay ANTS
Profiler. Để tìm thất thốt bộ nhớ bạn sẽ cần tìm những đối tượng bị thất thốt (chúng rất
dễ tìm chỉ cần bạn chụp hình bộ nhớ của bạn 2 lần sau đó so sánh chúng) duyệt qua tất cả
đối tượng vẫn còn 1 tham khảo tới nó sau đó sửa lỗi.


</div>
<span class='text_page_counter'>(56)</span><div class='page_container' data-page=56>

(iDisposable là 1 giải pháp hoàn hảo) hoặc sử dụng WeakEvent Pattern hay 1 phiên bản
đơn giản hơn.


<b>Phân mảnh</b>


1 lí do thường gây ra OutOfMemoryException là phân mảnh bộ nhớ. Khi bộ nhớ được
cấp phát trên heap, nó là 1 khối liên tục. Điều này có nghĩa là vùng nhớ cịn lại phải được
qt để tìm 1 khối nhớ đủ lớn. Khi chương trình chạy, vùng heap trở nên phân mảnh (như
ổ cứng của bạn) và bạn sẽ gặp trường hợp là nhiều khoảng trống nắm rải rác nhưng
khơng thể sử dụng được. Bình thường thì trình dọn rác sẽ tối ưu hóa bộ nhớ bằng cách
giải phóng bộ nhớ. Khi tối ưu hóa bộ nhớ, địa chỉ của các đối tượng thay đổi và .NET sẽ
cập nhật các tham khảo để phù hợp. Đôi khi, .NET không thể dời 1 đối tượng: khi 1 đối
tượng được gắn chặt vào 1 vùng nhớ nhất định.


<b>Kết dính bộ nhớ (Pinning)</b>


Kết dính xảy ra khi 1 đối tượng được gắn chặt vào 1 vùng nhớ xác định trên heap. Vùng
nhớ được kết dính khơng thể tối ưu bằng trình dọn rác và sẽ dấn đến phân mảnh. Tại sao
các giá trị lại bị kết dính ? Lí do thường gặp nhất vì đoạn mã của bạn có tương tác với mã
khơng được quản lí. Khi trình dọn rác tối ưu vùng heap, nó cập nhật tất cả tham khảo
trong đoạn mã được quản lý tuy nhiên nó khơng thể nào xử lí được đoạn mã khơng được
quản lý. Vì vậy trước khi biên dịch nó phải gắn đối tượng vào bộ nhớ. Vì có nhiều


phương thức của .NET phụ thuộc vào mã khơng được quản lý, kết dính xảy ra ngay cả
khi bạn khơng biết về nó (ví dụ tơi thường gặp nhất là .NET socket class phụ thuộc vào
cách hiện thực các đoạn mã không được quản lý và buffer)


Một cách xử lí là tạo các đối tượng lớn vì chúng sẽ khơng gây ra phân mảnh như các đối
tượng nhỏ (điều này càng chính xác hơn đối với các đối tượng được đặt trong 1 vùng nhớ
heap đặc biệt (Large Object Heap) mà không được tối ưu(compact)) Ví dụ, thay vì tạo 1
hàng trăm buffer 4KB, bạn có thể tạo 1 buffer lớn và tự gán từng khối. Ví dụ cũng như
các thơng tin về kết dính bạn có thể đọc bài viết Gred Young về kết dính và socket
khơng đồng bộ.


Có 1 lí do thứ 2 mà 1 đối tượng phải được kết dính – khi bạn bắt nó phải như vậy. Trong
C# (không phải VB.NET) nếu bạn biên dịch assembly với tùy chọn unsafe, bạn có thể kết
dính đối tượng thơng qua từ khóa fixed. Mặc dù kết dính q nhiều sẽ dẫn đến áp lực lên
hệ thống, tuy nhiên việc sử dụng câu lệnh fixed 1 cách chuẩn xác có thể làm tăng hiệu
suất. Vì sao ? Vì 1 đối tượng được kết dính có thể được điều chỉnh trực tiếp bằng phép
toán con trỏ - điều này không thể nếu đối tượng không được kết dính bởi vì trình dọn rác
sẽ tái cấp phát đối tượng của bạn ở nơi khác trong bộ nhớ.


Ví dụ sau đây chuyển từ chuỗi ASCII sang số nguyên chạy nhanh gấp 6 lần hàm int.Parse


<i>public unsafe static int Parse(string stringToConvert) </i>
<i>{ </i>


<i> int value = 0; </i>


<i> int length = stringToConvert.Length; </i>
<i> fixed(char* characters = stringToConvert) </i>
<i> { </i>



<i> for (int i = 0; i < length; ++i) </i>
<i> { </i>


<i> value = 10 * value + (characters[i] - 48); </i>
<i> } </i>


<i> } </i>


<i> return value; </i>
<i>}</i>


</div>
<span class='text_page_counter'>(57)</span><div class='page_container' data-page=57>

gì sẽ xảy ra) và khơng có nhiều chức năng như int.Parse và nói chung nó rất nguy hiểm
trong khi rất ít lợi ích.


<b>Xác lập giá trị null</b>


Vậy, bạn có nên gán các kiểu tham khảo thành null khi bạn không cịn làm việc với
chúng ? Dĩ nhiên khơng. Khi 1 biến ra khỏi 1 khối, nó sẽ được đẩy ra khỏi stack và tham
khảo sẽ bị xóa. Nếu bạn khơng thể chờ để kết thúc khối đó bạn nên viết lại mã của mình,

<b>Kết thúc tiền định</b>



Mặc dù có trình dọn rác nhưng các nhà lập trình vẫn phải quản lý các tham khảo của
mình. Đó là vì vài đối tượng giữ những tài nguyên quan trọng hay giới hạn như file hoặc
kết nối CSDL. Chúng phải được giải phóng càng sớm càng tốt. Điều này là 1 vấn đề vì
chúng ta khơng biết lúc nào trình dọn rác chạy – thực tế trình dọn rác chỉ chạy khi bộ nhớ
cịn rất ít. Đổi lại, các class chứa những tài nguyên như vậy nên sử dụng Disposable. Tất
cả nhà lập trình .NET đều quen với khái niệm này cùng với hiện thực cụ thể của nó
(Idisposable interface)vì vậy chúng ta khơng cần tìm hiểu lại thứ mà chúng ta đã biết.
Trong chương này, bạn chỉ cần hiểu vai trò của kết thúc tiền định. Nó khơng giải phóng
bộ nhớ sử dụng bởi đối tượng. Nó giải phóng tài nguyên. Trong trường hợp của kết nối


CSDL, nó giải phóng kết nối để sử dụng lại.


Nếu bạn quên gọi hàm Dispose trên 1 đối tượng hiện thực Idisposable, trình dọn rác sẽ
làm việc này cho bạn. Tuy nhiên bạn không thể tin tưởng nó vì vấn đề về hạn chế tài
nguyên là có thật (nó rất tầm thường nếu bạn thử dùng 1 dòng lặp để mở liên kết tới
CSDL). Bạn có thể thắc mặc tại sao 1 số đối tượng hiện thực cả 2 phương thức Close và
Dispose và bạn nên gọi cái nào. Trong mọi trường hợp tơi thấy chúng tương đương nhau
– vì vậy chọn cái nào là do bạn. Tôi đề nghị bạn sử dụng câu lệnh using và quên đi Close.
Một cách riêng tư tôi cảm thấy hơi bực bội (và không nhất quán) khi cả 2 đều được dùng.
Cuối cùng nếu bạn tạo 1 class dùng kết thúc tiền định bạn sẽ thấy hiện thực IDIsposable
là đơn giản. Có 1 bài hướng dẫn rất rõ trên MSDN


<b>Trong chương này</b>



</div>
<span class='text_page_counter'>(58)</span><div class='page_container' data-page=58>

<b>CHƯƠNG 8</b>

<b> Trở lại căn bản: Exception</b>


Thất bại nhanh chóng – Jim Shore


Exception là những cấu trúc rất mạnh mà lập trình viên ít khi quản lí và khơng được
chuẩn bị đầy đủ khi làm việc với nó. Điều này rất đáng tiếc vì exception thật ra đóng vai
trị quan trọng trong việc làm cho hệ thống của lập trình viên chạy tốt. Trong chương này
chúng ta sẽ tìm hiểu các khái niệm : xử lý, tạo và throw exception. Vì exception là khơng
thể tránh được vì vậy bạn khơng thể chạy hay là trốn nó.


<b>Xử lý exception</b>



Chiến lược xử lí exception gồm 2 nguyên tắc vàng:
1.Chỉ xử lí exception mà bạn thật sự có thể xử lý


2. Và bạn khơng thể làm bất cứ việc gì về số lượng ngoại lệ vốn có rất rất nhiều.
Nhiều nhà lập trình viên mới làm điều hồn tồn trái ngược với nguyên tắc thứ nhất và


chiến đấu trong vô vọng với nguyên tắc thứ hai. Khi chương trình của bạn hoạt động
khơng bình thường điều tốt nhất cần phải làm là loại bỏ nó tại đó và ngay lúc đó. Nếu bạn
khơng làm như vậy, bạn sẽ khơng chỉ mất những thơng tin quan trọng về lỗi đó mà bạn
cịn đặt chương trình của bạn vào một trạng thái khơng xác định, điều này có thể gây ra
những hậu quả nghiêm trọng hơn.


Bất cứ khi nào bạn sử dụng câu lệnh try/catch, hãy tự hỏi mình bạn có thể làm gì được
khi tạo một exception. Nếu cơ sở dữ liệu của bạn bị tắt, bạn có thể viết mã để phục hồi nó
hay khơng hay bạn sẽ hiển thị một thông báo lỗi tới người dùng và nhận thơng báo về lỗi
đó. Trước tiên, điều này có thể khó chấp nhận nhưng đơi khi tốt hơn là để chương trình bị
crash và ghi lại lỗi sau đó tiếp tục. Ngay cả đối với những hệ thống quan trọng, nếu bạn
sử dụng một CSDL bạn sẽ làm gì nếu nó bị hỏng? Lối suy nghĩ này không chỉ giới hạn
đối với CSDL mà ngay cả đối với những lỗi bình thường. Nếu bạn chuyển một giá trị cấu
hình thành một số nguyên và tạo một FormatException điều này có ý nghĩa khơng nếu
bạn cứ tiếp tục như là khơng có chuyện gì xảy ra? Chắc chắn là khơng.


Dĩ nhiên nếu bạn có thể giải quyết một exception thì bạn chắc chắn nên làm – nhưng chắc
chắn là chỉ bắt những exception mà bạn có thể giải quyết. Bắt exception và khơng xử lí
chúng được gọi là nuốt exception và đó là một đoạn mã xấu. Một ví dụ thường thấy là
kiểm tra giá trị đầu vào. Ví dụ sau chúng ta hãy xem xét giá trị categoryId được chuyển
từ QueryString của một trang ASP.NET


<i>int categoryId; </i>
<i>try </i>


<i>{ </i>


<i> categoryId = int.Parse(Request.QueryString["categoryId"]); </i>
<i>} </i>



<i>catch(Exception)</i>
<i>{ </i>


<i> categoryId = 1; </i>
<i>}</i>


</div>
<span class='text_page_counter'>(59)</span><div class='page_container' data-page=59>

<i>int categoryId; </i>
<i>try </i>


<i>{ </i>


<i> categoryId = int.Parse(Request.QueryString["categoryId"]) </i>
<i>} </i>


<i>catch(FormatException) </i>
<i>{ </i>


<i> categoryId = -1; </i>
<i>}</i>


(Một cách tiếp cận tốt hơn có thể được dùng với hàm int.TryParse dùng trong .NET 2.0 –
đặc biệt cần xem xét rằng in.Parse có thể ném ra 2 loại ngoại lệ khác mà chúng ta cũng
muốn điểu khiển theo cùng một cách, nhưng đây là một vấn đề khác).


<b>Logging </b>


Mặc dù hầu hết những ngoại lệ sẽ không được điều khiển, bạn vẫn nên ghi chép (log) lại
từng đố tượng một của mỗi loại. Một cách lý tưởng, bạn sẽ tập trung tất cả thông tin ghi
chét – một loại sự kiện OnError của kiểu HttpModule là lựa chọn tốt nhất cho một ứng
dụng ASP.NET hoặc web service. Tôi thường thấy những nhà phát triển nắm bắt ngoại lệ


khi chúng xuất hiện chỉ để ghi lại và rồi ném nó đi. Điều này gây ra những đoạn code lặp
và dư thừa – tốt hơn là để cho những ngoại lệ nổi lên xuyên suốt code của bạn và ghi lại
tất cả tại phần rìa ngồi của hệ thống. Có thể bạn sẽ muốn được thơng báo bởi email ngay
khi ngoại lệ xuất hiện, hoặc là xem lại nó mỗi ngày hoặc thơng qua một q trình khác để
gửi tới bảng tổng kết hằng ngày của bạn. Rất nhiều nhà phát triển tận dụng những khuôn
<i>mẫu logging sẵn có như là log4net hoặc Microsoft’s Logging Application Block.</i>


(Một vài từ cảnh báo dựa trên kinh nghiệm cá nhân : một vài kiểu ngoại lệ có khuynh
hướng bó lại thành cụm. Nếu bạn chọn việc gửi một email bất cứ khi nào có ngoại lệ xuất
hiện thì bạn sẽ nhanh chóng là cho hộp mail của mình bị tràn. Một giải pháp logging
không ngoan là hiện thực một vài dạng đệm (buffering) hoặc kết hợp (aggregation).


<b>Cleaning (làm sạch)</b>


Trong chương trước chúng ta đã nói về sự sự hồn thành tất định ứng với bản chất làm
việc “lười biếng” của những chương trình thu dọn rác. Ngoại lệ tăng thêm sự phức tạp
bởi vì bản chất rời rạc của nó có thể làm cho việc gọi phương thức Dispose có thể khơng
diễn ra. Xét một lời gọi database theo cách cổ điển :


<i>SqlConnection connection = new SqlConnection(FROM_CONFIGURATION) </i>
<i>SqlCommand command = new SqlCommand("SomeSQL", connection); </i>
<i>connection.Open(); </i>


<i>command.ExecuteNonQuery(); </i>
<i>command.Dispose(); </i>


<i>connection.Dispose();</i>


Nếu phương thức ExecuteNonQuery ném ra một ngoại lệ, cả câu lệnh cũng như kết nối
của chúng ta sẽ bị bỏ đi. Mộ giải pháp là dùng thêm phát biểu Try/Finally:



<i>SqlConnection connection; </i>
<i>SqlCommand command; </i>
<i>try </i>


<i>{ </i>


<i> connection = new SqlConnection(FROM_CONFIGURATION) </i>
<i> command = new SqlCommand("SomeSQL", connection); </i>
<i> connection.Open(); </i>


</div>
<span class='text_page_counter'>(60)</span><div class='page_container' data-page=60>

<i>finally </i>
<i>{ </i>


<i> if (command != null) { command.Dispose(); } </i>
<i> if (connection != null) { connection.Dispose(); } </i>
<i>}</i>


<i>hoặc tốt hơn nữa về mặt cú pháp là dùng phát biểu using (phát biểu này cũng sẽ được</i>
biên dịch thành dạng try/finally như trên):


<i>using (SqlConnection connection = new SqlConnection(FROM_CONFIGURATION)) </i>
<i>using (SqlCommand command = new SqlCommand("SomeSQL", connection)) </i>
<i>{ </i>


<i> connection.Open(); </i>


<i> command.ExecuteNonQuery(); </i>
<i>}</i>



Điều quan trọng là, ngay cả khi bạn không thể điều khiển được một ngoại lệ, và bạn tập
trung tất cả những việc ghi chép (logging), bạn vẫn cần phải quan tâm tới vị trí mà ngoại
lệ xuất hiện, đặc biệt là khi nó đi cùng với những lớp hiện thực Idisposable.


<b>Ném ngoại lệ</b>



Khơng có một quy tắc ma thuật nào trong việc ném ngoại lệ như đới đối với việc bắt nó
(nhắc lại, đối với việc bắt ngồi lệ, quy tắc là khơng bắt ngoại lệ trừ khi bạn thực sự điều
khiển được nó). Tuy nhiên, khi ném ngoại lệ, dù cho những ngoại lệ đó có của riêng bạn
hay khơng (điều sẽ được nói tới sau), thì cơng việc cũng tương đối đơn giản, Đầu tiên
chúng ta nhìn và cơ chế thực sự của việc ném ngoại lệ, chủ yếu dựa vào câu lệnh throw.
Sau đó chúng ta xem xét khi nào và vì sao bạn thực sự muốn ném ngoại lệ.


<b>Cơ chế ném ngoại lệ</b>


Bạn có thể hoặc là ném ra một ngoại lệ mới, hoặc là ném lại một ngoại lệ đã được bắt. Để
ném một ngoại lệ mới, đơn giản là tạo ra và ném nó.


create a new exception and throw it.


<i>throw new Exception("something bad happened!"); </i>
<i>//or </i>


<i>Exception ex = new Exception("something bad happened"); </i>
<i>throw ex;</i>


Tác giả đã thêm ví dụ hai bởi vì một vài nhà phát triển nghĩ ngoại lệ mà một trường hợp
đặc biệt, trường hợp riêng – nhưng sự thật là chúng chỉ giống như đối tượng khác (ngoại
trừ nó thừa kế từ System.Exception, một lớp thừa kế từ System.Object). Thật ra, bạn tạo
ra một ngoại lệ mới khơng có nghĩa là bạn ném nó, dù rằng bạn sẽ thực sự làm vậy.


Thỉnh thoảng bạn sẽ cần ném lại một ngoại lệ, bởi vì khi bạn khơng thể giải quyết ngoại
lệ, bạn vẫn cần thực thi những đoạn code khi ngoại lệ xuất hiện. Ví dụ phổ biến nhất là
quay ngược lại một giao dịch thất bại:


<i>ITransaction transaction = null; </i>
<i>try </i>


<i>{ </i>


<i> transaction = session.BeginTransaction(); </i>
<i> // do some work </i>


<i> transaction.Commit(); </i>
<i>} </i>


<i>catch </i>
<i>{ </i>


</div>
<span class='text_page_counter'>(61)</span><div class='page_container' data-page=61>

<i> throw; </i>
<i>} </i>


<i>finally </i>
<i>{ </i>


<i> //cleanup </i>
<i>}</i>


<i>Trong ví dụ trên, những câu lệnh throw làm cho những câu lệnh catch trở nên trong suốt.</i>
Đó là, những câu lệnh điều khiển ngoại lệ ở phía trên chuỗi thực thi khơng có những chỉ
dẫn nào rằng chúng ta đã bắt được ngoại lệ. Trong hầu hết các trường hợp, và đây cũng là


điều chúng ta muốn – quay ngược loại giao dịch thực sự không giúp đỡ bất kỳ ai khác
điều khiển ngoại lệ. Tuy nhiên, có một cách để ném lại ngoại lệ, trong đó có vẻ như ngoại
lệ xuất hiện bên trong đoạn code:


<i>catch (HibernateException ex) </i>
<i>{ </i>


<i> if (transaction != null) { transaction.Rollback(); } </i>
<i> throw ex; </i>


<i>}</i>


Bằng cách ném ngoại lệ một cách tường minh, vết của ngăn xếp được điều chỉnh để từ đó
dịng lệnh ném lại ngoại lệ xuất hiện ở mã nguồn. Đây hầu như luôn là một ý tưởng tồi,
giống như bị mất đi những thơng tin sống cịn. Vì vậy hãy cẩn thận với việc ném lại ngoại
lệ - nhận ra sự khác biệt đòi hỏi sự tinh tế và cũng rất quan trọng. Nếu bạn gặp phải
những tình huống như thế, khi mà bạn sẽ ném lại một ngoại lệ với phần điều khiển trong
mã nguồn, một cách làm tốt hơn là dùng những ngoại lệ lồng nhau:


<i>catch (HibernateException ex) </i>
<i>{ </i>


<i> if (transaction != null) { transaction.Rollback(); } </i>
<i> throw new Exception("Email already in use", ex); </i>
<i>} </i>


Với cách này, vết ngăn xếp ban đầu vẫn có thể truy cập được thơng qua thuộc tính
InnerException sẵn có cho mọi loại ngoại lệ.


<b>Khi nào phải ném ngoại lệ</b>



Một điều quan trọng đó là biết khi nào cần ném ra một ngoại lệ. Một chủ đề thú vị hơn
nhiều là khi nào và tại sao ta nên ném chúng. Làm cho những đoạn code “bất trị” gậy hại
cho ứng dụng của bạn là một yếu tố. Viết những đoạn code của riêng bạn để làm lại
những điều đó thì có vẻ hoàn toàn ngốc ngếch. Tuy nhiên, một nhà phát triển giỏi khơng
ngại gì việc sử dụng ngoại lệ một cách sáng suốt.


Thực sự có hai mức đọc suy nghĩ xem ngoại lệ nên dùng như thế nào. Ở mức độ thức
nhất, và được chấp nhận rộng rãi, đó là bạn không nên lưỡng lự khi bắt lấy một ngoại lệ
khi một tình huống ngoại lệ thực sự xuất hiện. Ví dụ ưa thích của người viết đó là việc
phân tích cú pháp (parse) của những file cấu hình. Nhiều nhà phát triển sử dụng một cách
hào phóng những giá trị mặc định cho bất kỳ những mục không hợp lệ nào. Điều này sẽ
khơng có vấn đề gì trong một vài trường hợp, nhưng có những khi nó đặt hệ thống vào
một tình trạng khơng đáng tin cậy hoặc một trạng thái khơng mong đợi. Một ví dụ khác
có thể là một ứng dụng Facebook trong đó trả về một kết quả không mong đợi từ lời gọi
một hàm trong API. Bạn có thể bỏ qua lỗi này, hoặc có thể giải quyết ngoại lệ này, ghi
chép lại nó (để từ đó có thể sửa lỗi, bởi vì API có thể đã thay đổi) và thể hiện một thông
điệp hữu ích cho người dùng của mình.


</div>
<span class='text_page_counter'>(62)</span><div class='page_container' data-page=62>

(design by contract) – một phương pháp luận mà người viết đang sử dụng mỗi lúc một
nhiếu. Về mặt bản chất, nếu phương thức SaveUser khơng thể lưu lại người dùng, nó nên
ném ra một ngoại lệ.


Trong những ngôn ngữ như C#, VB.NET và Java, với việc không hỗ trợ cơ chế thiết kế
theo thỏa thuận, cách tiếp cận này có thể đưa ra những kết quả lẫn lộn. Một Hashtable
(bảng băm) trả về null khi một khóa khơng được tìm thấy, nhưng một Dictionary ném ra
một ngoại lệ - một cách hành xử không mong đợi xuất hiện (nếu độc giả thấy tị mị vì
sao chúng hoạt động khác nhau thì có thể kiểm tra tại bài blog của Brad Abrams). Cúng
có một sợi dây liên hệ giữa những yếu tố hợp thành dịng điều khiển và những gì được
xem là ngoại lệ. Ngoại lệ không nên dùng để để điều khiển một vấn đề logic kiểu nếu/thì,


mà cho những cơng việc lớn hơn, chẳng hạn như trong một thư viên, giống như cách mà
hầu hết các lậptrình viên sẽ sử dụng chúng (phương thức int.Parse là một ví dụ tốt của
vấn đề này).


Nói chung, người viết nhận thấy rằng dễ dàng xác định những gì nên và khơng nên ném
ra một ngoại lệ. Tác giả thường tự đặt ra những câu hỏi, ví dụ như :


 Điều này có thực sự ngoại lệ
 Điều này có được trơng đợi


 Tơi có thể tiếp tực là điều gì đó có ý nghĩa tại điểm này


 Có điều gì tơi nên đặt trong một trạng thái lưu ý để tơi có thể sửa chữa, hoặc ít
nhất là xem lại lần thứ hai.


Có lẽ điều quan trọng nhất khi ném một ngoại lệ, hay giải quyết một ngoại lệ nói chung,
là nghĩ về người dùng. Đa số người dùng rất “ngây thơ” khi đem so sánh họ với lập trình
viên, và có thể dễ dàng trỡ nên lo lắng khi gặp một thông điệp báo lỗi. Jeff Atwood gần
đây có viết blog về tầm quan trọng của việc chuẩn bị cho những trường hợp chương trình
của chúng ta có khả năng giải quyết một có hợp lý khi bị crash



---.


NGƯỜI DÙNG KHƠNG BUỘC PHẢI NÓI RÕ VỚI BẠN VỀ MỌI LỖI CỦA PHẦN
MỀM


ĐỪNG CHO NGƯỜI DÙNG THẤY NHỮNG MÀN HÌNH MẶC ĐỊNH ĐANG CHẾT
ĐỨNG



CĨ MỘT GHI CHÉP CƠNG KHAI, CHI TIẾT VỀ NHỮNG LỖI CỦA ỨNG DỤNG



---Có vẻ là an tồn khi nói rằng khi màn hình Windows hiện lên màu xanh của việc chương
trình chết đứng chính là thơng điệp báo lỗi không nên đưa ra cho người dùng (và đừng
nghĩ rằng chỉ vì những trở ngại đã bị làm chậm lại nhiều thì chúng ta có thể lười biếng).

<b>Tạo những ngoại lệ riêng</b>



</div>
<span class='text_page_counter'>(63)</span><div class='page_container' data-page=63>

thơng tin đặc tả cho nó, điều mà không chỉ giúp cho bạn xác định những lỗi tiềm ẩn, mà
cịn có thể được dùng để thể hiện những thơng tin có ý nghĩa cho người dùng.


Nhiều ví dụ về ngoại lệ mà tác giả tạo ra khơng có ý nghĩa gì hơn là ngoại lệ làm dấu, có
nghĩa là, mở rộng cơ sở của lớp System.Exception, và không cung cấp những cách hiện
thực xa hơn. Tác giả ví von điều này với giao diện làm dấu (hay là thuộc tính làm dấu), ví
dụ như giao diện InamingContainer. Những điều này một cách cụ thể hữu dụng khi cho
phép bạn tránh việc nuốt mất những ngoại lệ. Hãy xem đoạn code ví dụ sau. Nếu phương
thức Save() không ném ra một ngoại lệ riêng, chúng ta thực sự có ít lựa chọn ngoại trừ
việc nuốt mất ngoại lệ:


<i>try </i>
<i>{ </i>


<i> user.Save(); </i>
<i>} </i>


<i>catch </i>
<i>{ </i>


<i> Error.Text = user.GetErrors(); </i>


<i> Error.Visible = true; </i>


<i>} </i>


<i>//versus </i>
<i>try </i>
<i>{ </i>


<i> user.Save(); </i>
<i>} </i>


<i>catch(ValidationException ex) </i>
<i>{ </i>


<i> Error.Text = ex.GetValidationMessage(); </i>
<i> Error.Visible = true; </i>


<i>}</i>


Ví dụ trên cũng chỉ ra rằng chúng ta có thể mở rộng ngoại lệ để có những cách hành xử
riêng đặc biệt liên quan tới ngoại lệ. Điều này có thể đơn giải như một đoạn mã lỗi
(ErrorCode), hoặc phức thông tin phức tạp hơn như là một ngoại lệ PermissionException,
điều sẽ thể hiện ra sự cho phép với người dùng và những sự cho phép cần thiết cịn thiếu.
Hiểu nhiên là, khơng phải tất cả ngoại lệ đều được cột chặt vào lĩnh vực của nó. Chúng ta
vẫn thường thấy những ngoại lệ định hướng theo sự vận hành. Nếu bạn dựa trên một web
service để trả về một đoạn mã lỗi, bạn có thể phủ lên đó một ngoại lệ riêng của mình để
ngắt việc thực thi (gọi là “rơi” một cách chắc chắn) và nâng lên cơ sở hạ tầng cho việc
ghi chép (logging) của bạn.


Thực sự tạo ra một ngoại lệ riêng cần một quá trình hai bước. Đầu tiên (và về mặt kỹ


thuật đây là tất cả những gì bạn cần) là tạo ra một lớp, với một cái tên có nghĩa, thừa kế
từ System.Exception.


<i>public class UpgradeException : Exception </i>
<i>{ </i>


<i>}</i>


Bạn nên đi theo những bước tăng cường và đánh dấu lớp của bạn với thuộc tính
SerializeAttribute, và ln cung cấp ít nhất bốn phương thức tạo (constructor) sau đây :


public YourException()


public YourException(string message)


public YourException(string message, Exception innerException)


</div>
<span class='text_page_counter'>(64)</span><div class='page_container' data-page=64>

Ba phương thức đầu tiên cho phép ngoại lệ của bạn được sử dụng theo cách nó được
trơng đợi. Cái thứ tư được dùng để hỗ trợ việc tuần tự hóa (serialize) trong trường hợp
.NET cần tuần tự hóa ngoại lệ của bạn – nghĩa là bạn cũng sẽ hiện thực phương thức
GetObjectData. Chức năng của sự tuần tự hóa là, trong trường hợp bạn có những thuộc
tính riêng, cái mà bạn mong muốn sẽ tồn tại, được tuần tự hóa và bất tuần tự hóa
(serialize/deserialize). Đây là một ví dụ hồn chỉnh:


<i>[Serializable] </i>


<i>public class UpgradeException: Exception </i>


<i>{ </i>


<i> private int _upgradeId; </i>


<i> public int UpgradeId { get { return _upgradeId; } } </i>
<i> public UpgradeException(int upgradeId) </i>


<i> { </i>


<i> _upgradeId = upgradeId; </i>
<i> } </i>


<i> public UpgradeException(int upgradeId, string message, Exception inner) </i>
<i> : base(message, inner) </i>


<i> { </i>


<i> _upgradeId = upgradeId; </i>
<i> } </i>


<i> public UpgradeException(int upgradeId, string message) : base(message) </i>
<i> { </i>


<i> _upgradeId = upgradeId; </i>
<i> } </i>


<i> protected UpgradeException(SerializationInfo info, StreamingContext c) </i>
<i> : base(info, c) </i>


<i> { </i>



<i> if (info != null) </i>
<i> { </i>


<i> _upgradeId = info.GetInt32("upgradeId"); </i>
<i> } </i>


<i> } </i>


<i> public override void GetObjectData(SerializationInfo i, StreamingContext c) </i>
<i> { </i>


<i> if (i != null) </i>
<i> { </i>


<i> i.AddValue("upgradeId", _upgradeId); </i>
<i> } </i>


<i> base.GetObjectData(i, c) </i>
<i> }</i>


<i>}</i>


<b>Trong chương này</b>



</div>
<span class='text_page_counter'>(65)</span><div class='page_container' data-page=65>

<b>CHƯƠNG 9</b>

<b> Basic : Proxy This and Proxy That</b>







---MỘT TRONG NHỮNG VẺ ĐẸP CỦA LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG LÀ VIỆC
SỬ DỤNG LẠI MÃ NGUỒN THƠNG QUA Q TRÌNH THỪA KẾ, NHƯNG ĐỂ
ĐẠT ĐƯỢC ĐIỀU ĐĨ, LẬP TRÌNH VIÊN PHẢI THỰC SỰ ĐỂ CHO NHỮNG LẬP
TRÌNH VIÊN KHÁC TÁI SỬ DỤNG MÃ CỦA MÌNH – GARY SHORT


<i>Một vào từ khóa đơn giản nhưng có sức mạnh kinh ngạc như virtual trong C#</i>
<i>(overridable in VB.NET). Khi bạn đánh một phương thức là virtual, bạn cho phép một</i>
lớp thừa kế nó có thể hiện thực lại cách hành xử. Khơng có chức năng này, thừa kế và đa
<i>hình sẽ khơng cịn hữu dụng nữa. Một ví dụ đơn giản, điều chỉnh một tí từ Programming</i>


<i>Ruby (ISBN: 978-0-9745140-5-5)), trong đó lớp KaraokeSong override phương thức</i>


to_s(ToString) của lớp Song :


<i>class Song </i>
<i> def to_s </i>


<i> return sprintf("Song: %s, %s (%d)", @name, @artist, @duration) </i>
<i> end </i>


<i>end </i>


<i>class KaraokeSong < Song </i>
<i> def to_s </i>


<i> return super + " - " @lyrics </i>
<i> end </i>


<i>end</i>



Đoạn mã trên chỉ ra cách mà KaraokeSong có thể được xâu dựng phí trên cách thức hoạt
động của lớp cơ sở. Việc cụ thể hóa khơng chỉ đơn thuần về mặt dữ liệu, mà cả về cách
thức hoạt động! Even if your ruby is a little rusty, you might have picked up that the base
to_s method isn't marked as virtual. Đó là vì nhiều ngơn ngữ, gồm có Java, làm cho
<i>phương thức mặc định là virtual. Điều này thể hiện sự khác nhau cơ bản về quan điểm</i>
giữa những nhà thiết kế Java và những nhà thiết kế C#/VB.NET. Trong C#, phương thức
<i>mặc định là final, và người phát triển phải cho phép việc override một cách tường minh</i>
<i>(thơng qua từ khóa virtual). Trong Java, phương thức mặc định là virtual và người phát</i>
triển phải thực hiện một cách tường minh việc khơng cho phép override bằng từ khóa


<i>final.</i>


<i>(Tranh luận về việc thiết lập mặc định là virtual hay final là một việc rất thú vị, nhưng đi</i>
ra ngoài tầm vực của chương này. Nếu độc giả có hứng thú tìm hiểu, người viết đề nghị
bạn đọc bài phỏng vấn đối với Anders Heijsber (kiến trúc sư trưởng của C#), cũng như là
2 bài blog của Eric Gunnerson, và cũng nên xem thử quan điểm của Michael Feathers.)
<i>Một cách điển hình, những phương thức virtual được xem xét với mơ hình miền thừa kế</i>
tương ứng. Có nghĩa là, một lớp KaraokeSong thừa kế từ Song, hoặc một lớp Dog thừa
kế từ lớp Pet. Điều này là một khái niệm rất quan trọng, nhưng cũng được tài liệu hóa rất
<i>tốt và hiểu rõ. Do đó, chúng ta sẽ khảo sát những phương thức virtual cho một chứng</i>
năng có tính kỹ thuật hơn : proxy.


</div>
<span class='text_page_counter'>(66)</span><div class='page_container' data-page=66>

Proxy là một thứ hoạt động giống như những thứ khác. Về khía cạnh luật pháp, một
proxy là việc ai đó được ủy quyền để bó phiếu hoặc hành động đại diện cho người khác.
Một proxy có cùng những quyền và hành xử khá giống với người ủy nhiệm. Trong thế
giới phần cứng, một proxy server nằm giữa người dùng và server mà người dùng truy
cập. Proxy server đó rõ ràng hành xử hệ như một server thực, nhưng với những chức
năng thêm về - như là đệm (caching), ghi chép (logging) hoặc lọc (filtering). Trong phần
mềm, mẫu thiết kế proxy là một lớp hành xử hệt như những lớp khác. Ví dụ như, nếu
chúng ta xây dựng một tác vụ theo vết hệ thống, chúng ta có thể quyết định sử dụng một


proxy để áp dụng việc ủy quyền một cách rõ ràng ngay trên đối tượng của tác vụ đó:


<i>public class Task </i>
<i>{ </i>


<i> public static Task FindById(int id) </i>
<i> { </i>


<i> return TaskRepository.Create().FindById(id); </i>
<i> } </i>


<i> public virtual void Delete() </i>
<i> { </i>


<i> TaskRepository.Create().Delete(this); </i>
<i> } </i>


<i>} </i>


<i>public class TaskProxy : Task </i>
<i>{ </i>


<i> public override void Delete() </i>
<i> { </i>


<i> if (User.Current.CanDeleteTask()) </i>
<i> { </i>


<i> base.Delete(); </i>
<i> } </i>



<i> else </i>
<i> { </i>


<i> throw new PermissionException(...); </i>
<i> } </i>


<i> } </i>
<i>}</i>


Nhờ có tính đa hình, FingByID có thể trả về, hoặc là một đối tượng kiểu Task hoặc là
một đối tượng kiểu TaskProxy. Phía client gọi phương thức khơng cần biết rằng đối
tượng kiểu nào đã được trả về, nó thậm chí khơng cần phải biết một đối tượng TaskProxy
có tồn tại. Nó chỉ dựa trên API để lập trình đã được cơng khai của lớp Task.


Bởi vì một proxy chỉ là một lớp con hiện thực những cách hành xử được thêm vào, bạn
có thể tự hỏi về việc một lớp Dog là proxy của lớp Pet. Những proxy có khuynh hướng
hiện thực những chức năng kỹ thuật hệ thống khác (ghi chép, đệm, ủy quyền, kết nối từ
xa…) theo những cách thức rõ ràng. Nói cách khác, bạn sẽ khơng khai báo một biến
thuộc kiểu TaskProxy – mà hầu như bạn sẽ khai báo một biến kiểu Dog. Vì vậy một
proxy sẽ khơng thêm vào những thành phần (vì bạn khơng lập trình với API của nó),
trong khi một lớp Dog có thể thêm một phương thức Bark.


<b>Interception</b>



</div>
<span class='text_page_counter'>(67)</span><div class='page_container' data-page=67>

dễ dàng hơn để hiểu được những gì diễn ra phía sau vỏ bọc, nhưng ở cùng một mức độ
hình mẫu áp dụng với RhinoMocks


(Một ghi chú bên lề về Nhibernate. Nó được xem như đối tượng ánh xạ O/R một cách
trong suốt, êm thấm, bởi vì nó khơng địi hỏi bạn phải điều chỉnh những lớp của mình để


có thể hoạt động. Tuy nhiên, nếu bạn muốn cho phép khả năng tải từ từ, tất cả thành phần
<i>phải là virtual. Điều này được xem là êm thấm/trong suốt bởi vì bạn khơng thêm vào</i>
thành phần NHibernate cụ thể cho lớp của bạn – ví dụ như thừa kế từ một lớp cơ sở
NHibernate hoặc là rải rác những thuộc tính NHibernate ở mọi nơi)


Sử dụng NHibernate có hai cơ hội khác nhau để thúc đẩy việc tải từ từ. Đầu tiên, và hiển
<i>nhiên nhất, đó là tải với những tập hợp (collection) con. Ví dụ như, bạn có thể khơng</i>
muốn tải tồn bộ phần Upgrades của đối tượng Model cho tới khi bạn thực sự cần nó.
Đây là cách mà file ánh xạ của bạn nhìn gần giống như vậy:


<i><class name="Model" table="Models"> </i>
<i> <id name="Id" column="Id" type="int"> </i>
<i> <generator class="native" /> </i>
<i> </id> </i>


<i> ... </i>


<i> <bag name="Upgrades" table="Upgrades" lazy="true" > </i>
<i> <key column="ModelId" /> </i>


<i> <one-to-many class="Upgrade" /> </i>
<i> </bag> </i>


<i></class></i>


<i>Bằng cách điều chỉ những thuộc tính cho việc tải từ từ với thành phần bag, chúng ta đang</i>
nói với NHibernate để nạp một cách từ từ tập hợp Upgrades. NHibernate có thể làm việc
này một cách dễ dàng bởi vì nó trả về kiểu tập hợp của riêng nó (kiểu tập hợp trong đó
hiện thực những giao diện chuẩn, ví dụ như IList, và do đó, với bạn thì nó trong suốt).
Thứ hai, và cũng thú vị hơn, là cách dùng chức năng nạp từ từ thì dành riêng cho những


đối tượng nằm trong phạm vị. Ý tượng chung là thỉnh thoảng bạn sẽ muốn toàn bộ đối
tượng được khởi tạo một cách từ từ. Vì sao? Hãy nói rằng một biến sale đã được tạo ra.
Những Sale sẽ kết hợp với cả SalesPerson lẫn mô hình của Car.


<i>Sale sale = new Sale(); </i>


<i>sale.SalesPerson = session.Get<SalesPerson>(1); </i>
<i>sale.Model = session.Get<Model>(2); </i>


<i>sale.Price = 25000; </i>
<i>session.Save(sale);</i>


Không may là, chúng ta đã phải truy xuất database hai lần để nạp SalesPerson và Model
thích hợp – mặc dù chúng ta khơng thực sự dùng nó. Sự thật là tất cả những gì ta cần là
ID của chúng (bởi vì đó là cái mà chúng ta lấy được điền vào database), cái mà chúng ta
đã có sẵn.


Bằng cách tạo ra một proxy, NHibernate cho phép chúng ta hoàn toàn tải từ từ một đối
tượng của kiểu tương ứng với trường hợp hiện tại. Đều đầu tiên phải làm là thay đổi việc
ánh xạ và cho phép tải từ từ một đối tượng những Model và những SalesPeople :


<i><class name="Model" table="Models" lazy="true" proxy="Model">...</class> </i>
<i><class name="SalesPerson" table="SalesPeople" </i>


<i> lazy="true" proxy="SalesPerson ">...</class></i>


</div>
<span class='text_page_counter'>(68)</span><div class='page_container' data-page=68>

<i>proxy hữu ích với một danh sách những phương thức chưa khai báo virtual. Và giờ tốt</i>
hơn là nên xem thử tiếp:


<i>Sale sale = new Sale(); </i>



<i>sale.SalesPerson = session.Load <SalesPerson>(1); </i>
<i>sale.Model = session.Load<Model>(2); </i>


<i>sale.Price = 25000; </i>
<i>session.Save(sale);</i>


Chú ý rằng chúng ta đang dùng phương thức Load thay vì Get. Sự khác biệt giữa hai cái
này đó là nếu bạn thu được một lớp hỗ trợ nạp từ từ, phương thức Load sẽ lấy proxy,
trong khi Get sẽ lấy đối tượng thực. Với đoạn code được đưa vào này, chúng ta khơng
cịn “đánh” vào database mà chỉ nạp những ID. Thay vì vậy, gọi
Session.Load<Model>(2) trả về một proxy – sinh ra động bởi NHibernate. Proxy này sẽ
có một is của 2, bởi vì chúng ra đã cung cấp giá trị của nó, và mọi thuộc tính khác sẽ
khơng được khởi tạo. Bất cứ lời gọi một thành phần nào khác của proxy, ví dụ như
sale.Model.Name sẽ rõ ràng được chặn đúng và đối tượng sẽ được nap ngay khi cần từ
database.


Chỉ là một lưu ý, cách hành xử kiểu nạp từ từ của NHibernate có thể khó để chỉ ra khi gỡ
lỗi trong Visual Studio. Đó là vì chức năng watch/local/tooltip của VS.NET duyệt qua
đối tượng, là cho việc tải diễn ra ngay lập tức. Các tốt nhất để khảo sát điều gì đang diễn
ra là thêm vào một vài breakpoint xung quanh đoạn code và kiểm tra xem hoạt động của
database hoặc là thông qua ghi chép của NHibernate, hoặc thông qua mô tả sơ lược của
SQL.


Hy vọng rằng bạn có thể tưởng tượng cách mà proxy được dùng bởi RhinoMocks cho
việc thu thập, xem lại và xác thực sự tương tác. Khi bạn tạo ra một phần, bạn đang thực
sự tao ra một proxy của đối tượng thực. Proxy này chặn đứng mọi lời gọi, và tùy vào
trạng thái của bạn mà nó là những việc của mình. Hiển nhiên, để nó hoạt động, bạn phải
<i>hoặc là làm giả một giao diện, hoặc là một thành phần virtual của lớp.</i>



<b>In This Chapter </b>



</div>

<!--links-->
<a href=' /><a href=' /><a href=' /> Nâng cao khả năng tài chính tại Tổng cty Chè Việt Nam
  • 97
  • 336
  • 0
  • ×