5. Abstract Classes & Interfaces
5.1 The abstract Method and abstract class
Trong các ví dụ trên lớp Shape
và Monster
, chúng tôi đã gặp sự cố khi tạo phiên bản Shape
và Monster
và chạy getArea()
hoặc attack()
. Điều này có thể được giải quyết thông qua abstract
method và abstract
class.
Một phương thức abstract
là một phương thức chỉ có phần khai báo nguyên mẫu hàm(ví dụ, tên phương thức, danh sách các đối số và kiểu trả về) mà không có phần thực hiện (ví dụ, phần thân của phương thức). Bạn sử dụng từ khóa abstract
để khai báo một phương thức abstract
.
Ví dụ, trong class Shape
, chúng ta có thể khai báo các abstract
phương pháp getArea()
, draw()
, vv, như sau:
abstract public class Shape {
......
......
abstract public double getArea();
abstract public double getPerimeter();
abstract public void draw();
}
Việc thực hiện các phương thức này là KHÔNG thể trong lớp Shape
, vì hình dạng thực tế chưa được biết đến. (Làm thế nào để tính diện tích nếu hình dạng không được biết?) Việc thực hiện các abstract
method này sẽ được cung cấp sau khi hình dạng thực tế được biết đến. Các abstract
phương thức này không thể được gọi vì chúng không có triển khai.
Một lớp chứa một hoặc nhiều abstract
phương thức được gọi là một abstract
lớp. Một abstract
lớp phải được khai báo với một bộ sửa đổi lớp abstract
. Một abstract
lớp KHÔNG THỂ được khởi tạo, vì định nghĩa của nó không đầy đủ.
Ký hiệu UML : abstract
lớp và phương thức được in nghiêng
5.2 Abstract Class EG. 1: Shape and its Subclasses
Chúng ta hãy viết lại Shape
lớp của chúng ta dưới dạng một abstract
lớp, chứa một abstract
phương thức getArea()
như sau:
The abstract Superclass Shape.java
abstract public class Shape {
private String color;
public Shape (String color) {
this.color = color;
}
@Override
public String toString() {
return "Shape[color=" + color + "]";
}
abstract public double getArea();
}
Một abstract
lớp không đầy đủ trong định nghĩa của nó, vì việc thực hiện các abstract
phương thức của nó bị thiếu. Do đó, một abstract
lớp không thể được khởi tạo . Nói cách khác, bạn không thể tạo các thể hiện từ một abstract
lớp (nếu không, bạn sẽ có một thể hiện không hoàn chỉnh với phần thân của phương thức bị thiếu).
Để sử dụng một abstract
lớp, bạn phải lấy một lớp con từ abstract
lớp đó. Trong lớp con dẫn xuất, bạn phải ghi đè các abstract
phương thức và cung cấp triển khai cho tất cả các abstract
phương thức. Lớp con dẫn xuất hiện đã hoàn tất và có thể được khởi tạo. (Nếu một lớp con không cung cấp triển khai cho tất cả các abstract
phương thức của lớp cha, lớp con vẫn còn abstract
.)
Tài sản này của abstract
lớp giải quyết vấn đề trước đó của chúng tôi. Nói cách khác, bạn có thể tạo các thể hiện của lớp con như Triangle
và Rectangle
, và sự liệng lên họ Shape
(để chương trình và hoạt động ở cấp độ giao diện), nhưng bạn không thể tạo thể hiện của Shape
, mà tránh được những cạm bẫy mà chúng tôi đã phải đối mặt. Ví dụ,
public class TestShape {
public static void main(String[] args) {
Shape s1 = new Rectangle("red", 4, 5);
System.out.println(s1);
System.out.println("Area is " + s1.getArea());
Shape s2 = new Triangle("blue", 4, 5);
System.out.println(s2);
System.out.println("Area is " + s2.getArea());
Shape s3 = new Shape("green");
}
}
Tóm lại, một abstract
lớp cung cấp một khuôn mẫu để phát triển hơn nữa . Mục đích của một lớp trừu tượng là cung cấp một giao diện chung (hoặc giao thức, hoặc hợp đồng, hoặc sự hiểu biết, hoặc quy ước đặt tên) cho tất cả các lớp con của nó. Ví dụ, trong abstract
lớp Shape
, bạn có thể định nghĩa các phương thức trừu tượng như getArea()
và draw()
. Không thể thực hiện được vì hình dạng thực tế không được biết đến. Tuy nhiên, bằng cách chỉ định chữ ký của các abstract
phương thức, tất cả các lớp con buộc phải sử dụng chữ ký của các phương thức này. Các lớp con có thể cung cấp các triển khai thích hợp.
Kết hợp với đa hình, bạn có thể upcast các thể hiện của lớp con Shape
và chương trình ở Shape
cấp độ, i, e., Tại giao diện. Việc tách giao diện và triển khai cho phép thiết kế phần mềm tốt hơn và dễ dàng mở rộng. Ví dụ, Shape
định nghĩa một phương thức được gọi getArea()
, mà tất cả các lớp con phải cung cấp việc thực hiện đúng. Bạn có thể yêu cầu một getArea()
từ bất kỳ lớp con nào của Shape, khu vực chính xác sẽ được tính toán. Hơn nữa, ứng dụng của bạn có thể được mở rộng dễ dàng để phù hợp với các hình dạng mới (chẳng hạn như Circle
hoặc Square
) bằng cách lấy thêm các lớp con.
Rule of Thumb: Chương trình tại giao diện, không phải lúc thực hiện. (Nghĩa là tạo các tham chiếu tại lớp cha; thay thế bằng các thể hiện của lớp con; và gọi các phương thức được xác định chỉ trong lớp cha.)
Ghi chú:
- Một phương thức trừu tượng không thể được khai báo
final
, vì final
phương thức không thể bị ghi đè. abstract
Mặt khác, một phương thức phải được ghi đè trong một hậu duệ trước khi nó có thể được sử dụng.
- Một
abstract
phương thức không thể private
(tạo ra lỗi biên dịch). Điều này là do private
phương thức không thể nhìn thấy đối với lớp con và do đó không thể bị ghi đè.
5.3 Abstract Class EG. 2: Monster
Chúng ta sẽ định nghĩa siêu lớp Monster
là một abstract
lớp, chứa một abstract
phương thức attack()
. Các abstract
lớp học không thể được khởi tạo (ví dụ, tạo ra các trường hợp).
abstract public class Monster {
private String name;
public Monster(String name) {
this.name = name;
}
abstract public String attack();
}
5.4 The Java's interface
Java interface
là một siêu lớp trừu tượng 100% , định nghĩa một tập hợp các phương thức mà các lớp con của nó phải hỗ trợ. An interface
chỉ chứa public
các phương thức trừu tượng (phương thức có chữ ký và không triển khai) và có thể là hằng số ( public
static
final
biến). Bạn phải sử dụng từ khóa " interface
" để xác định từ khóa để xác địnhinterface
(thay vì từ khóa " class
" cho các lớp thông thường). Các từ khóa public
và abstract
không cần thiết cho các phương thức trừu tượng của nó vì chúng là bắt buộc.
(JDK 8 giới thiệu các phương thức mặc định và tĩnh trong giao diện. JDK 9 giới thiệu các phương thức riêng tư trong giao diện. Chúng sẽ không được đề cập trong bài viết này.)
Tương tự như một abstract
siêu lớp, interface
không thể khởi tạo được. Bạn phải tạo một "lớp con" thực hiện giao diện và cung cấp triển khai thực tế của tất cả các abstract
phương thức.
Không giống như một lớp bình thường, nơi bạn sử dụng từ khóa " extends
" để lấy ra một lớp con. Đối với interface, chúng tôi sử dụng từ khóa " implements
" để thực thi ở một lớp con.
Một interface là một hợp đồng cho những gì các lớp có thể làm. Tuy nhiên, nó không chỉ định cách các lớp nên làm điều đó.
Một interface cung cấp một biểu mẫu , một giao thức , một tiêu chuẩn , một hợp đồng , một đặc tả , một bộ quy tắc , một giao diện , cho tất cả các đối tượng thực hiện nó. Đó là một đặc điểm kỹ thuật và quy tắc mà bất kỳ đối tượng thực hiện nó đồng ý tuân theo.
Trong Java, abstract
lớp và interface
được sử dụng để tách giao diện chung của một lớp khỏi cách triển khai của nó để cho phép lập trình viên lập trình tại giao diện thay vì thực hiện khác nhau .
Quy ước đặt tên giao diện: Sử dụng một tính từ (thường kết thúc bằng " able
") bao gồm một hoặc nhiều từ. Mỗi từ sẽ được viết hoa ban đầu (trường hợp lạc đà). Ví dụ, Serializable
, Extenalizable
, Movable
, Clonable
, Runnable
,, vv
5.5 Interface EG. 1: Shape Interface and its Implementations
Chúng ta có thể viết lại abstract
siêu lớp Shape
thành một interface
, chỉ chứa abstract
các phương thức, như sau:
Ký hiệu UML : Các lớp trừu tượng, Giao diện và các phương thức trừu tượng được hiển thị bằng chữ in nghiêng. Việc thực hiện giao diện được đánh dấu bằng một mũi tên gạch ngang dẫn từ các lớp con đến giao diện.
public interface Shape {
double getArea();
}
public class Rectangle implements Shape {
private int length, width;
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
@Override
public String toString() {
return "Rectangle[length=" + length + ",width=" + width + "]";
}
@Override
public double getArea() {
return length * width;
}
}
public class Triangle implements Shape {
private int base, height;
public Triangle(int base, int height) {
this.base = base;
this.height = height;
}
@Override
public String toString() {
return "Triangle[base=" + base + ",height=" + height + "]";
}
@Override
public double getArea() {
return 0.5 * base * height;
}
}
Một trình điều khiển thử nghiệm như sau:
public class TestShape {
public static void main(String[] args) {
Shape s1 = new Rectangle(1, 2);
System.out.println(s1);
System.out.println("Area is " + s1.getArea());
Shape s2 = new Triangle(3, 4);
System.out.println(s2);
System.out.println("Area is " + s2.getArea());
}
}
5.6 Interface EG. 2: Movable Interface and its Implementations
Giả sử rằng ứng dụng của chúng tôi liên quan đến nhiều đối tượng có thể di chuyển. Chúng ta có thể định nghĩa một giao diện được gọi movable
, chứa chữ ký của các phương thức di chuyển khác nhau.
Interface Moveable.java
public interface Movable {
public void moveUp();
public void moveDown();
public void moveLeft();
public void moveRight();
}
Tương tự như một abstract
lớp, interface
không thể được khởi tạo; bởi vì nó không đầy đủ (cơ thể của các phương thức trừu tượng bị thiếu). Để sử dụng một giao diện, một lần nữa, bạn phải rút ra các lớp con và cung cấp triển khai cho tất cả các phương thức trừu tượng được khai báo trong giao diện. Các lớp con hiện đã hoàn thành và có thể được khởi tạo.
MovablePoint.java
Để lấy được các lớp con từ một interface
, một bàn phím mới " implements
" sẽ được sử dụng thay vì " extends
" để lấy các lớp con từ một lớp bình thường hoặc một lớp hoặc mộtabstract
lớp. Điều quan trọng cần lưu ý là lớp con thực hiện giao diện cần ghi đè TẤT CẢ các phương thức trừu tượng được xác định trong giao diện; mặt khác, lớp con không thể được biên dịch. Ví dụ,
public class MovablePoint implements Movable {
private int x, y;
public MovablePoint(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
@Override
public void moveUp() {
y--;
}
@Override
public void moveDown() {
y++;
}
@Override
public void moveLeft() {
x--;
}
@Override
public void moveRight() {
x++;
}
}
Các lớp khác trong ứng dụng có thể thực hiện tương tự Movable
giao diện và cung cấp triển khai riêng của chúng cho các abstract
phương thức được xác định trong giao diện Movable
.
TestMovable.java
Chúng ta cũng có thể upcast các thể hiện của lớp con vào Movable
giao diện, thông qua đa hình, tương tự như một abstract
lớp.
public class TestMovable {
public static void main(String[] args) {
MovablePoint p1 = new MovablePoint(1, 2);
System.out.println(p1);
p1.moveDown();
System.out.println(p1);
p1.moveRight();
System.out.println(p1);
Movable p2 = new MovablePoint(3, 4);
p2.moveUp();
System.out.println(p2);
MovablePoint p3 = (MovablePoint)p2;
System.out.println(p3);
}
}
5.7 Implementing Multiple Interfaces
Như đã đề cập, Java chỉ hỗ trợ kế thừa duy nhất . Đó là, một lớp con có thể được bắt nguồn từ một và chỉ một siêu lớp. Java không hỗ trợ nhiều kế thừa để tránh kế thừa các thuộc tính xung đột từ nhiều siêu lớp. Nhiều kế thừa, tuy nhiên, có vị trí của nó trong lập trình.
Tuy nhiên, một lớp con có thể thực hiện nhiều giao diện. Điều này được cho phép trong Java vì một giao diện chỉ định nghĩa các phương thức trừu tượng mà không có các triển khai thực tế và ít có khả năng dẫn đến việc kế thừa các thuộc tính xung đột từ nhiều giao diện. Nói cách khác, Java gián tiếp hỗ trợ nhiều kế thừa thông qua việc thực hiện nhiều giao diện. Ví dụ,
public class Circle extends Shape implements Movable, Adjustable {
.......
}
5.8 interface Formal Syntax
Cú pháp để khai báo interface là:
[public|package] interface interfaceName
[extends superInterfaceName] {
static final ...;
...
}
Tất cả các phương thức trong một giao diện (interface) sẽ là public
và abstract
(mặc định). Bạn không thể sử dụng công cụ sửa đổi truy cập khác như private
, protected
và mặc định hoặc công cụ sửa đổi, chẳng hạn như static
, final
.
Tất cả các trường sẽ là public
, static
và final
(mặc định).
An interface
có thể " extends
" từ một siêu giao diện.
Try: Bạn hãy thử nghịch các access modifiers với interface để hiểu thêm về các trường hợp :)
Ký hiệu UML: Ký hiệu UML sử dụng mũi tên đường thẳng liên kết lớp con với siêu lớp cụ thể hoặc trừu tượng và mũi tên nét đứt với giao diện như minh họa. Lớp trừu tượng và phương pháp trừu tượng được thể hiện bằng chữ in nghiêng.
5.9 Why interfaces?
Giao diện là một hợp đồng (hoặc một giao thức, hoặc một sự hiểu biết chung) về những gì các lớp có thể làm. Khi một lớp thực hiện một giao diện nhất định, nó hứa sẽ cung cấp triển khai cho tất cả các phương thức trừu tượng được khai báo trong giao diện. Giao diện xác định một tập hợp các hành vi phổ biến. Các lớp thực hiện giao diện đồng ý với các hành vi này và cung cấp việc thực hiện riêng của chúng đối với các hành vi. Điều này cho phép bạn lập trình tại giao diện, thay vì thực hiện thực tế. Một trong những cách sử dụng chính của giao diện là cung cấp hợp đồng liên lạcgiữa hai đối tượng. Nếu bạn biết một lớp thực hiện một giao diện, thì bạn biết rằng lớp đó chứa các triển khai cụ thể của các phương thức được khai báo trong giao diện đó và bạn được đảm bảo có thể gọi các phương thức này một cách an toàn. Nói cách khác, hai đối tượng có thể giao tiếp dựa trên hợp đồng được xác định trong giao diện, thay vì thực hiện cụ thể của chúng.
Thứ hai, Java không hỗ trợ nhiều kế thừa (trong khi C ++ thì có). Nhiều kế thừa cho phép bạn lấy được một lớp con từ nhiều hơn một siêu lớp trực tiếp. Điều này đặt ra một vấn đề nếu hai siêu lớp trực tiếp có triển khai xung đột. (Cái nào cần theo dõi trong lớp con?). Tuy nhiên, nhiều thừa kế có vị trí của nó. Java thực hiện điều này bằng cách cho phép bạn "thực hiện" nhiều giao diện (nhưng bạn chỉ có thể "mở rộng" từ một siêu lớp đơn lẻ). Vì các giao diện chỉ chứa các phương thức trừu tượng mà không thực hiện thực tế, không có xung đột có thể phát sinh giữa nhiều giao diện. (Giao diện có thể chứa các hằng số nhưng không được khuyến nghị. Nếu một lớp con thực hiện hai giao diện với các hằng số xung đột, trình biên dịch sẽ đánh dấu lỗi biên dịch.)
5.10 Interface vs. Abstract Superclass
Đó là một thiết kế tốt hơn: giao diện hoặc siêu lớp trừu tượng? Không có câu trả lời rõ ràng.
Sử dụng siêu lớp trừu tượng nếu có một hệ thống phân cấp lớp rõ ràng. Lớp trừu tượng có thể chứa triển khai một phần (như các biến thể hiện và các phương thức). Giao diện không thể chứa bất kỳ triển khai nào, mà chỉ xác định các hành vi.
Ví dụ, luồng của Java có thể được xây dựng bằng giao diện Runnable
hoặc siêu lớp Thread
.
5.11 Exercises
LINK TO EXERCISES ON POLYMORPHISM, ABSTRACT CLASSES AND INTERFACES
5.12 (Advanced) Dynamic Binding or Late Binding
Chúng ta thường coi một đối tượng không phải là kiểu riêng của nó, mà là kiểu cơ sở của nó (siêu lớp hoặc giao diện). Điều này cho phép bạn viết mã không phụ thuộc vào loại triển khai cụ thể. Trong Shape
ví dụ này, chúng ta luôn có thể sử dụng getArea()
và không phải lo lắng liệu chúng là hình tam giác hay hình tròn.
Điều này, tuy nhiên, đặt ra một vấn đề mới. Trình biên dịch không thể biết chính xác tại thời điểm biên dịch đoạn mã nào sẽ được thực thi trong thời gian chạy (ví dụ: getArea()
có cách triển khai khác nhau cho Rectangle
và Triangle
).
Trong ngôn ngữ thủ tục như C, trình biên dịch tạo một cuộc gọi đến một tên hàm cụ thể và trình soạn thảo liên kết giải quyết cuộc gọi này đến địa chỉ tuyệt đối của mã sẽ được thực thi trong thời gian chạy. Cơ chế này được gọi là ràng buộc tĩnh ràng buộc (hoặc đầu ràng buộc ).
Để hỗ trợ đa hình, ngôn ngữ hướng đối tượng sử dụng một cơ chế khác nhau được gọi là năng động ràng buộc (hoặc late-binding ràng buộc hoặc thời gian chạy ràng buộc ). Khi một phương thức được gọi, mã được thực thi chỉ được xác định tại thời gian chạy. Trong quá trình biên dịch, trình biên dịch sẽ kiểm tra xem phương thức có tồn tại hay không và thực hiện kiểm tra kiểu trên các đối số và kiểu trả về, nhưng không biết đoạn mã nào sẽ thực thi trong thời gian chạy. Khi một thông điệp được gửi đến một đối tượng để gọi một phương thức, đối tượng sẽ tìm ra đoạn mã nào sẽ thực thi trong thời gian chạy.
Mặc dù ràng buộc động giải quyết vấn đề trong việc hỗ trợ đa hình, nhưng nó đặt ra một vấn đề mới khác. Trình biên dịch không thể kiểm tra xem toán tử đúc có an toàn không. Nó chỉ có thể được kiểm tra trong thời gian chạy (sẽ ném ClassCastException
nếu kiểm tra kiểu không thành công).
JDK 1.5 giới thiệu một tính năng mới gọi là generic để giải quyết vấn đề này. Chúng ta sẽ thảo luận về vấn đề này và khái quát chi tiết trong chương sau.
5.13 Exercises
LINK TO EXERCISES
Đăng nhận xét