Lớp trừu tượng là lớp được khai báo với từ khóa abstract
và có thể có hoặc không có các phương thức trừu tượng. Lớp trừu tượng không được dùng để tạo ra các đối tượng mà chỉ có thể được kế thừa. Ví dụ:
public abstract class Number() { }
Phương thức trừu tượng là các phương thức chỉ có phần tên chứ không có phần thân, phía sau tên phương thức là dấu chấm phẩy, ví dụ:
abstract double add(double a, double b);
Nếu một lớp có chứa bất kì một phương thức trừu tượng nào thì lớp đó phải là lớp trừu tượng.
public abstract class Number { abstract double add(double a, double b); }
Khi một lớp trừu tượng được kế thừa thì các lớp con luôn luôn phải code lại tất cả các phương thức trừu tượng của lớp cha, nếu không thì lớp con cũng phải khai báo là trừu tượng – abstract
.
Lưu ý là tính năng này cũng khá giống với interface, bởi vì trong interface thì một phương thức cũng không có phần thân, và các lớp implement lại interface này cũng phải code lại các phương thức đó.
So sánh giữa lớp trừu tượng và interface
Lớp trừu tượng và interface khá giống nhau ở chỗ là chúng ta không thể tạo ra đối tượng từ chúng, và mỗi loại đều có thể khai báo phương thức vừa có phần thân và vừa không có phần thân. Điểm khác nhau là trong phương thức trừu tượng thì không thể khai báo các thuộc tính static
hoặc final
, có thể định nghĩa các phương thức public
, protected
và private
. Đối với interface thì mặc định tất cả các thuộc tính đều là public static final
, tất cả các phương thức được định nghĩa đều là public
. Một điểm khác nữa là chúng ta chỉ có thể thừa kế 1 lớp (cho dù là có trừu tượng hay không), nhưng lại có thể implement vô số interface.
Chúng ta nên dùng lớp trừu tượng khi:
- Các lớp mà chúng ta sẽ định nghĩa sẽ gần như giống hoàn toàn với nhau và với lớp cha – tức là ít thay đổi
- Các lớp con sẽ chủ yếu dùng các phương thức và thuộc tính như nhau, hoặc sử dụng phạm vi hoạt động khác với
public
. - Chúng ta muốn khai báo các trường không phải
static
vàfinal
.
Nên dùng interface khi:
- Các lớp mà chúng ta sẽ định nghĩa không liên quan đến nhau.
- Chúng ta định nghĩa các phương thức nhưng không quan tâm đến lớp nào sẽ định nghĩa phương thức đó.
- Chúng ta muốn thực hiện đa thừa kế.
Cũng có trường hợp rất nhiều thư viện sử dụng cả lớp trừu tượng và interface.
Ví dụ
Giả sử chúng ta muốn viết một chương trình vẽ các hình như hình tròn, hình chữ nhật, đường thẳng, đường cong…v.v Các đối tượng này thường có chung một số trạng thái như vị trí, hướng, màu, nền…v.v và các hành động như di chuyển, xoay, thay đổi kích thước, vẽ…v.v Một trong số các hành động sẽ khác nhau tùy thuộc vào từng hình, chẳng hạn như vẽ – vẽ hình tròn sẽ khác với vẽ hình chữ nhật. Chúng ta sẽ định nghĩa lớp có tên như GraphicObject
là trừu tượng, và các lớp cụ thể hơn kế thừa lại lớp này như Rectangle
(hình chữ nhật), Circle
(hình tròn), Curve
(đường cong), Line
(đường thẳng). Lớp GraphicObject
sẽ lưu các thuộc tính dùng chung và các lớp con sẽ kế thừa lại, các lớp con cũng sẽ override lại các phương thức để dùng cho riêng chúng.
Ví dụ chúng ta khai báo lớp trừu tượng GraphicObject
như sau:
abstract class GraphicObject { int x, y; void moveTo(int newX, int newY) { this.x = newX; this.y = newY; } abstract void draw(); }
Lớp GraphicObject
có 2 thuộc tính là x
và y
. Một phương thức thường là moveTo()
và một phương thức trừu tượng là draw()
.
Tiếp theo chúng ta khai báo các lớp không trừu tượng là Circle
và Rectangle
kế thừa lại lớp GraphicObject
và các lớp này sẽ phải code lại phương thức draw()
.
class Circle extends GraphicObject { void draw() { // ... } } class Rectangle extends GraphicObject { void draw() { // ... } }
Lớp trừu tượng implement giao diện
Trong bài interface chúng ta đã biết là tất cả các lớp implement một interface thì phải code lại toàn bộ các phương thức trong interface đó. Tuy nhiên, một lớp có khai báo abstract
– trừu tượng mà có implement một interface thì không cần phải code lại toàn bộ các phương thức trong interface đó
abstract class X implements Y { // Có thể không cần code một phương thức nào của interface Y }