Showing posts with label C# OOP. Show all posts
Showing posts with label C# OOP. Show all posts

Sunday, March 20, 2016

Overriding - Overloading - Hidding

Tìm hiểu Overriding và Overloading trong OOP


Difference between method overriding(Ghi đè) and overloading(Nạp chồng).

Trong OOP, Overriding và Overloading là 2 kỹ thuật giúp tạo nên Tính đa hình (Polymorphism), mộ trong những sức mạnh của lập trình hướng đối tượng. Bài viết trong mục này sẽ tìm hiểu về khái niệm, cách dùng và tác dụng của 2 kỹ thuật trên.

Trong phạm vi bài viết này chúng ta chỉ đề cập đến 2 kỹ thuật này trong Method. Trong lập trình cấu trúc C, chúng ta không thể nào khai báo các function(method) trùng tên, nhưng trong OOP chỉ cần áp dụng hai kỹ thuật này, chúng ra hoàn toàn có thể làm được điều đó.

Overriding

Khi sử dụng Overriding, bạn đã thay đổi hành vi của method mà nó override. Nói đơn giản là nó đã ghi đè nội dung lên method trước đó. 
Lưu ý: Method này phải được khai báo giống hệt với method trước (nó có thể là lớp base hoặc lớp dẫn xuất trước) trước tên method, đối số truyền vào và kiểu trả về.
- Overriding thường được sử dụng trong method ở lớp con.
Một số quy tắc sử dụng phương thức overriding:
* Các phương thức được mô tả static thì không overriden nhưng được mô tả lại.
(có một bài viết về static method trong mục Programming Technique - bạn có thể tìm nó để đọc)
Các phương thức không được kế thừa sẽ không được overriden (hiển nhiên).
ví dụ như các Method được để chế độ private thì nó sẽ k được kế thừa -> thì sẽ k được overriden.
Chỉ có thể override các phương thức không phải final (java) được khai báo là public hoặc protected. -> cái này nói về Java, bạn có thể bỏ qua nếu chỉ lưu ý về C++

Overloading
Overloading đơn giản chỉ để tạo ra các method cùng tên trong cùng một Class. 
Lưu ý: Nhưng các method đó phải khác nhau về đối số đầu vào (argument) hoặc kiểu trả về.
– Các method với kỹ thuật này không ghi đè mà cùng tồn tại song song.

/*-----------------------------------------------------------------------------------------------------*/

Phân biệt sự khác nhau giữa overloading, overriding và hidding


1. Overloading và hidding
a. Overloading:
 Trong C++, overloading đơn giản là sử dụng cùng tên hàm cho các hàm có chức năng khác nhau trong cùng 1 phạm vi (class level,…). 
Giả sử chúng ta có 3 hàm sau được khai báo trong cùng 1 tập tin.

bool process( Credit & ); 
bool process( Acceptance & );
bool process( OrderForm & );
rõ ràng 3 hàm này được overloading .

 Trình biên dich phân biệt các hàm trên như thế nào?.
Thực tế trình biên dịch sẽ phân biệt các hàm trên giữa vào các tham số được sử dụng để gọi hàm.
Trong C++, tên hàm được hiểu là sự kết hợp giữa định danh của hàm (the function’s identifier)(trong ví dụ trên là process) và kiểu của tham số lúc khai báo.


 Khi ta nhúng các hàm trên vào trong 1 lớp:

class Processor
public:
virtual ~Processor();
bool process( Credit & );
bool process( Acceptance & );
bool process( OrderForm & );
};

Các hàm này vẫn overloading theo cách giống như trên .
Bây giờ chúng ta sẽ thực hiện kế thừa lớp trên như sau:

class MyProcessor : public Processor
public:
bool process( Rejection & );
};

Tại đây 3 hàm của lớp base Processor chúng bị ẩn đi nên chúng sẽ không bị overload trong lớp dẫn xuất.
Lưu ý: Cơ sở là hàm base, hàm cha. dẫn xuất là hàm có. chỉ có 1 hàm cở sở nhưng có nhiều hàm dẫn xuất

b. Hidding:
 Hidding là gì?
Trong class MyProcessor 3 hàm trong lớp Processor đều bị ẩn đi (lớp dẫn xuất bình thường không thế nhìn thầy(tuy nhiên, có thể gọi băng cách chỉ đến lớp cha ) ). Ta hiểu rằng bị ẩn là có tồn tại có thể gọi đến nhưng măc định là không biết đến, không đề cập.

 Hidding có tác dụng gì?
Chúng ta có các thuộc tính và các phương trình trùng tên trong lớp cơ sở và lớp dẫn xuất, tuy nhiên chúng lại không có liên quan đến nhau thì hidding giúp chúng vẫn hiện diện, có thể gọi đến và không xảy ra lỗi.

2. Overriding
 Overriding xảy ra khi nào?

Overrding chỉ xảy ra khi lớp cơ sở có sự xuất hiện của hàm ảo (từ khóa Virtual cho method cần được override).

Overriding không có gì liên quan đến overloading cả. 1 lớp cở sở không có hàm ảo thì không thế xuất hiện overriding mà chỉ có hidden.

class Doer {
public:
virtual ~Doer();
bool doit( Credit & );
virtual bool doit( Acceptance & );
virtual bool doit( OrderForm & );
virtual bool doit( Rejection & );
};

class MyDoer : public Doer {
private:
bool doit( Credit & ); // #1, hides
bool doit( Acceptance & ); // #2, overrides(có từ khóa virtual ở lớp base)
virtual bool doit( Rejection & ) const; // #3, doesn't override(tham số truyền vào là con trỏ hằng this khác với con trỏ this)
double doit( OrderForm & ); // #4, an error(hàm này bị lỗi vì nó cũng được overriding tuy nhiên kiểu trả về của chúng khác nhau vì “the function signture không được phân biệt bằng kiểu trả về”)
};

 Overrding và access level:

Hàm được đánh nhãn là #2 bị overriding tương ứng với chức năng của lớp cơ sở.

Chúng ta chú ý rằng overriding ko bị ảnh hưởng bởi acess level (public,private,protected) khi kế thừa.

Điều đó có nghĩa là lớp cở sở là public và lớp dẫn xuất là private thì vấn xảy ra overriding. Thông thường hàm bị overriding sẽ có cùng acess level với hàm tương ứng ở lớp cở sở.

class Visitor
public:
virtual void visit( Acceptance & );
virtual void visit( Credit & );
virtual void visit( OrderForm & );
virtual int numHits();
};
class ValidVisitor : public Visitor {
void visit( Acceptance & ); // overrides
void visit( Credit & ); // overrides
int numHits( int ); // #5, nonvirtual, khác nhau về đối số
};

Trong trường hợp này người thiết kế cho phép tùy biến cách “cư xử” cửa lớp cở sở, nhưng muốn người sử dụng sử dụng lại interface của lớp cở sở. Người thiết kế sẽ khai báo lớp base với hàm có access level là public và lớp dẫn xuất cò hàm tượng với những với access level là private

 Ý nghĩa của từ khóa virtual và tính virtual của hàm dẫn xuất:

Chú ý rằng lớp dẫn xuất (lớp kế thừa cái lớp trước đó) có hay không từ khóa virtual chỉ là tùy chọn không bắt buộc, ý nghĩa của ý nghĩa của hàm sẽ không thay đổi.


Có 1 quan niệm sai lầm phổ biên là khi không xuất hiện từ khóa virtual ở lớp dẫn xuất thì nó sẽ ngăn cản thì nó sẽ ngăn cản overriding ở các lớp dẫn xuất tiếp theo
-> Đây là một ý nghĩ sai lầm.

hàm MyValidVisitor::visit( Credit & ) sẽ bị overriding bởi hàm tương ứng ở lớp dẫn xuất ValidVisitor().
class MyValidVisitor : public ValidVisitor
void visit( Credit & ); // overrides
void visit( OrderForm & ); // overrides
int numHits(); // #6, virtual, overrides Visitor::numHits
};


Nó cũng hoàn toàn hợp lệ cho những lớp dẫn xuất ở xa hơn hàm MyValidVisitor::visit( OrderForm & ) overriding hàm tương ứng ở lớp Visitor.

Các lớp dẫn xuất cũng bị override với những hàm của lớp cở sở “ông,..”.mà những lớp đó không được nhìn thấy trong phạm vi lớp dẫn xuất ”chú”. Ví dụ hàm MyValidVisitor::numHits vẫn override hàm Visitor::numHits mặc dù hàm Visitor::numHits bị ẩn ở lớp ValidVisitor.


 Kiểu trả về “hiệp biến” (covariant return types) và overriding:
Trong phân tích trên: MyDoer::double doit( OrderForm & ), xảy ra lỗi khi trong thời gian dịch vì kiểu trả về của hàm bị overriding không tương xứng với hàm ở lớp cở sở.
Tuy nhiên có 1 ngoại lệ nếu kiểu trả về là con trỏ đến lớp 2 lớp có quan hệ với nhau (covariant return types) 
class B { 
virtual B *clone() const = 0;
};
class D : public B {
D *clone() const;
};
Hàm clone ở lớp dẫn xuất bị override là hoàn toàn hợp lệ.

source: http://diendan.congdongcviet.com/threads/t199237::phan-biet-su-khac-nhau-giua-overloading-overriding-hidding.cpp




Posted By Thang Vuong 00:01

Sunday, February 21, 2016

CÁC NGUYÊN LÝ CỦA LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG - Object Oriented Programming

Lập trình hướng đối tượng (OOP) là một trong những kỹ thuật lập trình rất quan trọng hiện nay. Nó được áp dụng ở hầu hết các ứng dụng thực tế xây dựng tại các doanh nghiệp. Hầu hết các ngôn ngữ lập trình và framework lập trình phổ biến hiện nay như Java, PHP, .NET đều hỗ trợ lập trình hướng đối tượng. Các lập trình viên đa phần đã được học về lập trình hướng đối tượng ở trường đại học nhưng các nguyên lý cơ bản của lập trình hướng đối tượng đôi khi lại không nắm rõ dẫn đến sử dụng sai, không đúng triết lý của lập trình hướng đối tượng.
Trong bài viết này, tôi sẽ tóm lược lại các nguyên lý cơ bản của lập trình hướng đối tượng nhằm giúp các bạn có được một cái nhìn tổng quát về OOP cũng như cách áp dụng nó.

Lập trình hướng đối tượng là gì?

Lập trình hướng đối tượng là một kỹ thuật lập trình cho phép lập trình viên tạo ra các đối tượng trong code trừu tượng hóa các đối tượng thực tế trong cuộc sống. Hướng tiếp cận này hiện đang rất thành công và đã trở thành một trong những khuôn mẫu phát triển phần mềm, đặc biệt là các phần mềm cho doanh nghiệp.
Khi phát triển ứng dụng sử dụng OOP, chúng ta sẽ định nghĩa các lớp (class) để mô hình các đối tượng thực tế. Trong ứng dụng các lớp này sẽ được khởi tạo thành các đối tượng và trong suốt thời gian ứng dụng chạy, các phương thức (method) của những đối tượng này sẽ được gọi.

Lớp định nghĩa đối tượng sẽ như thế nào: gồm những phương thức và thuộc tính (property) gì. Một đối tượng chỉ là một thể hiện của lớp. Các lớp tương tác với nhau bởi các public API: là tập các phương thức, thuộc tính public của các lớp.

Lớp và đối tượng


OOP có 3 nguyên lý cơ bản chúng ta sẽ cùng tìm hiểu chi tiết sau đây đó là:

Tính đóng gói (Encapsulation)

Tính đóng gói tức là quy tắc yêu cầu trạng thái bên trong của một đối tượng được bảo vệ và tránh truy cập được từ các code bên ngoài (tức là các code bên ngoài không thể trực tiếp nhìn thấy và thay đổi trạng thái của một đối tượng). Bất cứ truy cập nào tới trạng thái bên trong này bắt buộc phải thông qua một public API để đảm bảo trạng thái của đối tượng luôn hợp lệ bởi vì các public API đảm bảo tất cả các quy tắc kiểm tra tính hợp lệ cũng như trình tự thực hiện được áp dụng mỗi khi thay đổi trạng thái đó.
Vì trạng thái đối tượng không hợp lệ thường do: chưa được kiểm tra tính hợp lệ, các bước thực hiện không đúng trình tự hoặc bị bỏ qua nên trong
OOP có một quy tắc quan trọng cần nhớ đó là phải luôn khai báo các trạng thái bên trong của đối tượng là private và chỉ cho truy cập qua các public, protected method/property. Khi sử dụng các đối tượng ta không cần biết bên trong nó làm việc như thế nào, ta chỉ cần biết các public API là gì và điều này đảm bảo những gì thay đổi đối tượng sẽ được kiểm tra bởi các quy tắc logic bên trong, tránh đối tượng bị sử dụng không chính xác.

Cũng giống như viên thuốc, chúng ta chỉ biết nó chữa bệnh này, bệnh kia và một số thành phần chính còn cụ thể bên trong nó có những gì thì hoàn toàn không biết.

Tính đóng gói

Tính kế thừa (Inheritance)

Khi bắt đầu xây dựng ứng dụng bởi các lớp, chúng ta thường thấy trường hợp một số lớp dường như có quan hệ với những lớp khác, chúng khá tương đồng. VD: 3 lớp AndroidPhone, IPhone, WindowsPhone ở hình dưới:
Tính kế thừa
Mỗi lớp đều đại diện cho một loại smartphone khác nhau nhưng lại có những thuộc tính giống nhau. Thay vì sao chép những thuộc tính này, sẽ hay hơn nếu ta đặt chúng ở một nơi có thể dùng bởi những lớp khác. Điều này được thực hiện bởi tính kế thừa trong OOP: chúng ta có thể định nghĩa lớp cha – base class (trong trường hợp này là Smartphone ) và có những lớp con kế thừa từ nó (derived class), tạo ra một mối quan hệ cha/con như hình dưới:
Tính kế thừa
Bây giờ, các lớp con có thể kế thừa 3 thuộc tính từ lớp cha. Nếu các chức năng của lớp cha đã được định nghĩa đầy đủ thì lập trình viên sẽ không phải làm bất cứ việc gì với lớp con. Còn nếu một lớp con muốn chức năng khác so với định nghĩa ở lớp cha thì nó có thể dễ dàng ghi đè (override) chức năng đã được định nghĩa trên lớp cha này.
Tính kế thừa
Như hình trên bây giờ nếu gọi thuộc tính OSVersion trên lớp AndroidPhone thì nó sẽ dùng định nghĩa được khai báo ở lớp này, trong khi hai lớp Iphone & Windows Phone vẫn dùng định nghĩa được khai báo ở lớp Smartphone.
Tính đa hình (Polymorphism)

Với đa số lập trình viên thì tính Kế thừa và Đóng gói trong OOP khá dễ hiểu còn tính Đa hình khi mới tiếp cận sẽ thấy khó hiểu hơn một chút. Tuy nhiên đây lại là một tính chất có thể nói là chứa đựng hầu hết sức mạnh của lập trình hướng đối tượng. Hiểu một cách đơn giản: Đa hình là khái niệm mà hai hoặc nhiều lớp có những phương thức giống nhau nhưng có thể thực thi theo những cách thức khác nhau.

Ví dụ như ở phần trên, mỗi một smartphone kế thừa từ lớp Smartphone nhưng có thể lưu trữ dữ liệu trên cloud theo những cách khác nhau:
  • AndroidPhone lưu trữ bằng Google Drive
  • Iphone lưu trên iCloud
  • WindowsPhone sử dụng SkyDrive.
    Tính đa hình
Mặc dù tất cả đều là Smartphone và nếu ta viết một hàm dùng kiểu Smartphone làm tham số thì khi gọi hàm có thể truyền vào một đối tượng kiểu AndroidPhone, Iphone hoặc WindowsPhone bởi vì chúng đều kế thừa từ lớp Smartphone nên được chấp nhận (hiểu nôm na một AndroidPhone, Iphone, WindowsPhone cũng là một Smartphone).
Hàm trên thậm chí không cần quan tâm smartphone nào được truyền vào do chúng thừa kế từ lớp Smartphone nên mọi thứ cần thiết đã có: ở đây chính là những public method/property được định nghĩa trên lớp cha Smartphone. Nếu các lớp con không định nghĩa lại (overrides) phương thức CloudStore() thì phương thức CloudStore() trên lớp cha (Smartphone) sẽ được gọi. Còn nếu lớp con override lại phương thức CloudStore() của lớp cha như ở hình trên thì phương thức CloudStore() trên lớp con sẽ được gọi mặc dù code trong hàm đang thao tác với đối tượng kiểu Smartphone nói chung.

Tính Đa hình như trên là một tính chất hết sức mạnh mẽ bởi vì nó mang lại cho code khả năng tổng quát hóa. Chúng ta không cần tạo ra phương thức cho mỗi kiểu kế thừa từ lớp cha Smartphone mà chỉ cần nhận một biến kiểu Smartphone và có thể làm việc với bất cứ lớp nào kế thừa từ nó. Điều duy nhất không làm được ở đây là sử dụng những phương thức mà chỉ được khai báo trên các lớp con. VD: nếu ta có một phương thức trên lớp IPhone gọi là OpenSiri() nhưng không được khai báo trên lớp Smartphone, khi đó muốn gọi nó sẽ bắt buộc phải ép kiểu từ Smartphone sang IPhone trước khi gọi.
Interface
Đa hình dựa trên Kế thừa không phải bao giờ cũng là lựa chọn tốt nhất. VD: Ta có biểu đồ lớp như sau
Tính đa hình
Ta thấy rõ ràng rằng 3 lớp bên dưới (Iphone, Laptop, FingerprintScanner) đều là những thứ có thể truy cập được bằng vân tay nhưng chúng thực hiện theo những cách khác nhau. Những lớp này có chung một hành động tạm gọi là định danh bằng sinh trắc học – BiometricAuth(). Nếu ta cố gắng gộp cả 3 lớp này vào thành 1 lớp chung sẽ không hay vì rất khó để tìm ra điểm chung tổng quát của chúng ngoài việc có thể truy cập bằng vân tay.
Do vậy thay vì sử dụng Kế thừa ở đây, ta có thể sử dụng một kỹ thuật khác đó là Interface. Interface đơn giản là một giao kèo chỉ ra rằng code của bạn sẽ thực thi và hỗ trợ một public API cụ thể nào đó. Tuy nhiên các public API này được thực hiện như thế nào thì không được chỉ ra trên Interface mà sẽ được chỉ ra trên lớp thực thi interface này. Về cơ bản giao kèo là một danh sách các public method/property mà chắc chắn sẽ được thực thi trong lớp của bạn.
Áp vào VD trên, ta có thể tạo ra một interface là IBiometricAuth với một phương thức là BiometricAuth(). Tiếp theo cho các lớp Iphone, Laptop, FingerprintScanner thực thi interface IBiometricAuth này như hình sau
Tính đa hình
Vì mỗi lớp trên đều thực thi interface IBiometricAuth nên ta có thể đảm bào rằng chúng đều có phương thức BiometricAuth() và khai báo của phương thức sẽ giống y như được định nghĩa trên interface IBiometricAuth. Tương tự Kế thừa dựa trên Đa hình, sử dụng Interface cho phép chúng ta khai báo phương thức nhận tham số kiểu IBiometricAuth nhưng chấp nhận bất cứ đối tượng nào truyền vào mà kiểu của nó thực thi interface IBiometricAuth này. Những lớp thực thi IBiometricAuth không cần phải có chung lớp cha ngoại trừ interface IBiometricAuth. Trong phương thức ở trên, ta có thể gọi bất cứ phương thức đã được định nghĩa trên interface IBiometricAuth của đối tượng truyền vào mà không cần quan tâm kiểu thực sự của nó là gì: Không cần quan tâm nó là Iphone, Laptop hay FingerprintScanner, chỉ cần biết nó hỗ trợ interface IBiometricAuth vì thế có thể gọi được phương thức BiometricAuth().
Tóm lại

Trong bài viết này tôi đã diễn giải lại 3 nguyên lý cơ bản của lập trình hướng đối tượng sao cho đơn giản và dễ hiểu nhất. Mặc dù nó rất cơ bản và hầu như ai học về lập trình cũng đã từng được học hoặc đọc nhưng hy vọng bài viết này sẽ mang đến một điều gì đó dễ dàng tiếp cận hơn cho các bạn, nhất là những lập trình viên mới tiếp cận OOP. Hãy chia sẻ phản hồi của bạn về bài viết và share cho bạn bè nếu bạn thấy nó hữu ích nhé. Nếu bạn có câu hỏi gì xin vui lòng comment dưới bài viết để chúng ta cùng thảo luận.

Sharing is learning.

Source Link at here: Object Oriented Programming

Posted By Thang Vuong 21:14