Những lớp lồng bên trong có lợi là có khả năng truy cập đến tất cả các thành viên của lớp ngoài.
Một phương thức của lớp lồng có thể truy cập đến biến thành viên private của lớp ngoài.
Hơn nữa, lớp lồng bên trong có thể ẩn đối với tất cả các lớp khác, lớp lồng có thể là private cho lớp ngoài.
Cuối cùng, một lớp làm lồng bên trong là public và được truy cập bên trong phạm vi của lớp ngoài. Nếu một lớp Outer là lớp ngoài, và lớp Nested là lớp public lồng bên trong lớp Outer, chúng ta có thể tham chiếu đến lớp Tested như Outer.Nested, khi đó lớp bên ngoài hành động ít nhiều giống như một namespace hay một phạm vi.
class Class1 {
private int a,b;
public Class1 (int a, int b) {
this.a = a; this.b = b; }
public class con {
public void tong(Class1 l) {
System.Console.WriteLine("day la lop long ben trong ");
System.Console.WriteLine("a {0} + b {1} = {2}", l.a, l.b, l.a + l.b); }
} }
Điều thú vị trong phương thức Tong() truy cập dữ liệu thành viên private là l.a và
l.b. Hai viến thành viên private này sẽ không cho phép truy cập nếu con không phải là lớp lồng bên trong của lớp Class1
Lưu ý là trong hàm Main() khi khai báo một thể hiện của lớp lồng bên trong, chúng ta phải xác nhận tên của lớp bên ngoài, tức là lớp Class1.
Class1 t1 = new Class1(4, 5);
Class1.con t2 = new Class1.con();
Bài 7: Định nghĩa chồng toán tử tr ên lớp 7.1. Khái niệm về chồng toán tử
Hướng thiết kế của ngôn ngữ C# là tất cả các lớp do người dùng định nghĩa (userdefined classes) có tất cả các chức năng của các lớp đựơc xây dựng sẵn.
Ví dụ, giả sử chúng ta định nghĩa một lớp để thể hiện một phân số. Để đảm bảo rằng lớp này có tất cả các chức năng tương tự như các lớp được xây dựng sẵn, nghĩa là chúng ta cho phép thực hiện các phép toán số học trên các thể hiện của phân số chúng ta (như các phép toán cộng phân số, nhân hai phân số,...) và chuyển đổi qua lại giữa phân số và kiểu dữ liệu xây dựng sẵn như kiểu nguyên (int).
Chúng ta có thể dễ dàng thực hiện các toán tử bằng cách gọi một phương thức, tương tự như câu lệnh sau:
PhanSo c = a.cong(b);
// trong đó a, b là hai biến đối tượng PhanSo
Tuy nhiên với cách này thì không được tự nhiên cho lắm. Thật là tốt nếu chúng ta thiết kế được:
PhanSo c= a +b ;
Điều này trong ngôn ngữ C# hoàn toàn có thể làm được bằng cách nạp chồng toán tử
7.2. Cú pháp nạp chồng toán tử
Trong C#, các toán tử là các phương thức tĩnh, kết quả trả về của nó là giá trị biểu
diễn kết quả của một phép toán và các tham số là các toán hạng. Khi ta tạo một toán
tử cho một lớp ta nói là ta nạp chồng toán tử, nạp chồng toán tử cũng giống như bất
kỳ việc nạp chồng các phương thức nào khác
7.2.1. Toán tử 1 ngôi
public static Kieu_Du_Lieu operator{Phép toán} (Kieu_Du_Lieu lhs, Kieu_Du_Lieu rhs).
Ví dụ: như nạp chồng toán tử (+) chúng ta có thể viết:
public static PhanSo operator +(PhanSo lhs, PhanSo rhs)
7.2.2. Toán tử 2 ngôi Toán tử chuyển đổi Toán tử chuyển đổi
Từ ngữ ngầm định (implicit) được sử dụng khi một chuyển đổi đảm thành công mà không mất bất cứ thông tin nào của dữ liệu nguyên thủy. Trường hợp ngược lại, tường minh (explicit) không đảm bảo, bảo toàn dữ liệu sau khi chuyển đổi do đó việc này sẽ được thực hiện một cách công khai.
Ví dụ 1:
public PhanSo(int so) {
ts = so; ms = 1 }
public static implicit operator PhanSo(int so) {
return new PhanSo(so); }
public static explicit operator int(PhanSo pt) {
return pt.Ts / pt.Ms; }
7.3. Yêu cầu khi sử dụng toán tử
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ở