Yêu cầu khi sử dụng toán tử

Một phần của tài liệu Lập trình hướng đối tượng với C docx (Trang 73 - 142)

Ngôn ngữ C# cung cấp khả năng cho phép nạp chồng toán tử cho các lớp mà chúng ta xây dựng. Một điều quan trọng để đảm bảo là lớp của chúng ta phải hỗ trợ các phương thức thay thế cho phép những ngôn ngữ khác có thể gọi để tạo ra các hiệu ứng tương tự.

Do đó, nếu chúng ta nạp chồng toán tử (+) thì chúng ta nên cung cấp một phương thức Add() cũng làm cùng chức năng là cộng hai đối tượng

Hạn chế việc sử dụng tùy tiện nạp chồng toán tử, vì đôi khi nạp chồng toán tử làm cho mã lệnh phức tạp và dễ nhầm lẫn

Khi nạp chồng một toán tử đảm bảo nguyên tắc phải nạp chồng toán tử nghịch đảo với nó. (== với !=, < với >, <= với >=)

Sử dụng nạp chồng toán tử khi kết quả trả về thật sự rõ ràng (VD: nếu chúng ta thực hiện toán tử or hay toán tử and giữa hai đối tượng Time thì kết quả hoàn toàn không có nghĩa gì hết.

Phải cung cấp các phương thức thay thế cho toán tử được nạp chồng. Đa số các ngôn ngữ điều không hỗ trợ nạp chồng toán tử. Vì nguyên do này nên chúng ta phải thực thi các phương thức thứ hai có cùng chức năng với các toán tử.

Ví dụ 2:

public static PhanSo operator +(PhanSo lhs, PhanSo rhs)

{

PhanSo c=new PhanSo ();

c.ts = lhs.ts * rhs.ms + rhs.ts * lhs.ms; c.ms = rhs.ms * lhs.ms; c.ToiGian(); try { } finally return c; }

public PhanSo Cong(PhanSo b)

{

PhanSo c = new PhanSo();

c.ts = this.ts * b.ms + b.ts * this.ms; c.ms = this.ms * b.ms;

c.ToiGian(); return c; }

public static PhanSo operator *(PhanSo l, PhanSo r)

{

PhanSo c = new PhanSo(); c.Ts = l.Ts * r.Ts;

c.Ms = l.Ms * r.Ms; c.ToiGian();

return c; }

public static bool operator == (PhanSo l , PhanSo r)

{ if (l.ts * r.ms == r.ts * l.ms) return true; else return false; }

public static bool operator != (PhanSo l,PhanSo r)

{ if (l.ts * r.ms == r.ts * l.ms) return false ; else return true ; }

public override bool Equals(object obj)

{ if (!(obj is PhanSo)) return false; else { PhanSo b = (PhanSo)obj; if (this.ts * b.ms == this.ms * b.ts ) return true; else return false;

//return this == (PhanSo)obj; }

Bài 12: K ế thừa và đa hình(1) 12.1. Giới thiệu chung về thừa kế

Thừa kế là một trong bốn nguyên tắc cơ sở của phương pháp lập trình hướng đối tượng. Đặc biệt đây là cơ sở cho việc nâng cao khả năng sử dụng lại các bộ phận của chương trình. Thừa kế cho phép ta định nghĩa một lớp mới, gọi là lớp dẫn xuất, từ một lớp đã có, gọi là lớp cơ sở. Lớp dẫn xuất sẽ thừa kế các thành phần (dữ liệu, hàm) của lớp cơ sở, đồng thời thêm vào các thành phần mới, bao hàm cả việc làm “tốt hơn” hoặc làm lại những công việc mà trong lớp cơ sở chưa làm tốt hoặc không còn phù hợp với lớp dẫn xuất. Chẳng hạn có thể định nghĩa lớp “mặt hàng nhập khẩu” dựa trên lớp “mặt hàng”, bằng cách bổ sung thêm thuộc tính “thuế”. Khi đó cách tính chênh lệch giá bán, mua cũ trong lớp “mặt hàng” sẽ không phù hợp nữa nên cần phải sửa lại cho phù hợp. Lớp điểm có màu được định nghĩa dựa trên lớp điểm không màu bằng cách bổ sung thêm thuộc tính màu, hàm display() lúc này ngoài việc hiển thị hai thành phần toạ độ còn phải cho biết màu của đối tượng điểm. Trong cả hai ví dụ đưa ra, trong lớp dẫn xuất đều có sự bổ sung và thay đổi thích hợp với tình hình mới.

Thừa kế cho phép không cần phải biên dịch lại các thành phần chương trình vốn đã có trong các lớp cơ sở và hơn thế nữa không cần phải có chương trình nguồn tương ứng. Kỹ thuật này cho phép chúng ta phát triển các công cụ mới dựa trên những gì đã có được.

Thừa kế cũng cho phép nhiều lớp có thể dẫn xuất từ cùng một lớp cơ sở, nhưng không chỉ giới hạn ở một mức: một lớp dẫn xuất có thể là lớp cơ sở cho các lớp dẫn xuất khác. ở đây ta thấy rằng khái niệm thừa kế giống như công cụ cho phép mô tả cụ thể hoá các khái niệm theo nghĩa: lớp dẫn xuất là một cụ thể hoá hơn nữa của lớp cơ sở và nếu bỏ đi các dị biệt trong các lớp dẫn xuất sẽ chỉ còn các đặc điểm chung nằm trong lớp cơ sở.

Hình 4.1 mô tả một sơ đồ thừa kế của các lớp, có cung đi từ lớp này sang lớp kia nếu chúng có quan hệ thừa kế. Ta gọi đó là đồ thị thừa kế. Sau đây là một số mô tả cho các lớp xuất hiện trong đồ thị thừa kế ở trên.

1. Lớp mặt hàng

các thuộc tính tên

số lượng trong kho

giá mua

giá bán

các phương thức

hàm chênh lệch giá bán mua {giá bán - giá mua}

thủ tục mua(q)

{Thêm vào trong kho q đơn vị mặt hàng}

thủ tục bán(q)

{Bớt đi q đơn vị mặt hàng có trong kho}

2. Lớp mặt hàng nhập khẩu thừa kế từ mặt hàng

các thuộc tính thuế nhập khẩu

các phương thức

hàm chênh lệch giá bán -mua

{giá bán - giá mua* thuế nhập khẩu}

3. Lớp xe gắn máy thừa kế từ mặt hàng nhập khẩu

các thuộc tính dung tích xy lanh

các phương thức

4. Lớp hàng điện tử dân dụng thừa kế từ mặt hàng

điện áp

thời hạn bảo hành

các phương thức

hàm thời gian bảo hành thực tế ...

Tính đa hình cũng là một trong các điểm lý thú trong lập trình hướng đối tượng, được thiết lập trên cơ sở thừa kế trong đó đối tượng có thể có biểu hiện khác nhau tuỳ thuộc vào tình huống cụ thể. Tính đa hình ấy có thể xảy ra ở một hành vi của đối tượng hay trong toàn bộ đối tượng. Ví dụ trực quan thể hiện tính đa hình là một ti vi có thể vừa là đối tượng của mặt hàng vừa là đối tượng của lớp mặt hàng điện tử dân dụng. Các đối tượng hình học như hình vuông, hình tròn, hình chữ nhật đều có cùng cách vẽ như nhau: xác định hai điểm đầu và cuối, nối hai điểm này. Do vậy thuật toán tuy giống nhau đối với tất cả các đối tượng hình, nhưng cách vẽ thì phụ thuộc vào từng lớp đối tượng cụ thể. Ta nói phương thức nối điểm của các đối tượng hình học có tính đa hình. Tính đa hình còn được thể hiện trong cách thức hiển thị thông tin trong các đối tượng điểm màu/không màu.

Các ngôn ngữ lập trình hướng đối tượng đều cho phép đa thừa kế, theo đó một lớp có thể là dẫn xuất của nhiều lớp khác. Do vậy dẫn tới khả năng một lớp cơ sở có thể được thừa kế nhiều lần trong một lớp dẫn xuất khác, ta gọi đó là sự xung đột thừa kế. Điều này hoàn toàn không hay, cần phải tránh. Từng ngôn ngữ sẽ có những giải pháp của riêng mình, C# đưa ra khái niệm thừa kế ảo.

Trong chương này ta sẽ đề cập tới các khả năng của C# để thể hiện nguyên tắc thừa kế khi viết chương trình.

12.2. Xây dựng lớp dẫn xuất thừa kế từ lớp cơ sở

Xây dựng lớp Luong để tính lương cho các cán bộ với các thông tin sau:

Các thành phần dữ liệu

- String Hoten; // Là thuộc tình dùng để nhập thông tin về họ tên cho Cấn bộ

- double HesSoLuong;// Là thuộc tính dùng để nhập thong tin về hệ số lương cho cán bộ

- int LuongCoBan;// Là thuộc tính dùng để nhập lương cơ bản cho các cán bộ(Biết rằng tất cả các cán bộ thì có chung lương cơ bản

Các phương thức

Lớp mặt hàng các thuộc tính

tên

số lượng trong kho giá mua

giá bán các phương thức

chênh lệch giá bán mua mua(q) bán(q) Lớp Tivi các thuộc tính kích thước màn hình điều khiển từ xa Lớp Đồ điện tử dân dụng các thuộc tính điện áp thời hạn bảo hành các phương thức

thời gian bảo hành thực tế

Lớp Ôtô các thuộc tính Mác Lớp Xe gắn máy các thuộc tính dung tích xy lanh Lớp mặt hàng nhập khẩu các thuộc tính thuế nhập khẩu các phương thức

- Phương thức khởi tạo không tham số dùng để khởi tạo giá trị lương cơ bản LuongCoBan = 450, HeSoLuong=2.34;

- Phương thức thiết lập hai tham số dùng để khởi tạo LuongCoBan và HesoLuong thông qua đối số của phương thức

- Phương thức nhập thông tin của lớp

- Phương thức hiện thông tin của lớp

- Phương thức tính lương theo công thức HeSoLuong*LuongCoBan

Sau đó kế thừa lớp Luong để xây dựng lớp LuongMoi dùng để tính lương mới cho các cán bộ với việc bổ xung hệ số phụ cấp(HeSoPhuCap) cho mỗi cán bộ. Từ đó việc tính lương được tính theo công thức:

(HeSoLuong*LuongCoBan)+(HeSoLuong*LuongCoBan)*HeSoPhuCap. Hãy bổ xung các thành phần dữ liệu cũng như các phương thứ c cần thiết vào lớp LuongMoi để có thể tính lương cho cán bộ theo chuẩn mới đã nêu.

Trong ngụn ngữ C# để tạo một lớp dẫn xuất từ một lớp ta thêm dấu hai chấm vào sau tên lớp dẫn xuất và trước tên lớp cơ sở:

Xây dựng chương trình sử dụng các lớp trên

Ví dụ 12.1 Xây dựng lớp cơ sở và lớp kế thừa

using System; class Luong {

private string Hoten;

private double HeSoLuong; public static int LuongCoBan; public Luong()

{

LuongCoBan = 450; HeSoLuong = 2.34; }

public Luong(int lcb,double hsl) {

LuongCoBan = lcb; HeSoLuong = hsl; }

public void Nhap() { Console.Write("Ho va ten:"); Hoten = Console.ReadLine(); Console.Write("He so luong:"); HeSoLuong = int.Parse(Console.ReadLine()); }

public double TinhLuong() {

return LuongCoBan * HeSoLuong; }

public void Hien() {

Console.WriteLine("Ho ten:{0}\nHe so luong:{1}", Hoten, HeSoLuong); }

}

class LuongMoi : Luong {

private double HeSoPhuCap; public LuongMoi(): base() {

HeSoPhuCap = 0.4; }

public LuongMoi(int lcb, double hsl, double hspc): base(lcb, hsl) {

HeSoPhuCap = hspc; }

public new void Nhap() {

base.Nhap();

HeSoPhuCap = double.Parse(Console.ReadLine()); }

public new double TinhLuong() {

return base.TinhLuong() + base.TinhLuong() * HeSoPhuCap; }

}

class Tester {

static void Main() {

LuongMoi a = new LuongMoi(); a.Nhap(); a.Hien(); Console.WriteLine("Luong:{0}", a.TinhLuong()); Console.ReadKey(); } }

12.3. Mức truy cập trong lớp dẫn xuất

Khả năng hiện hữu của một lớp và các thành viên của nó có thể được hạn chế thông qua việc sử dụng các bổ sung truy cập: public, private, protected, internal, và protected internal.

Như chúng ta đã thấy, public cho phép một thành viên có thể được truy cập bởi một phương thức thành viên của những lớp khác. Trong khi đó private chỉ cho phép các phương thức thành viên trong lớp đó truy xuất. Từ khóa protected thì mở rộng thêm khả năng của private cho phép truy xuất từ các lớp dẫn xuất của lớp đó. Internal mở rộng khả năng cho phép bất cứ phương thức của lớp nào trong cùng một khối kết hợp (assembly) có thể truy xuất được. Một khối kết hợp được hiểu như là một khối chia xẻ và dùng lại trong CLR. Thông thường, khối này là tập hợp các tập tin vật lý được lưu trữ trong một thư mục bao gồm các tập tin tài nguyên, chương trình thực thi theo ngôn ngữ IL,...

Từ khóa internal protected đi cùng với nhau cho phép các thành viên của cùng một khối assembly hoặc các lớp dẫn xuất của nó có thể truy cập.

Chúng ta có thể xem sự thiết kế này giống như là internal hay protected. Các lớp cũng như những thành viên của lớp có thể được thiết kế với bất cứ mức độ truy xuất nào. Một lớp thường có mức độ truy xuất mở rộng hơn cách thành viên của lớp, còn các thành viên thì mức độ truy xuất thường có nhiều hạn chế. Do đó, ta có thể định nghĩa một lớp

MyClass như sau:

public class MyClass {

//...

protected int myValue; }

Như trên biến thành viên myValue được khai báo truy xuất protected mặc dù bản thân lớp được khai báo là public. Một lớp public là một lớp sẵn sàng cho bất cứ lớp nào khác muốn tương tác với nó. Đôi khi một lớp được tạo ra chỉ để trợ giúp cho những lớp khác trong một khối assemply, khi đó những lớp này nên được khai báo khóa internal hơn là khóa public.

Dưới đây là bảng về mức truy cập các thành phần của một lớp

Các thành phần trong

Lớp A Lớp B kế thừa lớp A Lớp C không kế thừa lớp

A

private Không truy xuất được Không truy xuất được

protected Truy xuất được vì B dẫn xuất từ A Không truy xuất được

internal Truy xuất được nếu B(C) cùng khối kết hợp (assembly) với A (cùng một project)

protected internal Truy xuất được vì B dẫn xuất từ A Truy xuất được nếu C cùng khối kết hợp (assembly) với A

12.4. Gọi phương thức khởi tạo của lớp cơ sở

Trong ví trên một lớp mới tên là LuongMoi được dẫn xuất từ lớp cơ sở Luong, lớp LuongMoi có hai phương thức khởi tạo một phương thức khởi tạo không tham số và một

phương thức khởi tạo ba tham số. Trong hai phương thức khởi tạo của lớp dẫn xuất này có gọi phương thức khởi tạo của lớp cơ sở. Cách gọi được thực hiện bằng việc đặt dấu hai chấm ngay sau phần khai báo danh sách tham số và tham chiếu đến lớp cơ sở thông qua từ khóa base:

public LuongMoi(): base() {

HeSoPhuCap = 0.4; }

public LuongMoi(int lcb, double hsl, double hspc): base(lcb, hsl) {

HeSoPhuCap = hspc; }

Bởi vì các lớp không được kế thừa các phương thức khởi dựng của lớp cơ sở, do đó lớp dẫn xuất phải thực thi phương thức khởi dựng riêng của nó. Và chỉ có thể sử dụng phương thức khởi dựng của lớp cơ sở thông qua việc gọi tường minh.

Một điều lưu ý trong ví dụ trên là việc lớp LuongMoi thực thi một phiên bản mới của phương thức Nhap() và TinhLuong():

public new void Nhap()

và public new double TinhLuong()

Từ khóa new được sử dụng ở đây để chỉ ra rằng người lập trình đang tạo ra một phiên bản mới cho phương thức này bên trong lớp dẫn xuất. Nếu lớp cơ sở có phương thức khởi dựng mặc định, thì lớp dẫn xuất không cần bắt buộc phải gọi phương thức khởi dựng của lớp cơ sở một cách tường minh. Thay vào đó phương thức khởi dựng mặc định của lớp cơ sở sẽ được gọi một cách ngầm định. Tuy nhiên, nếu lớp cơ sở không có phương thức khởi dựng mặc định, thì tất cả các lớp dẫn xuất của nó phải gọi phương thức khởi dựng của lớp cơ sở một cách tường minh thông qua việc sử dụng từ khóa base.

12.5. Truy xuất các thành phần của lớp cơ sở

Trong ví dụ trên phương thức Nhap() của lớp LuongMoi sẽ làm ẩn và thay thế phương thức Nhap của lớp cơ sở Luong. Khi chúng ta gọi phương thức Nhap của một đối tượng của lớp LuongMoi thì phương thức LuongMoi.Nhap() sẽ được thực hiện, không

phải phương thức Luong.Nhap() của lớp cơ sở Luong. Tuy nhiên, ta có thể gọi phương thức Nhap() của lớp cơ sở thông qua từ khóa base:

base.Nhap(); // gọi phương thức cơ sở

Từ khóa base chỉ đến lớp cơ sở cho đối tượng hiện hành

12.6. Boxing và Unboxing dữ liệu

Boxing và unboxing là những xử lý cho phép kiểu dữ liệu giá trị (như int, long,...) được đối xử như kiểu dữ liệu tham chiếu (các đối tượng). Một giá trị được đưa vào bên trong của đối tượng, được gọi là Boxing. Trường hợp ngược lại, Unboxing sẽ chuyển từ đối tượng ra một giá trị. Xử lý này đã cho phép chúng ta gọi phương thức ToString( ) trên kiểu dữ liệu int trong ví dụ 3.4.

Boxing được thực hiện ngầm định

Boxing là một sự chuyển đổi ngầm định của một kiểu dữ liệu giá trị sang kiểu dữ liệu tham chiếu là đối tượng. Boxing một giá trị bằng cách tạo ra một thể hiển của đối tượng cần dùng và sao chép giá trị trên vào đối tượng mới tạo. Ta có hình vẽ sau minh họa quá trình Boxing một số nguyên.

Boxing số nguyên.

Boxing được thực hiện ngầm định khi chúng ta đặt một kiểu giá trị vào một tham chiếu đang chờ đợi và giá trị sẽ được đưa vào đối tượng một cách tự động ngầm định. Ví dụ, nếu chúng ta gán một kiểu dư liệu cơ bản như kiểu nguyên int vào một biến

kiểu Object (điều này hoàn toàn hợp lệ vì kiểu int được dẫn xuất từ lớp Object) thì giá trị này sẽ được đưa vào biến Object, như minh họa sau:

using System; class Boxing {

public static void Main() {

int i = 123;

Console.WriteLine(“The object value = {0}”, i); }

}

Unboxing phải được thực hiện tường minh

Việc đưa một giá trị vào một đối tượng được thực hiện một cách ngầm định. Và sự thực hiện ngược lại, unboxing, tức là đưa từ một đối tượng ra một giá trị phải được

Một phần của tài liệu Lập trình hướng đối tượng với C docx (Trang 73 - 142)

Tải bản đầy đủ (PDF)

(142 trang)