[JAVA_CORE] Object Orientation (Update Polymorphism - 5/8)
Như đã trình bày ở Tổ chức học Core Java
Mình xin bắt đầu với topic đầu tiên: Object Orientation(OO) trong java.
Trước khi bắt đầu mình có 1 vài chú ý nho nhỏ :
- Rất nhiều từ mình sẽ không dịch sang tiếng việt mà giữ nguyên tiếng anh VD: Object, Class, Encapsulation…
Vì sao lại thế chắc các bạn cũng hiểu. - Không thảo luận những vấn đề không liên quan đến topic: VD: đang nói về Object Orientation lại có bạn hỏi về Exception, Thread…
- Những đóng góp ý kiến, các câu hỏi Why, When luôn luôn được chào đón và giúp các bạn hiểu sâu hơn về topic, đừng chỉ dừng lại ở What, How (nó là cái gì, dùng ntn). VD: Thảo luận về OO, nên đặt ra các câu hỏi như: tại sao nên dùng Encapsulation, Inheritance? Khi nào thì dùng nó, khi nào không nên dùng?
OK, Let’s start
Java là một ngôn ngữ Object Orientation, do đó các bạn muốn code java, nhất thiết phải tuân thủ đúng kỹ thuật của một ngôn ngữ OO. Nó bao gồm 4 thành phần:
- Encapsulation(đóng gói)
- Inheritance(Kế thừa)
- Polymorphism(Đa hình)
- Abstraction(Trừu tượng)
Ngoài ra trong topic mình sẽ đề cập đến một số vấn đề liên quan: Overriding, Overloading, Reference variable, Interface, Static, Contructor.
- Encapsulation
Encapsulation là gì? hãy lấy 1 ví dụ:
class BadOO {
public int size;
public int weight;
}
class ExploitBadOO {
public static void main (String [] args) {
BadOO b = new BadOO();
b.size = -5; // Legal but bad!!
}
}
Bạn là một thành viên trong nhóm code một dự án. Bạn code 1 class tên BadOO, một người khác trong team sử dụng class của bạn trong class của họ ExploitBadOO. Bạn sẽ không thể kiểm soát được nếu như cho họ toàn quyền quyết định dùng class của bạn. Trong VD trên, class bạn tạo ra không muốn size < 0, nhưng bạn của bạn rất dẽ dàng làm cho nó < 0:
BadOO b = new BadOO();
b.size = -5; // Legal but bad!!
Câu hỏi đặt ra ở đây là gì, làm thế nào bạn có thể kiểm soát cái mà bạn tạo ra?
Vâng, đó là câu trả lời cho Encapsulation:
Mục đích chính của Encapsulation chính là kiểm soát tất cả những khả năng gây lỗi khi người khác sử dụng code của bạn
Ngoài ra các bạn search sẽ thấy đcmột vài lợi ích khác, nhưng theo mình nó chỉ từ cái mày mà ra.
Vậy sử dụng Encapsulation như thế nào?
- Tất cả các biến trong Class phải được bảo vệ (sử dụng access modifier: protected, private).
- Tạo ra các method để truy suất các biến đó: như vậy muốn sử dụng biến phải thông qua các method này.
- Vớii các method, java khuyến cáo sử dụng theo chuẩn JavaBean chính là các hàm get, set.
Ví dụ trên sau khi áp dụng Encapsulation:
class BadOO {
private int size;
private int weight;
public int getSize() {
return size;
}
public void setSize(int size) {
if (size >= 0) {
this.size = size;
} else {
throw new IllegalArgumentException("Size can not be negative value!");
}
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
class ExploitBadOO {
public static void main (String [] args) {
BadOO b = new BadOO();
b.setSize(-5); // will throw exception
}
}
- Inheritance
Inheritance - Tính Kế thừa có ở mọi nơi trong Java, dù là những chương trình java nhỏ nhất đến các project phức tạp.
Khi bạn tạo ra 1 class, mặc địch nó được kế thừa từ class Object, đó là tại sao class bạn tạo ra luôn có sẵn một số method vd như toString(), equals(), hashcode() …
Tại sao java lại làm như vậy?
Ví dụ hàm equals(), những người tạo ra java chắc chắn rằng method này là vô cùng phổ biến cho những người viết java để so sánh giá trị 2 thực thể (Instances). Giả sử class Object không có hàm equals() thì sao? Khi đó hàng trăm, hàng triệu các lập trình viên sẽ phải thiết kế hàm equals riêng của họ.
Đó là một trong 2 nguyên nhân chính tạo ra Inheritance trong java:
- Code reuse (Tái sử dụng code)
- Để sử dụng cho polymorphism (đa hình)
a. Code Reuse
VD:
class Angle {
public void draw() {
System.out.println("drawing Angle");
}
// more code
}
class Rectangle extends Angle {
public void paint() {
System.out.println("painting Rectangle");
}
// more code
}
class Test {
public static void main (String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.draw();
rectangle.paint();
}
}
Outputs:
drawing Angle
painting Rectangle
Class Rectangle kế thừa Class Angle, do đó nó có thể sử dụng đc Method draw() của Angle.
Tính code reuse ở đây thể hiện rằng method draw() có thể được sử dụng ở bất cứ class nào miễn là nó kế thừa từ Angle.
b. Sử dụng trong Polymorphism
Chi tiết về Polymorphism mình sẽ thảo luân sau ở phần dưới.
Cũng từ ví dụ trên, mình thêm 1 class Triangle:
class Angle {
public void draw() {
System.out.println("drawing Angle");
}
// more code
}
class Rectangle extends Angle {
public void paint() {
System.out.println("painting Rectangle");
}
// more code
}
class Triangle extends Angle {
public void doXXX() {
System.out.println("do XXX");
}
// more code
}
Và 1 class test:
class Test {
public static void main (String[] args) {
Rectangle rectangle = new Rectangle();
Triangle triangle = new Triangle();
drawAngle(rectangle);
drawAngle(triangle);
}
public static void drawAngle(Angle angle) {
angle.draw();
}
}
Output:
drawing Angle
drawing Angle
Các ban thấy, cả 2 Ractangle và Triangle đều có thể là param truyền vào method drawAngle() được định nghĩa là kiểu Angle.
Ý nghĩa: method drawAngle(Angle angle) không quan tâm chính xác kiểu của parameter(đối số) truyền vào là gì, miễn là nó là một Angle, bởi vì nó chỉ cần sử dụng chức năng draw() của Angle.
1 số topic liên quan đến Inheritance:
- http://daynhauhoc.com/t/java-question-can-be-multi-inheritance.
3 Polymorphism
Tính polymorphism (đa hình) là gì? Hiểu đơn giản nó nghĩa là “nhiều hình thức” thể hiện.
Cùng một hành động có thể có ý nghĩa khác nhau với những đối tượng khác nhau.
Ví dụ: hành động eat(ăn) là khác nhau đối với cat(con mèo), hay dog(con chó).
Polymorphism trong java cũng có thể hiểu như vậy, cụ thể hơn, polymorphism trong java bao gồm:
- Polymorphism với class (chính là kết hợp với inheritance ở phần trên).
- Polymorphism với method (chính là khái niệm Method Override)
A. Polymorphism với class:
Đây chính là phần kết hợp với Inheritance mà mình đã nói trước đó.
Mình cho thêm 1 ví dụ khác:
class Developer {
public void code() {
System.out.println("Developer: Coding...");
}
}
class JavaDeveloper extends Developer {
public void code() {
System.out.println("JavaDeveloper: Coding...");
}
}
class PHPDeveloper extends Developer {
public void code() {
System.out.println("PHPDeveloper: Coding...");
}
}
public class TestClass {
public static void main(String[] args) {
Developer dev = new Developer();
Developer javaDev = new JavaDeveloper();
Developer phpDev = new PHPDeveloper();
writeCode(dev);
writeCode(javaDev);
writeCode(phpDev);
}
public static void writeCode(Developer dev) {
dev.code();
}
}
Output:
Developer: Coding...
JavaDeveloper: Coding...
PHPDeveloper: Coding...
Polymorphism ở đây là gì? đối số kiểu Developer truyền vào method writeCode() có thể có nhiều hình thức, lúc thì là JavaDeveloper, lúc thì là PHPDeveloper, thậm chí cũng có thể là Developer.
Bây giờ, mình muốn phân tích sâu hơn về đoạn code này, các bạn sẽ hiểu Java làm gì với chúng:
public static void main(String[] args) {
Developer dev = new Developer();
Developer javaDev = new JavaDeveloper();
Developer phpDev = new PHPDeveloper();
writeCode(dev);
writeCode(javaDev);
writeCode(phpDev);
}
- Trước hết là khái niệm Reference Variable.
Reference Variable: Biến Reference là gì?
Biến Reference là biến lưu trữ giá trị địa chỉ ô nhớ của Object mà nó trỏ đến.
Một biến Reference được định nghĩa 1 lần duy nhất, kiểu của nó(type) là cố định và không bao giờ thay đổi.
Ví dụ:
Developer dev;
Tạo ra một biến Reference tên là dev, kiểu của nó là Developer.
Khi khai báo như trên, giá trị của biến dev chưa được gán giá trị nào cả.
Nếu ta thêm 1 đoạn code:
dev = new Developer();
Bản chất của dòng lệnh này bao gồm 2 phần:
Phần 1: Tạo ra 1 Instance của Developer và các thuộc tính của nó thông qua hàm Constructor và lưu trữ Instance vào bộ nhớ.
Phần 2: Địa chỉ của ô nhớ lưu trữ Instance này được gán cho biến Reference(dev) bằng lệnh gán (dev =); Ta gọi "dev trỏ tới Developer Object"
Ta có thể thay thế 2 lệnh trên bằng 1 lệnh duy nhất: Developer javaDev = new JavaDeveloper();, bản chất chúng không có gì khác. Mình chỉ tách chúng ra để cho các bạn dễ hiểu hơn.
Tương tự như vậy:
Developer javaDev = new JavaDeveloper();
Tạo ra 1 Biến Reference là javaDev, 1 Instance của JavaDeveloper, và javaDev trỏ đến JavaDeveloper Object;
Developer phpDev = new PHPDeveloper();
Tạo ra 1 Biến Reference là phpDev, 1 Instance của PHPDeveloper, và phpDev trỏ đến PHPDeveloper Object;
Bây h ta phân tích tiếp hàm:
public static void writeCode(Developer dev) {
dev.code();
}
Đối số truyền vào là 1 biến Reference có kiểu là Developer, đó là tại sao cả 3 biến dev, javDev, phpDev đều có thể truyền được vào hàm vì chúng đều có kiểu Developer.
Vậy lệnh dev.code(); làm gì?
Lệnh này sẽ chạy method code() của Object mà biến Reference trỏ đến, như vậy với mỗi biến truyền vào, Object của nó là khác nhau và nó sẽ chạy khác nhau. Đó là vì sao in ra 3 dòng:
Developer: Coding...
JavaDeveloper: Coding...
PHPDeveloper: Coding...
updated 11h 3/8…
Giả sử ta thêm 1 biến Reference nữa và gán giá trị của nó cho javaDev:
code:
Developer javaDev = new JavaDeveloper();
Developer newDev = javaDev;
Java sẽ làm gì với lệnh Developer newDev = javaDev; Nếu các bạn đã hiểu rõ biến Reference là gì chắc cũng đoán ra.
Câu lệnh trên sẽ tạo ra 1 biến Reference mới newDev, và giá trị của nó được gán bằng giá trị của javaDev.
Như vậy, chúng ta có 2 biến Reference cùng trỏ đến 1 JavaDeveloper Object.
Nếu ta so sánh bằng biểu thức (javaDev == newDev) sẽ trả về true. Giờ chắc các bạn hiểu tại sao == dùng để so sánh Reference .
Ta lại thêm 1 đoạn code để gán lại giá trị của javaDev:
Developer javaDev = new JavaDeveloper();
Developer newDev = javaDev;
javaDev = new JavaDeveloper();
Sau câu lệnh thứ 3: Ta có thêm 1 JavaDeveloper Object mới, và biến javaDev giờ sẽ trỏ đến Object mới này.
Như vậy ta đang có javaDev trỏ đến Object mới, newDev vẫn trỏ đến Object cũ, giữa javaDev và newDev không hề có mối quan hệ nào nào cả.
Biểu thức (javaDev == newDev) lúc này sẽ trả về false vì 2 biến Reference giờ trỏ đến 2 Object khác nhau.
Còn 2 phần nữa khá hay về Reference Copy, và Binding variable, method mình sẽ viết sau
1 Số topic liên quan đến Polymorphism:
http://daynhauhoc.com/t/java-question-reference-variable-type
Update 2h 5/8
B. Polymorphism với method
Tính Polymorphism với method chính là khái niệm Overriding Method trong java.
Quay lại ví dụ trên, method code() đã được JavaDeveloper và PHPDeveloper override lại.
Khi đó lời gọi hàm dev.code() sẽ phụ thuộc vào Object mà biến Reference trỏ đến mà chạy hàm thích hợp.
Chắc khái niệm Override ai cũng quen thuộc rồi nên mình sẽ không nói nhiều ở đây. Thay vào đó mình sẽ nói đến vấn đề Reference Copy khá hay.
Reference Copy
Các bạn xem đoạn code:
class Developer {
private String name;
public Developer(String name) {
this.name = name;
}
public void showMyName() {
System.out.println("My name: " + this.name);
}
public void setName(String name) {
this.name = name;
}
}
public class TestClass {
public static void main(String[] args) {
Developer dev = new Developer("John");
dev.showMyName(); // show name John
changeDev(dev);
dev.showMyName(); // what's name?
}
public static void changeDev(Developer dev) {
dev = new Developer("Peter");
}
}
Theo mọi người output sẽ là gì?
CÓ người sẽ đoán là:
My name: John
My name: Peter
Không, rất tiệc kết quả là:
My name: John
My name: John
Tại sao lại như vậy? đây chính là khái niêm Reference copy.
Tất cá các Reference của bất cứ 1 Object nào truyền vào 1 method sẽ tạo ra 1 bản copy của Reference đó. Method sẽ sử dụng Reference copy chứ không dùng Reference gốc.
Ta sẽ phân tích lại đoạn code:
public static void changeDev(Developer dev) {
dev = new Developer("Peter");
}
- Biến dev được truyền vào ở lời gọi hàm changeDev(dev); là 1 biến Reference, nó đang trỏ đến một Developer Object với name = “John”.
- Sau lời gọi method changeDev(dev), tạo ra 1 biến Reference mới cùng trỏ đến Developer Object trên. Sau đó sẽ truyền vào method.
- Lệnh dev = new Developer(“Peter”); trong method sẽ tạo ra 1 Developer Object mới với name = “Peter”, biến Reference copy sẽ trỏ đến Object mới này.
- Kết thúc hàm, biến Reference copy sẽ được giải phóng (java có cơ chế GC sẽ tự giải phóng những biến này)
- Biến Reference dev vẫn đang trỏ đến Developer Object cũ và in ra với tên là “John”,
Bây giờ, ta lại sửa 1 chút hàm changeDev thành:
public static void changeDev(Developer dev) {
dev.setName("Peter");
}
Điều gì sẽ xảy ra?
Output:
My name: John
My name: Peter
Lúc này biến Reference copy đang trỏ vào Developer Object với name = “John”.
lời gọi hàm setName(“Peter”); sẽ gọi đến hàm của Object mà biến Reference copy trỏ đến, tức là set lại giá trị name của Developer Object = “Peter”.
Ra khỏi hàm, biến Reference copy cũng bị giải phóng, nhưng Object nó trỏ đến đã bị thay đổi. Đó là tại sao in ra “My name: Peter”
…updating…
Các bạn có thể thảo luận và đặt câu hỏi về Encapsulation ở đây
Sử dụng khai báo const đúng cách cũng là một cách để tránh bản thân phá “Encapsulation” nhỉ
Const ý nghĩa của nó khác chứ vì bạn sẽ không thay đổi được biến Const.
Mình cũng đang học java . Cái ví dụ của bạn quên sửa lại access modifier của biến kìa.
Ok cám ơn bác nhiều
Mình update thêm Inheritance…
không nên sử dụng get and set method
Why getter and setter methods are evil
The getter/setter idiom is a commonplace feature in many Java programs. Most of these accessor methods, however, are unnecessary and can severely impact your systems' maintainability. Using accessors violates the basic object-oriented (OO) principle...
Bạn nên giải thích luôn là tại sao chứ đưa 1 cái link bài viết dài vậy sẽ có nhiều người không đọc.
Mình không đồng ý với kết luận “không nên sử dụng get and set method”, nên hay không tùy thuộc vào từng mục đích cụ thể. Tất nhiên mình cũng không nói là phải dùng Get, Set. Đó chỉ là 1 cách để áp dụng Bean pattern, cái được java hỗ trợ rất nhiều.
sorry làm biếng không biết tóm tắt sao mặc dù đọc thấy có lý nhưng vẫn còn chưa hiểu nhiều nên đưa link lên lun
còn về java bean thì trong bài viết cũng có đề cập đén việc này
JavaBeans
You might object by saying, “But what about JavaBeans?” What about them? You can certainly build JavaBeans without getters and setters. The BeanCustomizer, BeanInfo, and BeanDescriptor classes all exist for exactly this purpose. The JavaBean spec designers threw the getter/setter idiom into the picture because they thought it would be an easy way to quickly make a bean—something you can do while you’re learning how to do it right. Unfortunately, nobody did that.
Bạn nói đúng, ý định của bài viết là nói về design OO. 1 OO design tốt nhất là sử dụng OO 1 cách tốt nhất, và mọi thứ đều có cái lợi cái hại việc có quá nhiều get và set method sẽ không thể dễ dàng matain, và đôi lúc khi chúng ta không còn sự lựa chọn nào khác thì mới dùng get và set
cho e hỏi trong method drawAngle ngoài phương thức angle.draw() có thêm nhiều chức năng khác v khi sử dụng nó thì tất cả chức năng trong drawAngle đều chạy hết hay sao ạ còn nếu chỉ muốn sử dụng 1 chức năng trong các chức năng đó thì sao ? e mới tập học java có gì đặt câu hỏi sai , a bỏ qua
Hi @cuong_hieu:
Đừng ngại đặt đâu hỏi, không ai đánh giá e ở đây cả
A hiểu câu hỏi của e, nhưng e đang nói sai 1 số từ:
Còn đây câu trả lời cho câu hỏi của e:
Khi e gọi drawAngle(rectangle); thì tất cả các dòng lênh trong method drawAngle() sẽ chạy hết tuần tự từ trên xuống dưới. Trừ khi e đặt 1 vài lênh đặc biệt để dừng lại hoặc bỏ qua. VD: return, if else…
thanks anh em code thử òi đúng là ra như vậy
OUTPUT
good job @cuong_hieu.
Cơ mà Angle ở đây nghĩa là góc nhé, không phai là Angel (Thiên thần) như e nghĩ =))).
A lại thấy có God voi Devil ở đây buồn cười quá
@duc123 tại sao lại không nên dùng set get vậy ạ? Em xem series của StudyandShare thì lại được khuyên là nên dùng, và tuyệt đối không nên truy cập trực tiếp
cuối bài nên tóm tắt sơ lược lại, vì ở trên kèm thêm ví dụ nên lý thuyết hơi rời rạc. (lười)
Chắc bạn không hiểu mong muốn của mình. Mình chỉ đưa ra các ý chính quan trọng, sau đó mọi người nên thảo luận va đặt ra các câu hỏi.
Còn mình tin là các bạn cũng không muốn đây là một bài giảng, nếu thế thì trên mạng đã có quá nhiều rồi.
Vd như không có từ khóa extends thì ra phải gọi đối tượng ra mới lấy dc giữ iệu từ class cha
Đúng dùng get và set là tránh truy cập trực tiếp, nhưng get và set lại không che giấu hết thông tin của lớp,(đây là ví dụ trong bài viết) giả sử bạn có 1 lớp đi và trong đó có 1 biến x kiểu int và bạn có phương thức getx thì phương thức đó phải trả về kiểu int, sau này lớp đó được cải tiến và bạn thấy rằng biến x đó kiểu int không còn phù hợp nữa bạn muốn đổi nó sang long chẳng hạn thì như vậy bạn sẽ phải thay đổi cả getx để nó trả về long, làm vậy sẽ ảnh hưởng tới tất cả những ứng dụng hoặc lớp khác sử dụng lớp của bạn, vì thế nên nó chưa được hoàn hảo cho lắm
bảo là đang nói về OO thì không nên thảo luận về Exception,… bạn code lại có Exception có việc gì không?
Mình sẽ rút kn tránh cho những lý luyết khác vào . Còn mọi người ignore nó là đc mà