Quan điểm lập trình

Thế là bạn nghĩ rằng lập trình là một thế giới vô cùng thú vị, và bạn muốn tham gia vào thế giới ấy? Trước khi bạn bắt đầu, điều duy nhất mà tôi muốn khuyên là: nếu bạn thực sự yêu thích lập trình thì đó rõ ràng là công việc tốt nhất mà bạn có thể có được. Ngược lại, nếu bạn chỉ cảm thấy thích, hay không quan tâm lắm đến lập trình, thì đó rõ ràng là công việc tồi tệ nhất của bạn. Bởi vì bạn đang gia nhập vào một thế giới mà sự cạnh tranh luôn là nỗi ám ảnh không thể tránh khỏi. Phát triển phần mềm gần như là một cuộc đua tranh. Trong đó, cuộc sống của bạn là một con đường và bạn phải chạy càng nhanh càng tốt, không cần biết dưới chân có gì, cho đến khi gặp đồng bằng hoặc là đụng phải vách đá cheo leo. Nếu bạn sẩy chân, mọi thứ kết thúc, và đó hoàn toàn là lỗi của bạn. Nghe có vẻ hơi ghê gớm đúng không? Nhưng đừng để những điều đó làm bạn nản lòng. Tôi chỉ không muốn vẽ nên một viễn cảnh tươi đẹp, nơi có những cánh đồng xanh ngút ngàn và những đám mây lững lờ trôi trên nền trời xanh thẳm. Thực tế là có thể chỉ vài phút sau đó trời sẽ mưa và bạn thì chẳng mang theo dù. Thế nhưng, chính những điều không chắc chắn, những thách thức và áp lực sẽ làm cho cuộc sống trở nên đầy hứng thú. Bạn vẫn còn đọc đến đây ư? Rất tốt, thế có nghĩa là bạn hoàn toàn nghiêm túc về điều này. Bây giờ điều tôi sẽ nói với bạn là một bản phác thảo về những gì đang chờ đợi bạn trong thế giới lập trình, chúng ta sẽ nói một ít về kỹ thuật và cả những niềm vui của thế giới ấy.

pdf40 trang | Chia sẻ: tlsuongmuoi | Lượt xem: 2144 | Lượt tải: 0download
Bạn đang xem trước 20 trang tài liệu Quan điểm lập trình, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
ến đầu ra mà không cần các chi tiết phần cứng hoặc kiến trúc của hệ thống dưới thiết kế. 2. Thiết kế đường dữ liệu. Trong giai đoạn này người thiết kế chỉ rõ thanh ghi và các đơn vị logic. Những thành phần này được kết nối bằng các bus một hay hai chiều rồi điều khiển hoạt động dữ liệu giữa các thanh ghi và các đơn vị logic thông qua bus. Hình 2 : Kết quả của giai đoạn thiết kế dữ liệu 3. Thiết kế luận lý. Thiết kế luận lý là bước tiếp theo trong quá trình thiết kế và liên quan đến ứng dụng các cổng và các mạch lật cơ bản cho việc cài đặt các thanh ghi dữ liệu, các bus, các đơn vị logic và phần cứng điều khiển chúng. Kết quả của giai đoạn thiết kế này là một danh sách kết nối ( netlist ) của cổng và mạch lật. Công nghệ chế tạo các cổng và các chi tiết kỹ thuật của các mạch lật không có trong netlist này. Sau đó chuyển netlist thành sơ đồ hay danh sách transistor. Điều này liên quan đến sự thay thế cổng và màch lật bằng transistor hay các phần tử thư viện tương ứng nhưng phải xem xét chế độ tải và định thời. 4. Thiết kế vật lý. ·Tối ưu luận lý : Dùng để loại bỏ các biến dư thừa trong mạch. ·Ánh xạ công nghệ đã tối thiểu số khối logic, diện tích. ·Placement dùng để bố trí các khối để có tốc độ nhanh nhất. ·Routing kết nối các khối logic thành hệ thống số hoàn chỉnh. 5 . Chế tạo. Sử dụng danh sách các transistor và đặc tả kỹ thuật để đốt cháy cầu chì hay nạp dữ liệu vào SRAM 29 của thiết bị có thể lập trình hoặc tạo mặt nạ cho việc sản xuất mạch tích hợp. Nhiều nhà sản xuất đã nghiên cứu phát triển các sản phẩm lập trình được như: Field Programmable Gate Arrays ( FPGAs ), Complex Programmable Logic Devices ( CPLDs ), vi mạch Hard Wire, Serial PROMs. ·Vi mạch FPGAs gồm một ma trận các đơn vị logic: Những liên kết kim loại giữa các khối logic có thể được nối một cách tuỳ ý bằng các chuyển mạch có thể lập trình được để tạo thành một mạch như yêu cầu. FPGAs chứa một số lượng lớn các cổng logic, các thanh ghi, các mạch vào ra tốc độ cao. ·Vi mạch CPLDs chứa nhiều khối chức năng và khối vào ra, liên kết với nhau thông qua ma trận chuyển mạch. CPLDs là hệ thống tích hợp nhỏ từ 800 đến 10.000 cổng nhưng có tốc độ cao, thiết kế đơn giản. ·Vi mạch Hard Wire lập trình bằng mặt nạ của SRAM – dựa trên nền tảng của FPGAs. Các cổng của Hard Wire tương tự như FPGAs nhưng các phần tử logic được liên kết bằng kim loại cố định nên kích thước nhỏ và giá thành thấp. ·Serial PROMs là vi mạch nhớ có thể lập trình một lần được sử dụng để nạp dữ liệu cho SRAM FPGAs. II.CÁC NGÔN NGỮ MÔ TẢ PHẦN CỨNG. Hiện nay có rất nhiều ngôn ngữ mô tả phần cứng được phát triển cho mục đích mô phỏng, thiết kế, kiểm tra : 1. AHPL là một HDL mô tả dòng dữ liệu. Ngôn ngữ này sử dụng tín hiệu đồng hồ để đồng bộ các phép gán dữ liệu cho các thanh ghi và các mạch lật nhưng không hỗ trợ các mạch không đồng bộ. Kiểu dữ liệu trong AHPL bị cố định và hạn chế ở các kiểu bit, vector bit. Các thủ tục hay hàm chỉ thực hiện trong các đơn vị luận lý tổ hợp. 2. CDL (Computer Design Language) là ngôn ngữ mô tả dòng dữ liệu phát triển trong trường học, không hỗ trợ phân cách thiết kế. 3. CONLAN (Consensus Language) cho phép mô tả phân cách nhưng bị giới hạn sử dụng tham khảo bên ngoài. 4. IDL (Interactive Design Language) là ngôn ngữ sử dụng trong hãng IBM được thiết kế để tự động tạo ra các cấu trúc PLA. Nhưng nó không bao trùm mô tả mạch tổng quát. 5. ISPS (Instruction Set Proccessor à ngôn ngữ mô tả hành vi cấp cao được thiết kế để tạo ra môi trường cho thiết kế phần mềm dựa trên phần cứng. Điều khiển định thời trong ISPS bị hạn chế. 6. TEGAS (Test Generation And Simulation) là hệ thống để tạo tín hiệu kiểm tra và mô phỏng mạch số. 7. TI – HDL (Texas Instrument Hardware Description Language) là ngôn ngữ đa cấp cho thiết kế và mô phỏng phần cứng. Ngôn ngữ này cố định kiểu dữ liệu và không cho phép các kiểu dữ liệu do người dùng định nghĩa. 8. VERILOG là ngôn ngữ hỗ trợ phân cấp thiết kế xuất hiện sau VHDL, dễ sử dụng, được tiêu chuẩn hoá quốc tế. 9. VHDL (Very High Speed Intergrated Circuits Hardware Description Language) là ngôn ngữ mô tả phần cứng được công nhận tiêu chuẩn IEEE năm 1987, có đầy đủ sức mạnh cho việc mô tả và thiết kế hệ thống số ngày nay. VHDL hỗ trợ mô tả phân cấp từ hệ thống xuống tận cổng logic hay các kiểu mạch. VHDL hỗ trợ mạch đặc điểm về định thời. VHDL cung cấp các cấu trúc hết sức tổng quát. Vui cười. Người Mỹ và người Nhật quyết định tranh tài bằng một cuộc đua thuyền. Cả hai đội tập luyện rất vất vả để đạt đến đỉnh cao phong độ. Và trong ngày tranh tài, người Nhật đã về trước người Mỹ đúng 1 dặm. Đội mỹ vô cùng thất vọng trước thất bại này, khí thế không còn. Trưởng đoàn quyết định tìm hiểu lý do của thất bại thảm hại này. Sau khi xem lại băng hình, ông ta phát hiện đội Nhật có 8 người chèo và 1 người hướng dẫn. Trong khi đó, đội Mỹ chỉ có 1 người chèo và 8 người hướng dẫn. Một năm sau, sau khi đã bỏ ra hàng triệu đô la để phân tích và rút kinh nghiệm, đội Mỹ đã được tổ chức lại như sau: 4 người quản lý hướng dẫn, 3 người quản lý hướng dẫn khu vực và một hệ thống phân tích phong độ cho người chèo thuyền còn lại. Lần này, người Nhật thắng 2 dặm. Bị bẽ mặt, trưởng đoàn Mỹ đã sa thải tay chèo vì làm việc quá kém cỏi và khen thưởng các tay quản lý còn lại vì đã có công phát hiện ra vấn đề. 30 Sử dụng từ khóa static trong C++ Việt Thanh Cách dùng của từ khóa static bên trong một hàm là đơn giản nhất. Nó có nghĩa là một khi biến đã được khởi tạo, nó sẽ tồn tại trong bộ nhớ cho đến cuối chương trình. Bạn có thể tưởng tượng là biến đó đã bị “đóng” cứng lại. Và duy trì giá trị của nó cho đến khi chương trình kết thúc. Ví dụ, bạn có thể dùng một biến static để ghi lại số lần hàm được gọi bằng cách thêm vào hàm một dòng static int count = 0; count++. Bởi vì biến count là một biến static, dòng lệnh static int count = 0 sẽ chỉ thực hiện một lần duy nhất. Những lần tiếp theo hàm được gọi, count sẽ tiếp tục tăng. Và bạn có thể kiểm tra xem hàm đã được thực thi bao nhiêu lần bất cứ lúc nào trong chương trình bằng cách kiểm tra giá trị của nó. Bạn cũng có thể dùng static theo cách tương tự để ngăn chặn việc một biến bị khởi tạo lại bên trong một vòng lặp. Ví dụ, trong đoạn mã sau, nếu không có dòng static thì giá trị của biến number_ of_time sẽ chỉ là 1 sau khi thực hiện xong vòng lặp. Tuy nhiên, do chúng ta đã khai báo là static, biến number_of_time chỉ được khởi tạo một lần ở lần lặp đầu tiên và tiếp tục tăng cho đến khi kết thúc các vòng lặp. Và bạn có thể tính được là nó có giá trị là 100. for(int x=0; x<10; x++) { for(int y=0; y<10; y++) { static int number_of_times = 0; number_of_times++; count<<number_of_times; } } Bạn có thể dùng một biến static để chứa thông tin về giá trị cuối cùng mà một hàm trả về, chẳng hạn như bạn muốn chứa giá trị lớn nhất mà một hàm tính được. Nếu bạn đang hiện thực một hàm phân tích chuỗi, bạn cũng có thể chứa token cuối cùng được trả về bởi hàm trong một biến static. Như vậy bạn có thể lấy giá trị đó đơn giản bằng cách gọi hàm kèm theo một tham số xác định là hàm phải trả về token cuối cùng. Cách dùng thứ hai của static là bên trong một định nghĩa class. Hầu hết các biến khai báo bên trong một class xuất hiện trên theo từng đối tượng được tạo ra. Nghĩa là đối với những đối tượng khác nhau thuộc cùng một lớp thì những biến tương ứng hoàn toàn có thể mang những giá trị khác nhau. Trong khi đó, một biến thành viên kiểu static sẽ mang cùng một giá trị trong bất kỳ thể hiện nào của lớp, ngay cả khi chẳng có thể hiện nào được tạo ra. Ví dụ, nếu bạn muốn đếm số đối tượng của một lớp, bạn có thể dùng một biến thành viên kiểu static để theo dõi. Chú ý, bạn nên tham chiếu đến các thành phần static của class thông qua tên class thay vì tên của một đối tượng thuộc lớp đó. Làm như thế sẽ giúp bạn luôn nhớ rằng biến static đó không thuộc về một đối tượng riêng lẻ nào của class. Và bạn cũng không cần phải tạo ra một đối tượng của lớp đó để có thể dùng các biến thành viên kiểu static. Bạn cũng có thể tạo một hàm thành viên kiểu static cho class. Hàm thành viên kiểu static là những hàm không đòi hỏi phải có một thể hiện nào của class để tồn tại. Và bạn có thể truy xuất hàm thành viên kiểu static giống như truy xuất các biến thành viên kiểu static. Nên nhớ rằng hàm static chỉ có thể thao tác trên những biến static của class. Hàm thành viên kiểu static thường được dùng để xử lý các biến static, chẳng hạn như tăng trị số biến static khi bạn dùng biến đó làm số định danh duy nhất cho một thể hiện của class. Ví dụ, bạn có thể dùng mã lệnh như sau: class user { private: int id; static int next _id = 0; public: static int next_user_id() { next_id++; return next_id; } /*****************/ user() { id = user.next_id; //hoặc, id = user.next_user_id(); } }; Dòng mã: user a_user; sẽ thiết lập id thành một số id tiếp theo chưa được gán cho bất cứ user nào trong tiến trình này. Chú ý rằng chúng ta nên khai báo id là const. Cuối cùng, chúng ta có thể dùng static để khai báo những biến toàn cục bên trong một tập tin mã lệnh. Trong trường hợp này, việc dùng static xác định rằng mã nguồn của những tập tin khác trong project sẽ không thể truy xuất biến static này. Chỉ có mã lệnh bên trong tập tin chứa khai báo biến static mới có thể truy xuất đến nó. Hay nói cách khác, tầm vực của biến static chỉ giới hạn trong tập tin chứa nó. 31 Mô tả vấn đề Can thiệp tham số SQL hay SQL INJECTION là một kỹ thuật thay thế các tham số SQL trên địa chỉ URL của trang web bằng các tham số có chủ ý của người tấn công. Xem xét địa chỉ của các trang web động lập trình bằng PHP, ta có thấy các bộ phận sau: Giao thức vd: HTPP:// Địa chỉ máy chủ vd: www.yoursite.com Tê ứa mã kịch bản vd: index.php Tham số vd: id Giá trị truyền vào tham số vd: 12345 php?id=12345 SQL Injection tận dụng những chỗ hổng của cơ chế xử lý các giá trị này. Ví dụ, một script có thể chỉ sử dụng các giá trị số. Nếu truyền vào đó một chữ cái thì script đó sẽ từ chối thực hiện và kèm theo sau đó là một hay một loạt các thông báo lỗi xuất hiện, để lộ ra các thông tin nhạy cảm. Ví dụ, bạn có thể kiểm tra script có cơ chế truyền giá trị tham số kiểu như trên bằng cách gửi đi một URL với giá trị không hợp lệ được truyền cho tham số php?thisid=’ Điều này sẽ tạo ra một lỗi SQL. Nó có thể bộc lộ ra các chi tiết như là tên bảng hay tên trường trong cơ sở dữ liệu. Vậy là người muốn xâm nhập đã có thông tin để thao túng cơ sở dữ liệu của bạn. Khống chế SQL INJECTION Phạm Công Định (Theo Jason Lewis) Với các thông tin đó, người này có thể tạo ra một URL có dạng như sau: php?id=UNION SELECT user- name, password FROM USERS Câu lệnh SQL được can thiệp vào tham số sẽ hợp nhất hai lệnh SELECT tác động lên hai trường user-name và password nằm trong bảng USERS để in lên cùng một trang. Ngay cả khi bạn đã dùng MD5 để mã hóa mật khẩu thì người tấn công có kinh nghiệm vẫn có thể dùng các chương trình thăm dò kiểu từ điển hay brute force để dò ra hàm băm MD5 và phá nó. Cách thức khắc phục Chúng ta biết rằng người tấn công thường dùng các câu lệnh SQL hoặc các câu lệnh hệ thống khác để can thiệp vào phần giá trị các tham số cho nên cách hiện thực nhất là phải kiểm tra tất cả các giá trị truyền vào các tham số này trước khi đưa vào xử lý. Chúng ta gọi đó là dùng bộ lọc. Quy tắc là dùng bộ lọc này lọc ra tất cả các giá trị được truyền vào có chứa các chuỗi đặc biệt. Sau đây là mã minh họa: <?php function anti_injection($data) { //Tạo ra một mảng các giá trị đặc biệt cần lọc bỏ. $banned = array(“insert”, “select”, “update”, “delete”, “distinct”, “having”, “truncate”, “replace”, “handler”, “like”, “as”, “or”, “procedure”, “limit”, “order by”, “group by”, “asc”, “desc”, “union”); if (eregi(“[a-zA-Z0-9]+”, $data)) { $data = trim(str_replace($banned, ‘’, strtolower($data))); } else { $data = NULL; } // Tạo ra mảng mà để chúng // ta đẩy dữ liệu vào đó // để kiểm tra xem có kí tự nào // đặc biệt thuộc loại cần kiểm // duyệt không. Nếu có thì mảng // sẽ biến thành NULL và khiến // cho script không hoạt động. // Nếu mọi chuyện bình thường // thì nó trả lại $data $newData = array $data); if (in_array(NULL, $newData)){ die (‘Invalid Commands in Data’); } else { return } } // Đoạn mã sau kiểm tra mức độ // hoàn thiện của bộ lọc với chuỗi // can thiệp cụ thể và kết quả trả lại // sau khi đã lọc. $test = ‘The quick insert brown select fox jumped union over the lazy dog’; $somevar = anti_injection ($test); echo ‘Chuỗi ban đầu ’; echo $test; echo ‘Chuỗi đã lọc bỏ’; echo $somevar; ?> 32 Ví dụ 1: int main() { string a(“Hello”); string b(); string c = string (“World”); // … return 0; } Lỗi: string b(); Biểu thức này không tạo một đối tượng b có kiểu là string. Thay vào đó, nó lại là một khai báo mẫu (prototype) cho một hàm b không có thông số và có kiểu trả về là string. Khắc phục: Phải nhớ bỏ đi dấu ( ) khi sử dụng một constructor mặc định. Việc định nghĩa khai báo một hàm cục bộ là không có giá trị trong C vì nó không thể hiện tầm vực thực sự của hàm. Hầu hết các lập trình viên đều định nghĩa các khai báo mẫu này trong cá ưng dù vậy thì một tính năng không có giá trị mà bạn không bao giờ sử dụng có thể sẽ vẫn ám ảnh bạn. Ví dụ 2: template class Array { public: Array(int size); T& operator [ ] (int); Array& operator=(const Array&); // … }; int main() { Array a(10); a[0] = 0; a[1] = 1 ; a[2] = 4; a[3] = 9; a[4] = 16; a[5] = 25; a = 36; a[7] = 49; a[8] = 64; a[9] = 81; // … return 0; } Lỗi : a = 36; Điều ngạc nhiên là chương trình sẽ biên dịch câu lệnh trên thành: a = array (36); a được thay thế bằng một mảng mới gồm 36 phần tử. Khắc phục: Các constructor với một đối số sẽ có hai chức năng khi chuyển đổi kiểu. Tránh việc dùng constructor với đối số là một số nguyên. Hãy sử dụng từ khóa explicit nếu bạn không thể tránh được. Ví dụ 3: template class Array { public: explicit Array(int size); // ... private: T* _data; int _size; }; template Array::Array(int size) : _size(size), _data(new T(size)) {} int main() { Array a(10); a[1] = 64;// chương trình bị treo // ... } Lỗi: Template Array::Array(int size) _size(size), _data(new T(size)) // nên là new T[size]{} Tại sao chương trình lại biên dịch? new T(size) trả về một con trỏ T* tới một phần tử của kiểu T, có giá trị là số nguyên size. Trong khi new T[size] trả về một con trỏ T* tới một dãy có size đối tượng kiểu T, được xây dựng với một constructor mặc định. Khắc phục: Thận trọng khi làm việc với mảng và con trỏ. Ví dụ 4: template class Array { public: explicit Array(int size); Một số lỗi thường gặp khi sử dụng Constructor trong C++ Nguyễn Đức Thịnh 33 // ... private: T* _data; int _capacity; int _size; }; template Array::Array(int size): _size(size), _capacity(_size + 10), _data(new T[_capacity]) {} int main() { Array a(100); . . . // chương trình bắt đầu thực // hiện từng phần } Lỗi: Array::Array(int size) : _size(size), _capacity(size + 10), _data(new T[_capacity]) {} Khắc phục: Phải khởi tạo các biến thành viên theo đúng thứ tự đã khai báo! Array::Array(int size) : _data(new T[_capacity]) _capacity(_size + 10), _size(size), Thủ thuật: Không sử dụng các biến thành viên trong quá trình khởi tạo. Array::Array(int size) : _data(new T[size + 10]) _capacity(size + 10), _size(size), Ví dụ 5: class Point { public: Point(double x = 0, double y = 0); // ... private: double _x, _y; }; int main() { double a, r, x, y; // ... Point p = (x + r * cos(a), y + r * sin(a)); // ... return 0; } Lỗi: Point p = (x + r * cos(a), y + r * sin(a)); Câu lệnh trên có thể là: Point p(x + r * cos(a), y + r * sin(a)); Hay: Point p = Point(x + r * cos(a), y + r * sin(a)); Biểu thức: (x + r * cos(a), y + r * sin(a)) có một ý nghĩa khác. Dấu phẩy trong biểu thức dùng để bỏ qua giá trị x + r * cos(a) và chỉ tính giá trị y + r * sin(a). Constructor: point (double x = 0, double y = 0) khởi tạo một Point(y + r * sin(a) , 0). Khắc phục: Đối số mặc định có thể dẫn đến một lời gọi hàm không mong muốn. Trong trường hợp của chúng ta, cấu trúc Point (double) không hợp lý, nhưng Point ( ) thì ngược lại. Chỉ dùng giá trị mặc định cho đối số khi tất cả các mẫu gọi hàm đều cho kết quả có nghĩa. Ví dụ 6: class Shape { public: Shape(); private: virtual void reset(); Color _color; }; class Point : public Shape { public: // ... private: double _x, _y; }; void Shape::reset() { _color = BLACK; } void Point::reset() { Shape::reset(); _x = 0; _y = 0; } Shape::Shape() { reset(); } Không có constructor Point trong ví dụ này, ta sử dụng hàm ảo trong constructor Shape. Lỗi: Shape::Shape() { reset(); } Point p; Khi xây dựng Point, hàm Shape::reset () chứ không phải hàm ảo Point::reset () được gọi. Tại sao? Giải thích: hàm ảo không hoạt động trong constructor. Đối tượng con Shape được xây dựng trước đối tượng Point. Bên trong constructor Shape, việc xây dựng đối tượng một cách riêng rẽ cũng tạo ra một đối tượng thuộc lớp Shape. 34 Trong cuộc cạnh tranh khốc liệt với Netscape, Internet Explorer đã cố gắng thu hút người dùng bằng rất nhiều chức năng thú vị, tiện dụng. Một vấn đề khác cũng quan trọng không kém là các phần mềm của hãng thứ ba. Chính vì thế nên Microsoft cũng cung cấp rất nhiều cách để các lập trình viên có thể tiếp cận được với Internet Explorer. Một trong những “thứ thú vị” đó là tùy biến Context Menu của IE (Menu hiện ra khi bạn click chuột phải vào trang Web). Nếu như bạn đã dùng IE, hẳn bản phải biết mỗi khi chúng ta nhấp chuột phải vào một Picture thì sẽ có một Menu xuất hiện cho phép sử dụng Picture đó làm Wallpaper, làm sao chương trình biết được chính xác bạn đã chọn hình nào? Hay như các chương trình hỗ trợ việc Download (NetAnts ...), mỗi khi bạn nhấp chuột phải vào một link, thì sẽ xuất hiện một menu con của chương trình giúp bạn Download link đã chọn, làm sao chương trình đó nhúng menu của nó vào menu của IE và làm sao chương trình đó biết được bạn đã chọn Link nào? 1.Nhúng Menu của bạn vào Menu của Internet Explorer Để thêm một MenuItem vào Menu của IE rất đơn giản, bạn chỉ cần tạo một khóa (subkey) trong Registry ở khóa HKEY_CURRENT_USER \Software\ Microsoft\Internet Explorer\MenuExt\ . Nếu bạn không biết sử dụng Registry thì hãy tìm hiểu một chút về Registry rồi tiếp tục. Tên của khóa chính là Caption của Menu mà bạn muốn thêm vào. Bạn có thể sử dụng kí tự “&” để xác định kí tự sẽ được gạch chân. Kí tự đứng ngay sau “&” sẽ được gạch chân (phím tắt). Đặt giá trị Default của nó là đường dẫn của tập tin mà bạn muốn nó chạy mỗi khi người dùng click vào menu mà bạn mới tạo ra. Ví dụ bạn muốn tạo một Menu có tên là My Cool Menu, mỗi khi click vào Menu này thì sẽ chạy chương trình Notepad (Nói trước để bạn khỏi phải bực mình, nó sẽ không hoạt động như ta mong muốn), thì bạn sẽ tạo một khóa như trong hình minh họa. Và bạn hãy mở Tùy biến Menu ngữ cảnh của Internet Explorer Huỳnh Phúc Hưng (Nicky) IE ngay để xem kết quả. Thật tuyệt là Menu của bạn đã xuất hiện nhưng nếu bạn click vào đó thì sẽ chẳng có chương trình Notepad nào được gọi cả. Tại sao lại như vậy? Bạn cứ bình tĩnh đọc tiếp rồi sẽ rõ, hãy cứ tạm “ấm ức” như vậy cái đã. Bạn sẽ nhận thấy một điều là Menu của bạn sẽ luôn xuất hiện khi người dùng nhấp chuột phải, nhưng bạn lại muốn Menu của bạn chỉ xuất hiện khi người dùng nhấp chuột phải vào một link hay một Picture thì sao ?. Bạn chỉ cần tạo một Value (kiểu REG_DWORD) mới tên là Contexts và đặt giá trị cho nó là 22 như hình minh họa ở trên. Tại sao nó phải là 22 (ở hệ Hex). Đây là kết quả khi bạn sử dụng kĩ thuật bit mask, dùng phép toán OR để tổng hợp từ các giá trị sau đây. CONTEXT_MENU_DEFAULT : 0x1 CONTEXT_MENU_IMAGE : 0x2 CONTEXT_MENU_CONTROL : 0x4 CONTEXT_MENU_TABLE : 0x8 CONTEXT_MENU_TEXTSELECT : 0x10 CONTEXT_MENU_ANCHOR : 0x20 CONTEXT_MENU_UNKNOWN : 0x40 Ví dụ ở đây bạn cần hiển thị Menu khi click chuột phải vào Link và Picture, bạn sẽ phải chuyển 20 và 2 (ở hệ thập lục phân) sang hệ nhị phân, sau đó tổng hợp bằng phép toán OR và cuối cùng là chuyển giá trị nhị phân trở lại thập lục phân (Hex). Bạn hoàn toàn có thể kiểm tra lại bằng tay. Nếu bạn không rõ lắm về kĩ thuật này thì cũng đừng lo lắng. Bây giờ ta hãy xem vì sao khi click vào menu bạn vừa chọn lại không có chuyện gì xảy ra nhé. Đó là vì chúng ta đã làm không đúng với những gì mà IE mong đợi, giá trị default phải là đường dẫn mộ chứa đoạn mã xử lý của bạn. IE sẽ chạ ày ở hậu 35 trường và bạn sẽ không thấy nó chạy nhưng kết quả của nó thì có thể thấy được. Bây giờ bạn hãy tạo một ở thư mục C:\ (hay ở chỗ khác tùy bạn) có nội dung như sau: var parentwin = external.menuArguments; var doc = parentwin.document; alert(“My parent window is: “ + doc.title); Sau đó sửa giá trị default thành đường dẫn củ bạn vừa mới tạo (của tôi sẽ là C:\test.htm). Bây giờ hãy khởi động lại IE và chọn Menu bạn vừa mới tạo. Nếu bạn thấy có “một cái gì đó” hiện lên thì có nghĩa là bạn đang đi đúng hướng và có thể tiếp tục và cũng nên vui vẻ lên một chút được rồi đó. 2. Xử lý Menu Bạn đã có thể thêm menu của bạn vào Menu của IE nhưng đó chỉ mới nửa vấn đề, vấn đề còn lại là làm sao để biết người dùng đã chọn Link nào và làm sao để gọi chương trình của bạn. Muốn giải quyết được vấn đề này bạn cần biết một chút về VBScript. Chúng ta sẽ sử dụng đối tượng (Object) external, một cô nàng dễ thương mà IE đã dành cho các lập trình viên. Nếu muốn tìm hiểu chi tiết về Object này bạn có thể tìm thấy nó trong MSDN. Bạn sửa nộ ở trên thành: Sub AddLink(Url,Info) Dim oShell Set oShell = CreateObject(“WScript.Shell”) oShell.run “C:\test.exe “ + Url Set oShell = Nothing end sub Sub OnContextMenu() set srcEvent = external.menuArguments.event set EventElement = external.menuArguments. document.elementFromPoint ( srcEvent.clientX, rcEvent.clientY ) if srcEvent.type = “MenuExtAnchor” then set srcAnchor = EventElement do until TypeName(srcAnchor)=”HTMLAnchorE lement” set srcAnchor=srcAnchor.parentElement Loop Call AddLink(srcAnchor.href,srcAnchor. innerText) elseif srcEvent.type=”MenuExtImage” then if TypeName(EventElement)=”HTMLAreaEleme nt” then Call AddLink(EventElement.href,EventElement. Alt) else set srcImage = EventElement set srcAnchor = srcImage.parentElement do until TypeName(srcAnchor)=”HTMLAnchorE lement” set srcAnchor=srcAnchor.parentElement if TypeName(srcAnchor)=”Nothing” then call AddLink(srcImage.href,srcImage.Alt) exit sub end if Loop Call AddLink(srcAnchor.href,srcImage.Alt) end if elseif srcEvent.type=”MenuExtUnknown” then set srcAnchor = EventElement do until TypeName(srcAnchor)=”HTMLAnchorE lement” set srcAnchor=srcAnchor.parentElement if TypeName(srcAnchor)=”Nothing” then Call AddLink(EventElement.href,EventElement. innerText) exit sub end if Loop Call AddLink(srcAnchor.href,srcAnchor. innerText) elseif 1=1 then MsgBox(“Unknown Event Source “”” + srcEvent. type + “””” + vbCrLf) end if end sub call OnContextMenu() Cả đoạn Code trên chỉ làm mỗi một việc là trả về URL của đối tượng bạn đã chọn. Bạn không cần phải bực bội nếu không hiểu hết nó. Vào một ngày đẹp trời nào đó bạn đang tắm và bỗng thốt lên “à ra thế”, ngoài đường đông người lắm, đừng có như thế mà chạy ra đường há. Bạn chỉ cần chú ý đến hàm AddLink() ở đầ ôi xin ghi lại để các bạn dễ nhìn. Sub AddLink(Url,Info) Dim oShell Set oShell = CreateObject(“WScript.Shell”) oShell.run “C:\test.exe “ + Url Set oShell = Nothing End sub Hàm này sẽ gọ ới tham số là đường dẫn của link (tham số URL) người dùng vừa chọn. Bạn cũng thắc mắc là còn cái tham số Info thì để làm cái gì. Cái đó sẽ cho bạn biết thông tin về cái Link. Bạn cứ hiểu như vậy còn cụ thể thì bạn sẽ tự khám phá ra khi ngồi “vọc” nó. Bạn chỉ cần thay đường dẫn đến chương trình của bạn là xong. Công việc còn lại cuối cùng là chương trình của bạn. Bạn phải viết một đoạn mã để lấy kết quả mà bạn đã dày ...xem tiếp trang 40 36 Sự khác nhau giữa hàm thành viên, hàm không thành viên và hàm friend Việt Thanh Sự khác nhau lớn nhất giữa hàm thành viên và hàm không thành viên là các hàm thành viên có thể là hàm ảo trong khi hàm không thành viên thì không. Như là một hệ quả, nếu bạn có một hàm ảo thì hàm ảo đó phải là thành viên của một lớp nào đó. Xét một lớp thể hiện các số hữu tỉ: class Rational { public: Rational(int numerator = 0, int denominator = 1); int numerator() const; int denominator() const; private: ... }; Như đã thấy, hiện giờ lớp này là vô dụng. Bạn cần phải thêm các phép toán như: cộng, trừ, nhân, chia, … nhưng bạn chưa biết chắc phải hiện thực chúng dưới dạng hàm thành viên, hàm không thành viên hay hàm friend. Khi ngờ vực, hãy dùng hướng đối tượng. Bạn biết rằng phép nhân các số hữu tỉ sẽ liên quan đến lớp Rational, do đó hãy thêm thao tác này vào lớp như là một hàm thành viên: class Rational { public: ... const Rational operator*(const Rational& rhs) const; }; Bây giờ bạn có thể nhân 2 số hữu tỉ một cách dễ dàng: Rational oneEighth(1, 8); Rational oneHalf(1, 2); Rational result = oneHalf * oneEighth; result = result * oneEighth; Nhưng bạn vẫn chưa thỏa mãn. Bạn cũng muốn hỗ trợ các thao tác hòa trộn giữa các chế độ (mixed-mode operations). Ví dụ như nhân một đối tượng Rational với một số kiểu int. Khi bạn cố gắng làm điều này bạn sẽ thấy rằng nó chỉ thực hiện được một nửa: result = oneHalf * 2; // tốt result = 2 * oneHalf; // lỗi! Đây là một điềm gở. Phép nhân có tính giao hoán. Nguyên nhân gây ra lỗi sẽ trở nên rõ ràng hơn nếu bạn viết 2 câu lệnh trên ở dạng khác tương đương: result = oneHalf.operator*(2); // tốt result = 2.operator*(oneHalf); // lỗi! Đối tượng oneHaft là thuộc lớp Rational có chứa hàm operator*, nên trình biên dịch sẽ gọi hàm này. Tuy nhiên, số nguyên 2 không có một lớp tương đương nên sẽ không có hàm operation*. Trình biên dịch sẽ tìm một hàm operator* không thành viên khác, ví dụ: result = operator*(2, oneHalf); // lỗi! Nhưng dĩ nhiên là không có hàm không thành viên operator* nào nhận đối số là một số nguyên int và một đối tượng Rational nên việc tìm kiếm thất bại. Xem lại lời gọi thành công, bạn sẽ thấy tham số thứ hai là một số nguyên 2, trong khi hàm Rational:: operator* chỉ nhận các đối tượng Rational làm tham số. Điều gì đang xảy ra? Tại sao số 2 lại làm việc ở vị trí này còn vị trí kia thì không? Điều đang xảy ra chính xác là sự chuyển kiểu. Trình biên dịch biết rằng bạn đang truyền một số nguyên int và hàm thì lại đòi hỏi tham số là một đối tượng Rational. Nhưng nó cũng biết rằng nó có thể tạo ra một đối tượng Rational tương ứng bằng cách gọi hàm khởi dựng của Rational với đối số là số nguyên int bạn cung cấp nên nó đã làm như vậy. Nói cách khác nó xem lời gọi của bạn tương tự như sau: const Rational temp(2); // tạo một đối tượng Rational tạm từ số 2 result = oneHalf * temp; // hay oneHalf. operator*(temp); 37 Dĩ nhiên trình biên dịch chỉ làm điều này khi có các hàm khởi dựng nonexplicit (không tường minh) bởi vì các hàm khởi dựng explicit (tường minh) không thể được dùng để chuyển kiểu ngầm. Nếu lớp Rational được định nghĩa như sau: class Rational { public: explicit Rational(int numerator = 0, // hàm khởi dựng bây giờ là explicit int denominator = 1); ... const Rational operator*(const Rational& rhs) const; ... }; Thì cả 2 dòng lệnh sau đều bị lỗi: result = oneHalf * 2; // lỗi! result = 2 * oneHalf; // lỗi! Việc hỗ trợ khả năng nhân giữa các tập số khác nhau là rất khó khăn khi thực hiện theo các này. Nhưng ít nhất thì cách hành xử của cả hai phát biểu là đồng nhất. Tuy nhiên, lớp Rational chúng ta đang xem xét được thiết kế để cho phép chuyển kiểu ngầm từ các kiểu dựng sẵn sang Rational. Đó là lý do vì sao hàm khởi dựng của Rational không được khai báo là explicit. Trình biên dịch của bạn sẽ thực hiện việc chuyển kiểu ngầm trên mọi tham số của mọi hàm gọi. Nhưng chúng chỉ thực hiện điều đó đối với các tham số được liệt kê trong danh sách tham số chứ không phải cho đối tượng chứa hàm thành viên được gọi, nghĩa là đối tượng tương ứng với con trỏ *this bên trong một hàm thành viên. Đó là lý do vì sao lời gọi sau chạy được: result = oneHalf.operator*(2); // chuyển đổi int -> Rational trong khi lời gọi sau thì không: result = 2.operator*(oneHalf); // không chuyển đổi int -> Rational Trường hợp đầu tiên gọi một tham số được liệt kê trong khai báo hàm nhưng trường hợp thứ hai thì không. Tuy nhiên, bạn vẫn muốn hỗ trợ khả năng thực hiện phép toán giữa các kiểu khác nhau. Và như vậy cách để thực hiện điều này đã trở nên rõ ràng: làm cho hàm operator* trở thành không thành viên. Điều đó cho phép trình biên dịch thực hiện việc chuyển kiểu ngầm trên tất cả các đối số: class Rational { ... // không chứa hàm operator* }; const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); } Rational oneFourth(1, 4); Rational result; result = oneFourth * 2; // tốt! result = 2 * oneFourth; // tốt! Điều này dĩ nhiên là một kết cục có hậu. Nhưng chúng ta vẫn còn một khúc mắc. Hàm operator* có nên là hàm friend của lớp Rational không? Trong trường hợp này, câu trả lời là không. Bởi vì hàm operator* có thể được hiện thực hoàn toàn dưới dạng giao tiếp công cộng của một lớp. Đoạn mã bên trên chỉ ra một cách để làm điều đó. Bạn nên tránh dùng hàm friend bất cứ khi nào có thể. Tuy nhiên, việc một hàm không phải là thành viên, dù về mặc khái niệm nó vẫn là một phần của giao tiếp của lớp, cần phải truy xuất đến các thành phần không phải là công cộng của một lớp cũng không phải là điều bất bình thường. Ví dụ, trong trường hợp của lớp String, nếu bạn cố overload các hàm operator>> và operator<< để đọc và ghi các đối tượng String thì bạn sễ nhanh chóng phát hiện ra rằng chúng không nên là hàm thành viên. Nếu không bạn phải đặt đối tượng String bên trái khi bạn gọi hàm: // một lớp khai báo không đúng hàm operator>> // và hàm operator<< - vì cho chúng là hàm thành viên class String { public: String(const char *value); ... istream& operator>>(istream& input); ostream& operator<<(ostream& output); private: char *data; }; String s; s >> cin; // hợp lệ nhưng trái với quy ước thông thường s << cout; // như trên Điều đó sẽ làm mọi người bối rối. Như là một hệ quả, những hàm này không nên là hàm thành viên. Chú ý rằng đây là một trường hợp khác với trường hợp chúng ta vừa thảo luận bên trên. Ở đây, mục tiêu là làm phù hợp với cú pháp tự nhiên của hàm trong khi 38 trường hợp trên lại liên quan đến vấn đề chuyển kiểu ngầm. Nếu bạn đang thiết kế những hàm như vậy, bạn sẽ đối diện với trường hợp sau: istream& operator>>(istream& input, String& string) { delete [] string.data; đọc từ luồng nhập vào bộ nhớ và tạo string.data trỏ đến nó return input; } ostream& operator<<(ostream& output, const String& string) { return output << string.data; } Chú ý rằng cả hai hàm đều cần truy xuất đến trường data của lớp String, một trường private. Tuy nhiên, bạn cũng đã biết rằng phải làm cho những hàm này là không thành viên. Vậy là bạn đã bị dồn vào chân tường và bạn không còn lựa chọn nào khác: một hàm không thành viên với khả năng truy xuất đến các thành phần không công cộng phải là một hàm friend của lớp đó. Giả sử bạn có hàm f là một hàm bạn cần phải khai báo thế nào đó cho đúng và lớp C là lớp mà hàm đó có liên quan về mặt khái niệm thì những gì chúng ta vừa thảo luận có thể tóm tắt lại như sau: · Hàm ảo phải là hàm thành viên: Nếu f phải là hàm ảo, làm cho nó trở thành thành viên của lớp C. · Hàm operator>> và operator<< không bao giờ là hàm thành viên: nếu f là hàm operator>> hay operator>>, làm cho f là một hàm không thành viên. Nếu thêm vào đó, f phải truy xuất đến các thành viên không công cộng của C, làm cho f là một hàm friend của lớp C. · Chỉ có hàm không thành viên thực hiện chuyển kiểu trên đối số bên trái nhất của nó: nếu f phải chuyển kiểu trên đối số trái nhất, nó phải là hàm không thành viên. Nếu thêm vào đó, f phải truy xuất đến các thành viên không công cộng của C, f phải là một hàm friend của lớp C. · Những thứ khác nên là hàm thành viên: nếu không có trường hợp nào ở trên là đúng vào trường hợp của hàm f thì nó nên là thành viên của lớp C. Thời gian gần đây, nhất là sau khi học môn “Xây dựng phần mềm hướng đối tượng” các bạn sinh viên dấy lên phong trào dùng UserControl trong các projects môn học. “Nhà nhà dùng UserControl, người người dùng UserControl”, “UserControl mọi lúc, mọi nơi”. Ai cũng cố gắng đưa UserControl vào trong projects của mình. Với sự hỗ trợ của IDE, việc xây dựng một UserControl trên nền .NET khá dễ dàng. Nhưng để có được một UserControl “có chất lượng”, hay nói tổng quát hơn là một GUI Component “có chất lượng”, thì thật ra không dễ chút nào! Nó đòi hỏi chúng ta phải có kiến thức vững về lập trình hướng đối tượng và am tường phân tích thiết kế hướng đối tượng. Tất cả bắt đầu vào những năm 70 của thế kỷ 20, tại phòng thí nghiệm Xerox PARC ở Palo Alto. Sự ra đời của giao diện đồ họa (Graphical User Interface) và lập trình hướng đối tượng (Object Oriented Programming) cho phép lập trình viên làm việc với những thành phần đồ họa như những đối tượng đồ họa có thuộc tính và phương thức riêng của nó. Không dừng lại ở đó, những nhà nghiên cứu ở Xerox PARC còn đi xa hơn khi cho ra đời cái gọi là kiến trúc MVC (viết tắt của Model – View – Controller). Trong kiến trúc MVC, một đối tượng đồ họa (GUI Component) bao gồm 3 thành phần cơ bản: Model, View, và Controller. Model có trách nhiệm đối với toàn bộ dữ liệu cũng như trạng thái của đối tượng đồ họa. View chính là thể hiện trực quan của Model, hay nói cách khác chính là giao diện của đối tượng đồ họa. Và Controller điều khiển việc tương tác giữa đối tượng đồ họa với người sử dụng cũng như những đối tượng khác. Khi người sử dụng hoặc những đối tượng khác cần thay đổi trạng thái của đối tượng đồ họa, nó sẽ tương tác thông qua Controller của đối tượng đồ họa. Controller sẽ thực hiện việc thay đổi trên Model. Khi có bất kỳ sự thay đổi nào ở xảy ra ở Model, nó sẽ phát thông điệp (broadcast message) thông báo cho View và Controller biết. Nhận được thông điệp từ Model, View sẽ cập nhật lại thể hiện của mình, đảm bảo rằng nó luôn là thể hiện trực quan chính xác của Model. Còn Controller, khi nhận được thông điệp từ Model, sẽ có những tương tác cần thiết phản hồi lại người sử dụng hoặc các đối tượng khác. Lấy ví dụ một GUI Component đơn giản là Checkbox. Checkbox có thành phần Model để quản Kiến trúc 39 Model – View – Controller Minh Huy lý trạng thái của nó là check hay uncheck, thành phần View để thể hiện nó với trạng thái tương ứng lên màn hình, và thành phần Controller để xử lý những sự kiện khi có sự tương tác của người sử dụng hoặc các đối tượng khác lên Checkbox. Khi người sử dụng nhấn chuột vào Checkbox, thành phần Controller của Checkbox sẽ xử lý sự kiện này, yêu cầu thành phần Model thay đổi dữ liệu trạng thái. Sau khi thay đổi trạng thái, thành phần Model phát thông điệp đến thành phần View và Controller. Thành phần View của Checkbox nhận được thông điệp sẽ cập nhật lại thể hiện của Checkbox, phản ánh chính xác trạng thái Checkbox do Model lưu giữ. Thành phần Controller nhận được thông điệp do Model gởi tới sẽ có những tương tác phản hồi với người sử dụng nếu cần thiết. Kiến trúc MVC đã tách biệt (decoupling) sự phụ thuộc giữa các thành phần trong một đối tượng đồ họa, làm tăng tính linh độ à tính tái sử dụng (reusebility) của đối tượng đồ họa đó. Một đối tượng đồ họa bấy giờ có thể dễ dàng thay đổi giao diện bằng cách thay đổi thành phần View của nó trong khi cách thức lưu trữ (Model) cũng như xử lý (Controller) không hề thay đổi. Tương tự, ta có thể thay đổi cách thức lưu trữ (Model) hoặc xử lý (Controller) của đối tượng đồ họa mà những thành phần còn lại vẫn giữ nguyên. Kiến trúc MVC đã được ứng dụng để xây dựng rất nhiều framework và thư viện đồ họa khác nhau. Tiêu biểu là bộ thư viện đồ họa của ngôn ngữ lập trình hướng đối tượng SmallTalk (cũng do Xerox PARC nghiên cứu và phát triển vào thập niên 70 của thế kỷ 20). Các Swing Components của Java cũng được xây dựng dựa trên kiến trúc MVC. Ví dụ đi cùng với JButton là ButtonUI (thành phần View) và ButtonModel (thành phần Model). Ta hoàn toàn có thể viết MyButtonUI hoặc YourButtonUI để thay đổi giao diện của JButton theo ý mình (tương tự cho ButtonModel). Một điểm khá thú vị đối với Swing Components là nó cho phép ta chỉ thay đổi giao diện một phần nào đó của component. Ví dụ ta có thể thay đổi thể hiện của list item trong JList thông qua ListCellRenderer. Ngay cả Microsoft Visual C++ (VC++) cũng ứng dụng MVC để xây dựng Document View Architecture. Bạn nào đã từng tạo một project MDI trong VC++ đều thấy rằng VC++ sẽ tạo ra các lớp CXXXDoc và CXXXView (XXX là tên project của chúng ta). CXXXDoc chính là thành phần Model và CXXXView là thành phần View của chương trình. Như vậy nếu theo đúng kiến trúc MVC thì tất cả những xử lý liên quan đến lưu trữ dữ liệu của chương trình phải được đặt ở CXXXDoc, còn những xử lý liên quan đến việc thể hiện phải được đặt ở CXXXView. Khi có sự thay đổi dữ liệu ở CXXXDoc, cần cập nhật lại hiển thị ở CXXXView, CXXXDoc sẽ gọi hàm UpdateAllView của nó để phát thông điệp thông báo cho tất cả các View gắn kết với nó. Tại CXXXView ta bắt sự kiện OnUpdate để cập nhật lại hiển thị của View. Hồi đó mỗi lần làm chương trình VC++, tôi đặt tất cả xử lý ở CXXXView, xong rồi ở CXXXDoc hay CMainFrame cần gọi cái gì đó của CXXXView thì cứ việc khai báo một con trỏ pView trỏ đến CXXXView. hì hì, giờ nghĩ lại thấy “bưởi” quá. Vì như vậy vô tình ta đã làm cho CXXXDoc và CMainFrame phụ thuộc (coupling) vào CXXXView, khi muốn thay đổi View thì rất khó khăn. Khi cài đặt kiến trúc MVC ta cần lưu ý những điểm sau: - Thành phần Model không cần thiết phải biết đến các View và Controller cụ thể gắn kết với nó. Khi có thay đổi, Model chỉ việc phát thông điệp cho những ai đăng ký với nó. Điều này có thể được thực hiện thông qua Observer Pattern. - Nên áp dụng Facade Pattern để kết hợp Model, View, và Controller lại với nhau thành “3 trong 1” cho dễ quản lý và thao tác đối với người sử dụng. - Kiến trúc MVC không phải là kiến trúc 3 tầng (3-Tiers Architecture). Mặc dù giữa 2 kiến trúc này có nhiều điểm tương đồng nhưng chúng nói về 2 khía cạnh khác nhau. Tài liệu tham khảo: - SwingDoc. - Design Patterns của Gangs of Four. - Mã nguồn của gói javax.swing. - Developing Solutions with Visual C++ 6.0 của Aptech Education. - Và rất nhiều tài liệu khác. 40 Vào năm 1988, Bertrand Meyer, cha đẻ của ngôn ngữ lập trình hướng đối tượng Eiffel, trong quyển Object Oriented Sofware Construction của mình, đã phát biểu một câu mà bây giờ trở thành nguyên lý Open-Closed nổi tiếng, xin được trích nguyên văn: “Software entities (classes, modules, functions, etc) should be open for extension, but closed for Thoạt nghe thì câu này có vẽ mâu thuẫn. Cách thông thường để mở rộng một chương trình là sửa đổi nó. Như vậy việc thiết kế một chương trình “closed for Một chương trình bên cạnh tính độc lập tương đối của mình còn có quan hệ với rất nhiều chương trình khác. Việc sửa đổi nó có thể dẫn đến việc sửa đổi tất cả những chương trình liên quan. Và trong một hệ thống lớn thì việc này quả là một thảm họa. Nâng cấp một phần của hệ thống kéo theo phải nâng cấp toàn bộ hệ thống! Như vậy làm thế nào để xây dựng được một chương trình chạy ổn định, thích ứng được với nhưng thay đổi trong tương lai? Xem xét kỹ nguyên lý Open- Closed ta thấy rằng việc mở rộng chương trình chỉ nên là bổ sung thêm những cái mới chứ không nên sửa đổi những gì đã có. Xét chương trình sau: const int LINE = 0; const int RECTANGLE = 1; void DrawLine() { // draw line } void DrawRectangle() { // draw rectangle } void DrawShape(int iType) { switch (iType) { case LINE: DrawLine(); break; case RECTANGLE: DrawRectangle(); break; } } Đoạn chương trình giúp ta vẽ đường thẳng và hình chữ nhật. Nếu muốn thêm vào vẽ hình tròn vào, ta thêm phải thêm “case CIRCLE: DrawCircle(); break;”. Như vậy khi muốn vẽ thêm một hình mới, ta lại phải sửa đổi lại hàm DrawShape. Nhưng bạn hãy thử tưởng tượng chương trình của chúng ta có đến vài chục hoặc vài trăm hàm liên quan đến việc vẽ hình? Lúc đó ta phải thực hiện việc sửa đổi trên rất nhiều hàm của chương trình! Một chương trình được thiết kế như vậy không tuân thủ nguyên lý Open-Closed. Để thỏa nguyên lý Open-Closed, ta sửa lại như sau: class Shape { public: virtual void Draw() = 0; }; class Line { private: // line’s data public: virtual void Draw(); }; class Rectangle { private: // rectangle’s data public: virtual void Draw(); }; void DrawShape(Shape* obj) { obj->Draw(); } Trong đoạn chương trình trên, khi muốn vẽ thêm hình tròn, ta chỉ việc thêm class Circle mà không cần sửa đổi chương trình hiện có. Cốt lõi vấn đề ở đây là chính là lớp Shape. Nó là abstraction của các đối tượng hình. Có thể nói nguyên lý Open-Closed là nguyên lý cơ bản nhất của lập trình hướng đối tượng, bên cạnh 3 nguyên lý khác là: nguyên lý Thay thế Liskov, nguyên lý Nghịch đảo phụ thuộc, và nguyên lý Phân tách Interface. Nó là nền tảng của mọi phân tích thiết kế hướng đối tượng (Object Oriented Analysis and Design). Nó giúp chương trình dễ dàng tái sử dụng và mở rộng, nâng cao tính trong sáng và có một chút gì đó rất “lịch lãm”! Tài liệu tham khảo: bài viết The Open-Closed Principle của Robert C. Martin tại website objectmentor.com/resources/articles/ocp.pdf. Nguyên lý Open-Closed Minh Huy 41 Nhắc đến Yahoo Messeger (YM) thì có lẽ những người hay chat điều biết đến, đó có lẽ là một chương trình chat tuyệt nhất hiện nay. Các thủ thuật sử dụng trong YM rất phong phú, chẳng hạn: chat tiếng Việt trong YM, chèn hình ảnh vào cửa sổ chat, nhân cách hóa nick, tạo nick ảo… Tuy nhiên có lẽ những thủ thuật này đã không còn mới mẻ gì đối với các bạn, vì vậy hôm nay tôi xin giới thiệu một thủ thuật tương đối mới do tôi vô tình tìm được trong quá trình lập trình. Đó là thủ thuật giúp bạn có thể Change status của mình một cách tự động ngay cả khi bạn không ngồi trên máy. Trước tiên tôi sẽ nói sơ qua về cách làm này. Nếu chịu tìm tòi bạn sẽ để ý rằng các status của YM không lưu vào cơ sở dữ liệu (CSDL) của YM mà nó lưu vào Registry của máy theo đường dẫn sau: HKEY_CURRENT_USER\Software\Yahoo\ Ở đây nick thogia_sunrang là nick của tôi. Ngoài việc lưu các status ở đây, YM còn lưu tất cả nick ảo của một người vào registry nữa, phần này tôi sẽ để các bạn tự khám phá. Tự động thay đổi Status của Yahoo! Messenger La Minh Trường Lợi dụng đặc điểm này ta có thể thay đổi các status một các dễ dàng bằng cách ghi các status vào registry một cách tự động. Sau đây tôi sẽ trình bày đoạn code minh họa việc change status một cách tự động bằng ngôn ngữ VB Option Explicit ‘ Mot so khai bao can thiet Public Declare Function SendMessage Lib “user32” Alias “SendMessageA” _ (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long Public Declare Function FindWindow Lib “user32” Alias “FindWindowA” _ (ByVal lpClassName As String, ByVal lpWindowName As String) As Long Public Declare Function RegOpenKey Lib “advapi32.dll” Alias “RegOpenKeyA” _ (ByVal hKey As Long, ByVal lpSubKey As String, phkResult As Long) As Long Public Declare Function RegCloseKey Lib “advapi32.dll” _ (ByVal hKey As Long) As Long Public Declare Function RegCreateKey Lib “advapi32.dll” Alias “RegCreateKeyA” _ (ByVal hKey As Long, ByVal lpSubKey As String, phkResult As Long) As Long Public Declare Function RegDeleteKey Lib “advapi32.dll” Alias “RegDeleteKeyA” _ (ByVal hKey As Long, ByVal lpSubKey As String) As Long Public Declare Function RegDeleteValue Lib “advapi32.dll” Alias “RegDeleteValueA” _(ByVal hKey As Long, ByVal lpValueName As String) As Long Public Declare Function RegQueryValueEx Lib “advapi32.dll” Alias “RegQueryValueExA” _(ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Any, lpcbData As Long) As Long Public Declare Function RegSetValueEx Lib “advapi32.dll” Alias “RegSetValueExA” _ (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long Public Const WM_COMMAND = &H111 42 Public Const HKEY_CURRENT_USER = &H80000001 Public Const REG_SZ = 1 Public hCurKey As Long ‘ Change status Sub Y_SetStatus(blah As String) Dim It As Long It = FindWindow(“yahoobuddymain”, vbNullString) SaveSettingString HKEY_CURRENT_USER, + “\Custom Msgs”, 1, blah ‘luu thong diep vao Registry SendMessage It, WM_COMMAND, 388, 1& End Sub ‘ Kiem tra xem nguoi nao la nguoi dang nhap cuoi cung Function Y_GetLastLogin() Y_GetLastLogin = GetSettingString(HKEY_ CURRENT_USER, “Software\Yahoo\Pager”, “Yahoo! User ID”, “Yahoo! ID”) ‘lay thong diep tu registry End Function ‘ Doc gia tri tu registry de lay thong tin nguoi dang nhap cuoi cung Public Function GetSettingString(hKey As Long, strPath As String, strValue As String, Default As String) As String Dim lngValueType As Long Dim strBuffer As String Dim lngDataBufferSize As Long Dim intZeroPos As Integer If Not IsEmpty(Default) Then GetSettingString = Default Else GetSettingString = “” End If RegOpenKey hKey, strPath, hCurKey RegQueryValueEx hCurKey, strValue, 0&, lngValueType, ByVal 0&, lngDataBufferSize If lngValueType = REG_SZ Then strBuffer = String(lngDataBufferSize, “ “) RegQueryValueEx hCurKey, strValue, 0&, 0&, ByVal strBuffer, lngDataBufferSize intZeroPos = InStr(strBuffer, Chr$(0)) If intZeroPos > 0 Then GetSettingString = Left$(strBuffer, intZeroPos - 1) Else GetSettingString = strBuffer End If End If RegCloseKey hCurKey End Function ‘ Luu registry, giup ban thay doi cac status Public Sub SaveSettingString(hKey As Long, strPath As String, strValue As String, strData As String) RegCreateKey hKey, strPath, hCurKey RegSetValueEx hCurKey, strValue, 0, REG_SZ, ByVal strData, Len(strData) RegCloseKey hCurKey End Sub Có lẽ thủ thuật này hơi khác các thủ thuật khác ở chỗ là nó không dễ thực hiện, vì để thực hiện được bạn cần có kiến thức lập trình. Tuy nhiên nếu bạn muốn sử dụng thì có thể liên hệ với toàn soạn để chép chương trình hoàn chỉnh. Chương trình này tương đối dễ sử dụng. Sau khi download về, bạn hãy giải nén vào một thư mục nào đấy, trong thư mục vừa giải nén, bạn sẽ thấ ộ à status.txt và mộ để chạy chương trình chính, bạn sẽ thay đổi nội dung để có được các status theo ý mình bằng cách sau. Ví dụ bạn muốn status bạn có 2 câu là A và B thì bạn sẽ sử ư sau: · Hàng 1: 2 . Hàng 1 chỉ số status bạn có · Hàng 2: A. Các hàng từ 2 trở đi là các status bạn điền vào · Hàng 3: B Chú ý khi nhậ ạn không nên nhập hàng trắng, nếu không chương trình sẽ bị lỗi. Theo mặc định thì chương trình sẽ tự động change status sau mỗi 10s (Đây là khoảng thời gian tương đối ổn, nếu khoảng thời gian giữa 2 lần thay đổi quá ngắn thì chương trình sẽ phản tác dụng, có nghĩa là chỉ có bạn thấy được sự thay đổi này còn bạn của bạn thì không : D) và sẽ lặp lại cho đến khi bạn tắt chương trình. công làm nên. Như đã nói ở trên, URL sẽ được gửi tới chương trình của bạn dưới dạng một tham số (parameter). Tùy bạn sử dụng ngôn ngữ gì để viết chương trình mà cách thức lấy tham số sẽ khác nhau. Tôi xin lấy một ví dụ bằng VB để nhiều bạn có thể hiểu. Bạn hãy cẩn thận lư ày lại, mở VB, tạo một Project mới và thêm vào đoạn Code sau, dịch đoạn code nà đó đổi tên nó thành test. exe rồi copy nó đến thư mục C: (C:\test.exe). Private Sub Form_Load() MsgBox Command$ End Sub Ok. Bây giờ lại khởi động lại IE và thử một lần nữa xem sao. Bạn sẽ thấy mỗi khi click vào Menu thì một Message box sẽ hiển thị link mà bạn vừa chọn hoặc URL của hình mà bạn chọn. Nếu bạn không quen lắm với khái niệm tham số của chương trình thì bạn có thể dùng vài cách khác. Ví dụ như bạn có thể lưu URL vào một khóa nào đó trong Registry (VbScript có thể làm được điều này), sau đó chương trình của bạn sẽ tìm đến khóa này để lấy cái mà nó cần. Tuy nhiên bạn hãy cố làm quen với tham số đi là vừa, nó ngắn gọn và dễ dàng. Bây giờ việc còn lại của bạn chỉ là sửa lạ test.htm phù hợp với chương trình của bạn. Cảm ơn các bạn đã đọc bài và chúc thành công ! ...tiếp theo trang 33

Các file đính kèm theo tài liệu này:

  • pdfQuan điểm lập trình.pdf