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.
40 trang |
Chia sẻ: tlsuongmuoi | Lượt xem: 2126 | Lượt tải: 0
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:
- Quan điểm lập trình.pdf