5.  Abstract Classes & Interfaces

5.1  The abstract Method and abstract class

Trong các ví dụ trên lớp  Shapevà 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 abstractphương thức này không thể được gọi vì chúng không có triển khai.
OOP_PolymorphismAbstractShape.png
Một lớp chứa một hoặc nhiều abstractphương thức được gọi là một abstractlớp. Một abstractlớp phải được khai báo với một bộ sửa đổi lớp abstractMột abstractlớp KHÔNG THỂ được khởi tạo, vì định nghĩa của nó không đầy đủ.
Ký hiệu UML : abstractlớ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 Shapelớp của chúng ta dưới dạng một abstractlớp, chứa một abstractphương thức getArea()như sau:
The abstract Superclass Shape.java
/**
 * This abstract superclass Shape contains an abstract method
 *   getArea(), to be implemented by its subclasses.
 */
abstract public class Shape {
   // Private member variable
   private String color;
   
   /** Constructs a Shape instance with the given color */
   public Shape (String color) {
      this.color = color;
   }

   /** Returns a self-descriptive string */   
   @Override
   public String toString() {
      return "Shape[color=" + color + "]";
   }
   
   /** All Shape's concrete subclasses must implement a method called getArea() */
   abstract public double getArea();
}
Một abstractlớp không đầy đủ trong định nghĩa của nó, vì việc thực hiện các abstractphương thức của nó bị thiếu. Do đó, một abstractlớ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 abstractlớ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 abstractlớp, bạn phải lấy một lớp con từ abstractlớp đó. Trong lớp con dẫn xuất, bạn phải ghi đè các abstractphương thức và cung cấp triển khai cho tất cả các abstractphươ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 abstractphương thức của lớp cha, lớp con vẫn còn abstract.)
Tài sản này của abstractlớ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ư Trianglevà 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());
      
      // Cannot create instance of an abstract class
      Shape s3 = new Shape("green");
      //compilation error: Shape is abstract; cannot be instantiated
   }
}
Tóm lại, một abstractlớ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 abstractlớ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 abstractphươ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 Shapevà chương trình ở Shapecấ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ư Circlehoặ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ì finalphương thức không thể bị ghi đè. abstractMặ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 abstractphương thức không thể private(tạo ra lỗi biên dịch). Điều này là do privatephươ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 Monsterlà một abstractlớp, chứa một abstractphương thức attack()Các abstractlớp học không thể được khởi tạo (ví dụ, tạo ra các trường hợp).
/**
 * The abstract superclass Monster defines the expected common behaviors,
 *   via abstract methods.
 */
abstract public class Monster {
   private String name;  // private instance variable

   /** Constructs a Monster instance of the given name */
   public Monster(String name) {
      this.name = name;
   }

   /** Define common behavior for all its subclasses */
   abstract public String attack();
}

5.4  The Java's interface

Java interfacelà 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 interfacechỉ 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 finalbiế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 publicvà abstractkhô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 abstractsiêu lớp, interfacekhô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 abstractphươ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ụ, SerializableExtenalizableMovableClonableRunnable,, vv

5.5  Interface EG. 1: Shape Interface and its Implementations

Chúng ta có thể viết lại abstractsiêu lớp Shapethành một interface, chỉ chứa abstractcác phương thức, như sau:OOP_InterfaceShape.png
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.
/**
 * The interface Shape specifies the behaviors
 *   of this implementations subclasses.
 */
public interface Shape {  // Use keyword "interface" instead of "class"
   // List of public abstract methods to be implemented by its subclasses
   // All methods in interface are "public abstract".
   // "protected", "private" and "package" methods are NOT allowed.
   double getArea();
}
/**
 * The subclass Rectangle needs to implement all the abstract methods in Shape
 */
public class Rectangle implements Shape {  // using keyword "implements" instead of "extends"
   // Private member variables
   private int length, width;

   /** Constructs a Rectangle instance with the given length and width */
   public Rectangle(int length, int width) {
      this.length = length;
      this.width = width;
   }

   /** Returns a self-descriptive string */
   @Override
   public String toString() {
      return "Rectangle[length=" + length + ",width=" + width + "]";
   }

   // Need to implement all the abstract methods defined in the interface
   /** Returns the area of this rectangle */
   @Override
   public double getArea() {
      return length * width;
   }
}
/**
 * The subclass Triangle need to implement all the abstract methods in Shape
 */
public class Triangle implements Shape {
   // Private member variables
   private int base, height;

   /** Constructs a Triangle instance with the given base and height */
   public Triangle(int base, int height) {
      this.base = base;
      this.height = height;
   }

   /** Returns a self-descriptive string */
   @Override
   public String toString() {
      return "Triangle[base=" + base + ",height=" + height + "]";
   }

   // Need to implement all the abstract methods defined in the interface
   /** Returns the area of this triangle */
   @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);  // upcast
      System.out.println(s1);
      //Rectangle[length=1,width=2]
      System.out.println("Area is " + s1.getArea());
      //Area is 2.0

      Shape s2 = new Triangle(3, 4);  // upcast
      System.out.println(s2);
      //Triangle[base=3,height=4]
      System.out.println("Area is " + s2.getArea());
      //Area is 6.0

      // Cannot create instance of an interface
      //Shape s3 = new Shape("green");
      //compilation error: Shape is abstract; cannot be instantiated
   }
}

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.
OOP_InterfaceMovable.png
Interface Moveable.java
/**
 * The Movable interface defines a list of public abstract methods
 *   to be implemented by its subclasses
 */
public interface Movable {  // use keyword "interface" (instead of "class") to define an interface
   // An interface defines a list of public abstract methods to be implemented by the subclasses
   public void moveUp();    // "public" and "abstract" optional
   public void moveDown();
   public void moveLeft();
   public void moveRight();
}
Tương tự như một abstractlớp, interfacekhô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ụ,
/**
 * The subclass MovablePoint needs to implement all the abstract methods
 *   defined in the interface Movable
 */
public class MovablePoint implements Movable {
   // Private member variables
   private int x, y;   // x and y coordinates of the point
      
   /** Constructs a MovablePoint instance at the given x and y */
   public MovablePoint(int x, int y) {
      this.x = x;
      this.y = y;
   }

   /** Returns a self-descriptive string */ 
   @Override
   public String toString() {
      return "(" + x + "," + y + ")";
   }

   // Need to implement all the abstract methods defined in the interface Movable
   @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ự Movablegiao diện và cung cấp triển khai riêng của chúng cho các abstractphươ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 Movablegiao diện, thông qua đa hình, tương tự như một abstractlớp.
public class TestMovable {
   public static void main(String[] args) {
      MovablePoint p1 = new MovablePoint(1, 2);
      System.out.println(p1);
      //(1,2)
      p1.moveDown();
      System.out.println(p1);
      //(1,3)
      p1.moveRight();
      System.out.println(p1);
      //(2,3)

      // Test Polymorphism
      Movable p2 = new MovablePoint(3, 4);  // upcast
      p2.moveUp();
      System.out.println(p2);
      //(3,3)

      MovablePoint p3 = (MovablePoint)p2;   // downcast
      System.out.println(p3);
      //(3,3)
   }
}

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 { 
          // extends one superclass but implements multiple interfaces
   .......
}

5.8  interface Formal Syntax

Cú pháp để khai báo interface là:
[public|package] interface interfaceName
[extends superInterfaceName] {
   // constants
   static final ...;

   // public abstract methods' signature
   ...
}
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ư privateprotectedvà mặc định hoặc công cụ sửa đổi, chẳng hạn như staticfinal.
Tất cả các trường sẽ là publicstaticvà final(mặc định).
An interfacecó 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.
OOP_UMLSuperclass.png

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 Runnablehoặ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 Shapeví 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 Rectanglevà 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 ClassCastExceptionnế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

Post a Comment

Mới hơn Cũ hơn