Lập trình động (Dynamic programming)
Có thể nói nét chủ đạo trong .NET 4.0 đó là lập trình động (dynamic programming). Các đối tượng (object) ngày nay trở nên động (dynamic) theo ý nghĩa là cấu trúc và hành vi của nó không bị bó buộc bởi kiểu dữ liệu tĩnh (static) như trước đó, đồng thời trình biên dịch không cần biết kiểu dữ liệu của đối tượng trong quá trình biên dịch chương trình. Điều này sẽ rất có ích khi chương trình cần phải tương tác với các đối tượng từ các ngôn ngữ lập trình động giống như Python hoặc Ruby, hay là làm việc với các đối tượng mà có sự thay đổi về mặt cấu trúc như HTML hoặc DOM. Qua đó chúng ta có thể sử dụng nhiều ngôn ngữ khác nhau trên cùng một ứng dụng. Các đoạn mã của C#, VB.NET, IronPython,Javascripts… có thể hòa trộn với nhau để tạo ra một chương trình hoàn chỉnh.
Với dynamic trong .NET 4.0, khi lập nhận được một đối tượng, chúng ta không cần phải quan tâm đối tượng đó đến từ COM, IronPython hay HTML DOM hoặc Reflection. Chúng ta có thể sẵn sàng áp dụng các phép toán cho nó và để cho môi trường thực thi quyết định phép toán nào sẽ áp dụng cho đối tượng nào.
Lấy ví dụ:
Gọi phương thức từ lớp C#
Calculator calc = GetCalculator(); int sum = calc.Add(10, 20);
Gọi thông qua Reflection:
object calc = GetCalculator(); Type calcType = calc.GetType(); object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod, null, new object[] { 10, 20 }); int sum = Convert.ToInt32(res);
Gọi thông qua DOM:
ScriptObject calc = GetCalculator(); object res = calc.Invoke("Add", 10, 20); intsum = Convert.ToInt32(res);
Với dynamic C# 4: Chúng ta không cần quan tâm đối tượng trả về từ phương phương thức GetCalculator() thuộc lớp C#, Reflection hay DOM. Trong khi chúng ta vẫn có thể tiến hành gọi phương thức Add của nó. Rõ ràng doạn mã này ngắn gọn và dễ hiểu hơn rất nhiều trước đó.
dynamic calc = GetCalculator(); int sum = calc.Add(10, 20);
System.Dynamic.DynamicObject
. NET 4.0 giới thiệu một lớp mới gọi là DynamicObject cho phép định nghĩa các hành vi có thể được thực thi trong quá trình chạy. Đây là một đặc trưng rất mới trong .NET 4.0. Chúng ta biết rằng các đối tượng trong .NET 3.5 và trước đó đều phải xác định cấu trúc và các hành vi trong quá trình biên dịch. Với DynamicObject cho phép các lập trình viên xây dựng các đối tượng một cách linh hoạt hơn. Việc lấy giữ liệu hay xác định hành vi cho đối tượng có thể được quyết định và thực thi trong quá runtime. Lập trình viên sẽ viết mã để điều khiển quá trình lấy và thao tác giữ liệu cho đối tượng. Điều này sẽ rất có ích khi chúng ta làm việc các đối tượng có sự thay đổi về mặt cấu trúc như XML hay ADO object.
Lấy ví dụ: Bài toán đọc một đối tượng XML
Trước hết chúng ta khai báo một lớp kế thừa từ DynamicObject
public class EasierXML : DynamicObject { private XDocument _xml = new XDocument();public EasierXML(string Xml) { this._xml = XDocument.Parse(Xml); } public overridebool TryGetMember(GetMemberBinder binder, out object result) { string nodeName = binder.Name; result = _xml.Element("test").Element(nodeName).Value; return true; } }
Chúng ta sử dụng đối tượng này để đọc cấu trúc XML như sau:
dynamic easierXML = new EasierXML(@"<test><node1>Alpha</node1><node2>Beta</node2></test>"); Console.WriteLine(easierXML.node1); Console.WriteLine(easierXML.node2);
Trong ví dụ này, phương thức TryGetMember() được gọi đến cho node1, node2, do đó cho phép
truy vấn tài liệu XML và trả lại các nút riêng lẻ. Trong trường hợp cấu trúc XML có sự thay đổi, có một số node nào đó được thêm, chúng ta chỉ việc gọi đến easierXML.node3, easierXML.node4,.. mà không cần phải thay đổi đến lớp EasierXML
Một ví dụ khác, sử dụng DynamicObject còn giúp chương trình giảm thiểu được các lỗi phát sinh trong quá trình ép kiểu giữ liệu khi chúng ta làm việc với các đối tượng ADO.
Theo cách lập trình thông thường: chúng ta phải tiến hành ép kiểu mỗi khi đọc giá trị từ database
com.CommandText = "SELECT * FROM Employees"; using (IDataReader reader = com.ExecuteReader()) { while (reader.Read()) { int id = Int32.Parse(reader["EmployeeID"].ToString()); string firstName = reader["FirstName"].ToString(); DateTime dob = DateTime.Parse(reader["BirthDate"].ToString()); } }
Sử dụng dynamic: kiểu dữ liệu được tự động xác định trong quá trình thực thi
com.CommandText = "SELECT * FROM Employees"; using (IDataReader reader = com.ExecuteReader()) { dynamic dr = new DynamicReader(reader); while(reader.Read()) { int id = dr.EmployeeID; string firstName = dr.FirstName; DateTime dob = dr.BirthDate; } }
Ứng dụng dynamic cho WPF 4 trong quá trình gắn kết giữ liệu động (dynamic binding)
Trên đây là nét đặc trưng trong nền tảng ngôn ngữ .NET 4.0 đó là dynamic programming. Vậy chúng ta có thể ứng dụng nó vào các bài toán thực tế như thế nào? Lấy một ví dụ cho một ứng dụng desktop client sử dụng WPF. Như chúng ta biết rằng WPF tách biệt phần hiển thị của ứng dụng với các đoạn mã logic. Điều đó giúp cho các lập trình viên và các nhà thiết kế có thể làm việc độc lập mà vẫn không ảnh hưởng tới quá trình phát triển của ứng dụng. Các ứng dụng ngày nay thường được phát triển thông qua kiến trúc nhiều tầng. Một kiến trúc căn bản bao gồm tầng thể hiện, tầng logic nghiệp vụ và tầng thao tác dữ liệu. Thông thường, tầng thể hiện sẽ phát sinh nhiều yêu cầu thay đổi nhất từ phía khách hàng, trong khi tầng thao tác giữ liệu phải đảm bảo tính ổn định về mặt kiến trúc. Dó đó một kiến trúc tốt cần đảm bảo những yêu cầu thay đổi từ phía khach hàng không làm ảnh hưởng quá nhiều đến thiết kệ hiện có của ứng dụng, đặc biệt là các thiết kế liên quan đến thao tác giữ liệu.
Xét một ví dụ sau: Quản lý thông tin cá nhân
Giữ liệu được lưu trong 1 file XML có cấu trúc:
<?xml version="1.0" encoding="utf-8" ?> <People> <Person> <FirstName>Quang</FirstName> <LastName>Lê</LastName> </Person> <Person> <FirstName>Sơn</FirstName> <LastName>Hà</LastName> </Person>
Các tiếp cận trước đây (chưa có dynamic)
Thực thể (Business Entity)
Khai báo một lớp Person
public class Person { public string FirstName { get; set; } public string LastName { get; set; } }
Thao tác giữ liệu (Data access)
Lấy giữ liệu từ nguồn (trong ví dụ này sử dụng XML là nguồn lưu trữ giữ liệu, tương tự chúng ta có thể thay thế XML bằng CSDL có cấu trúc như SQL Server, Oracle)
public IEnumerable<Person> GetData() { XDocument oDoc = XDocument.Load("People.xml"); var myData = from info in oDoc.Descendants("Person") select new Person { FirstName = info.Element("FirstName").Value, LastName = info.Element("LastName").Value, }; return myData.ToList<Person>(); }
Hiển thị (Presentation)
Gắn kết giữ liệu và hiển thị lên controls:
<DataGrid Name="myDataGrid" VerticalAlignment="Top" ItemsSource="{Binding}"AutoGenerateColumns="False" > <DataGrid.Columns> <DataGridTextColumn x:Name="firstNameColumn" Binding="{Binding Path=FirstName}" Header="First Name"Width="SizeToHeader" /> <DataGridTextColumn x:Name="lastNameColumn" Binding="{Binding Path=LastName}" Header="Last Name" Width="SizeToHeader" /> </DataGrid.Columns> </DataGrid>
Điều gì sẽ xảy ra nếu khách hàng yêu cầu quản lý thêm thông tin về tuổi (Age) và giới tính (isMale). Rõ ràng với cách thiết kế trên chúng ta phải thay đổi tất cả các tầng trong kiến trúc của ứng dụng. Điều đó làm tăng chi phí cho việc nâng cấp ứng dụng, điều mà cả khách hang và nhà phát triển đều không mong muốn.
Vậy làm sao để giải quyết vấn đề trên?
Sử dụng dynamic binding (gắn kết động)
Thực thể (Business Entity)
Triển khai DynamicObject. Lớp này có thể sử dụng cho tất cả các nguồn giữ liệu là XML mà không phân biệt cấu trúc của nó. Nhiệm vụ của nó là lấy ra một cách chính xác giá trị trong các thẻ XML
class DynamicXML : DynamicObject { XElement _element; public ReadXML(XElement e) { _element = e; } public override bool TryGetMember(GetMemberBinder binder, outobject result) { //Triển khai phương thức lấy giá trị của đối tượng } }
Thao tác giữ liệu (Data access)
Lấy giữ liệu từ nguồn và trả về đối tượng dynamic
dynamic GetPersonData() { var root = XElement.Load("People.xml"); dynamic xml =new DynamicXML(root); return xml.Person; }
Hiển thị (Presentation)
Gắn kết giữ liệu và hiển thị lên controls
<DataGrid Name="myDataGrid" VerticalAlignment="Top" ItemsSource="{Binding}"AutoGenerateColumns="False" > <DataGrid.Columns> <DataGridTextColumn x:Name="firstNameColumn" Binding="{Binding Path=FirstName}" Header="First Name"Width="SizeToHeader" /> <DataGridTextColumn x:Name="lastNameColumn" Binding="{Binding Path=LastName}" Header="Last Name" Width="SizeToHeader" /> </DataGrid.Columns> </DataGrid>
Trong trường hợp khách hàng yêu cầu quản lý thêm thông tin về tuổi (Age) và giới tính (isMale). Điều duy nhất mà chúng ta phải làm đó là yêu cầu người thiết kế ứng dụng thêm đoạn mã sau vào phần hiển thị, trong khi không phải thay đổi hay biên dịch lại các tầng khác của ứng dụng. Qua đó đảm bảo tính vững chắc của kiến trúc chương trình cũng như giảm thiểu chi phí cho nâng cấp ứng dụng rất nhiều mỗi khi có yêu cầu thay đổi từ phía khách hàng.
<DataGridTextColumn x:Name="ageColumn" Binding="{Binding Path=Age}" Header="Age"Width="SizeToHeader" /> <DataGridTextColumn x:Name="isMaleColumn" Binding="{Binding Path=IsMale}" Header="Is Male" Width="SizeToHeader" />
Rõ ràng, với cách tiếp cận sử dụng dynamic, chúng ta thấy rằng số lượng mã cần thiết để viết và thay đổi cho một chức năng của chương trình ít hơn rất nhiều cách trước đó.