4.  Polymorphism - Đa Hình

Từ " đa hình " có nghĩa là " nhiều dạng ". Nó xuất phát từ tiếng Hy Lạp " poly " (có nghĩa là nhiều ) và " morphos " (có nghĩa là hình thức ). Ví dụ, trong hóa học, carbon thể hiện tính đa hình vì nó có thể được tìm thấy ở nhiều dạng: than chì và kim cương. Nhưng, mỗi hình thức có thuộc tính riêng biệt (và giá cả).

4.1  Substitutability - Thay thế

Một lớp con sở hữu tất cả các thuộc tính và hoạt động của siêu lớp của nó (bởi vì một lớp con được thừa hưởng tất cả các thuộc tính và hoạt động từ siêu lớp của nó). Điều này có nghĩa là một đối tượng lớp con có thể làm bất cứ điều gì siêu lớp của nó có thể làm. Kết quả là, chúng ta có thể thay thế một thể hiện của lớp con khi một cá thể siêu lớp được mong đợi và mọi thứ sẽ hoạt động tốt. Điều này được gọi là thay thế .
OOP_PolymorphismCircleCylinder.png
Trong ví dụ trước của chúng tôi về Circlevà CylinderCylinderlà một lớp con của CircleChúng ta có thể nói rằng Cylinderis-a " Circle(thực ra, nó " hơn-a-a " Circle). Phân lớp siêu lớp thể hiện mối quan hệ được gọi là " is-a ".
Circle.java
Circle.java
/** The superclass Circle */
public class Circle {
   // private instance variable
   private double radius;
   /** Constructs a Circle instanve with the given radius */
   public Circle(double radius) {
      this.radius = radius;
   }
   /** Returns the radius */
   public double getRadius() {
      return this.radius;
   }
   /** Returns the area of this circle */
   public double getArea() {
      return radius * radius * Math.PI;
   }
   /** Returns a self-descriptive string */
   public String toString() {
      return "Circle[radius=" + radius + "]";
   }
}
Cylinder.java
/** The subclass Cylinder */
public class Cylinder extends Circle {
   // private instance variable
   private double height;
   /** Constructs a Cylinder instance with the given height and radius */
   public Cylinder(double height, double radius) {
      super(radius);
      this.height = height;
   }
   /** Returns the height */
   public double getHeight() {
      return this.height;
   }
   /** Returns the volume of this cylinder */
   public double getVolumne() {
      return super.getArea() * height;
   }
   /** Overrides the inherited method to return the surface area */
   @Override
   public double getArea() {
      return 2.0 * Math.PI * getRadius() * height;
   }
   /** Override the inherited method to describe itself */
   @Override
   public String toString() {
      return "Cylinder[height=" + height + "," + super.toString() + "]";
   }
}
Thông qua khả năng thay thế , chúng ta có thể tạo một thể hiện Cylindervà gán nó cho một Circletham chiếu (siêu lớp của nó), như sau:
// Substitute a subclass instance to a superclass reference
Circle c1 = new Cylinder(1.1, 2.2);
Bạn có thể gọi tất cả các phương thức được định nghĩa trong Circlelớp để tham chiếu c1, ( thực tế đang giữ một Cylinderđối tượng), vd
// Invoke superclass Circle's methods
System.out.println(c1.getRadius());
//2.2
Điều này là do một thể hiện của lớp con sở hữu tất cả các thuộc tính của siêu lớp của nó.
Tuy nhiên, bạn KHÔNG THỂ gọi các phương thức được định nghĩa trong Cylinderlớp để tham chiếu c1, vd
// CANNOT invoke method in Cylinder as c1 is a Circle reference
c1.getHeight();
//compilation error: cannot find symbol method getHeight()
c1.getVolume();
//compilation error: cannot find symbol method getVolume()
Điều này là do c1tham chiếu đến Circlelớp, không biết về các phương thức được định nghĩa trong lớp con Cylinder.
c1là một tham chiếu đến Circlelớp, nhưng giữ một đối tượng của lớp con của nó CylinderCác tài liệu tham khảo c1, tuy nhiên, vẫn giữ bản sắc nội bộ của nó . Trong ví dụ của chúng tôi, lớp con Cylinderghi đè các phương thức getArea()và toString()c1.getArea()hoặc c1.toString()gọi phiên bản phiên bản ghi đè được xác định trong lớp con , thay vì phiên bản được xác định trong Điều này là do trên thực tế đang giữ một đối tượng trong nội bộ.CylinderCirclec1Cylinder
System.out.println(c1.toString());  // Run the overridden version
//Cylinder[height=1.1,Circle[radius=2.2]]
System.out.println(c1.getArea());   // Run the overridden version
//15.205308443374602
Tóm lược
  1. Một thể hiện của lớp con có thể được chỉ định (thay thế) cho tham chiếu của lớp cha.
  2. Sau khi được thay thế, chúng ta có thể gọi các phương thức được định nghĩa trong lớp cha; chúng ta không thể gọi các phương thức được định nghĩa trong lớp con.
  3. Tuy nhiên, nếu lớp con ghi đè các phương thức được kế thừa từ lớp bậc trên, các phiên bản lớp con (ghi đè) sẽ được gọi.

4.2  Polymorphism EG. 1: Shape và các class con của nó

Đa hình rất mạnh trong OOP để phân tách interface và triển khai để cho phép lập trình viên lập trình interface trong thiết kế cho hệ thống phức tạp .
Hãy xem xét ví dụ sau. Giả sử rằng chương trình của chúng tôi sử dụng nhiều loại hình dạng, chẳng hạn như hình tam giác, hình chữ nhật, v.v. Chúng ta nên thiết kế một siêu lớp được gọi là Shape, xác định các giao diện công cộng (public interface) (hoặc hành vi) của tất cả các hình dạng. Ví dụ, chúng tôi muốn tất cả các hình dạng có một phương thức được gọi getArea()phương thức này trả về diện tích của hình dạng cụ thể đó. Các lớp hình học Shape có thể được viết như sau.
OOP_PolymorphismShape.png
Superclass Shape.java
/**
 * Superclass Shape maintain the common properties of all shapes
 */
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 shapes must provide a method called getArea() */
   public double getArea() {
      // We have a problem here!
      // We need to return some value to compile the program.
      System.err.println("Shape unknown! Cannot compute area!");
      return 0;
   }
}
Hãy lưu ý rằng chúng ta có một vấn đề khi viết getArea()phương thức trong lớp Shape, bởi vì diện tích không thể được tính trừ khi hình dạng thực tế được biết đến. Chúng tôi sẽ in một thông báo lỗi trong thời gian này. Trong phần sau, tôi sẽ chỉ cho bạn cách giải quyết vấn đề này.
Sau đó chúng ta có thể rút ra các lớp con, chẳng hạn như Trianglevà Rectangle, từ siêu lớp Shape.
Subclass Rectangle.java
/**
 * The Rectangle class, subclass of Shape
 */
public class Rectangle extends Shape {
   // Private member variables
   private int length, width;
   
   /** Constructs a Rectangle instance with the given color, length and width */
   public Rectangle(String color, int length, int width) {
      super(color);
      this.length = length;
      this.width = width;
   }

   /** Returns a self-descriptive string */   
   @Override
   public String toString() {
      return "Rectangle[length=" + length + ",width=" + width + "," + super.toString() + "]";
   }
   
   /** Override the inherited getArea() to provide the proper implementation for rectangle */
   @Override
   public double getArea() {
      return length*width;
   }
}
Subclass Triangle.java
/** 
 * The Triangle class, subclass of Shape
 */
public class Triangle extends Shape {
   // Private member variables
   private int base, height;
   
   /** Constructs a Triangle instance with the given color, base and height */
   public Triangle(String color, int base, int height) {
      super(color);
      this.base = base;
      this.height = height;
   }
   
   /** Returns a self-descriptive string */
   @Override
   public String toString() {
      return "Triangle[base=" + base + ",height=" + height + "," + super.toString() + "]";
   }
   
   /** Override the inherited getArea() to provide the proper implementation for triangle */
   @Override
   public double getArea() {
      return 0.5*base*height;
   }
}
Các lớp con ghi đè getArea() phương thức được kế thừa từ lớp cha và cung cấp các triển khai thích hợp cho getArea().
Trình điều khiển thử nghiệm ( TestShape.java )
Trong ứng dụng của chúng tôi, chúng tôi có thể tạo các tham chiếu Shape và gán cho chúng các thể hiện của các lớp con, như sau:
/**
 * A test driver for Shape and its subclasses
 */
public class TestShape {
   public static void main(String[] args) {
      Shape s1 = new Rectangle("red", 4, 5);  // Upcast
      System.out.println(s1);  // Run Rectangle's toString()
      //Rectangle[length=4,width=5,Shape[color=red]]
      System.out.println("Area is " + s1.getArea());  // Run Rectangle's getArea()
      //Area is 20.0

      Shape s2 = new Triangle("blue", 4, 5);  // Upcast
      System.out.println(s2);  // Run Triangle's toString()
      //Triangle[base=4,height=5,Shape[color=blue]]
      System.out.println("Area is " + s2.getArea());  // Run Triangle's getArea()
      //Area is 10.0
   }
}
Vẻ đẹp của mã này là tất cả các tham chiếu đến từ siêu lớp (tức là lập trình ở cấp độ giao diện ). Bạn có thể khởi tạo thể hiện của lớp con khác nhau và mã vẫn hoạt động. Bạn có thể mở rộng chương trình của bạn một cách dễ dàng bằng cách thêm vào nhiều lớp con, chẳng hạn như CircleSquare, vv, một cách dễ dàng.
Tuy nhiên, định nghĩa trên của lớp Shape đặt ra một vấn đề, nếu ai đó khởi tạo một đối tượng Shape và gọi getArea()từ đối tượng Shape, chương trình sẽ bị phá vỡ.
public class TestShape {
   public static void main(String[] args) {
      // Constructing a Shape instance poses problem!
      Shape s3 = new Shape("green");
      System.out.println(s3);
      //Shape[color=green]
      System.out.println("Area is " + s3.getArea());  // Invalid output
      //Shape unknown! Cannot compute area!
      //Area is 0.0
   }
}
Điều này là do Shapelớp có nghĩa là cung cấp một giao diện chung cho tất cả các lớp con của nó, được cho là để cung cấp việc thực hiện thực tế. Chúng tôi không muốn bất cứ ai khởi tạo một ví dụ ShapeVấn đề này có thể được giải quyết bằng cách sử dụng lớp trưu tượng được gọi là abstract class .

4.3  Polymorphism EG. 2: Monster và Subclasses của nó

OOP_PolymorphismMonster.png
Đa hình là một cơ chế mạnh mẽ trong OOP để phân tách giao diện và triển khai để cho phép lập trình viên lập trình tại giao diện trong thiết kế của một hệ thống phức tạp. Ví dụ, trong ứng dụng trò chơi của chúng tôi, chúng tôi có nhiều loại quái vật có thể tấn công. Chúng ta sẽ thiết kế một siêu lớp được gọi Monstervà xác định phương thức attack()trong siêu lớp. Các lớp con sau đó sẽ cung cấp thực hiện thực tế của họ. Trong chương trình chính, chúng tôi khai báo các thể hiện của siêu lớp, được thay thế bằng lớp con thực tế; và gọi phương thức được định nghĩa trong lớp cha.
Superclass Monster.java
/**
 * The superclass Monster defines the expected common behaviors for its subclasses.
 */
public class Monster {
   // private instance variable
   private String name;

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

   /** Defines a common behavior called attack() for all its subclasses */
   public String attack() {
      return "!^_&^$@+%$* I don't know how to attack!";
      // We have a problem here!
      // We need to return a String; else, compilation error!
   }
}
Subclass FireMonster.java
public class FireMonster extends Monster {
   /** Constructs a FireMonster with the given name */
   public FireMonster(String name) {
      super(name);
   }
   /** Subclass provides actual implementation for attack() */
   @Override
   public String attack() {
      return "Attack with fire!"; 
   }
}
Subclass WaterMonster.java
public class WaterMonster extends Monster {
   /** Constructs a WaterMonster instance with the given name */
   public WaterMonster(String name) {
      super(name);
   }
   /** Subclass provides actual implementation for attack() */
   @Override
   public String attack() {
      return "Attack with water!";
   }
}
Subclass StoneMonster.java
public class StoneMonster extends Monster {
   /** Constructs a StoneMonster instance with the given name */
   public StoneMonster(String name) {
      super(name);
   }
   /** Subclass provides actual implementation for attack() */
   @Override
   public String attack() {
      return "Attack with stones!";
   }
}
A Test Driver TestMonster.java
public class TestMonster {
   public static void main(String[] args) {
      // Program at the specification defined in the superclass/interface.
      // Declare instances of the superclass, substituted by subclasses.
      Monster m1 = new FireMonster("r2u2");   // upcast
      Monster m2 = new WaterMonster("u2r2");  // upcast
      Monster m3 = new StoneMonster("r2r2");  // upcast

      // Invoke the actual implementation
      System.out.println(m1.attack());  // Run FireMonster's attack()
      //Attack with fire!
      System.out.println(m2.attack());  // Run WaterMonster's attack()
      //Attack with water!
      System.out.println(m3.attack());  // Run StoneMonster's attack()
      //Attack with stones!

      // m1 dies, generate a new instance and re-assign to m1.
      m1 = new StoneMonster("a2b2");  // upcast
      System.out.println(m1.attack());  // Run StoneMonster's attack()
      //Attack with stones!

      // We have a problem here!!!
      Monster m4 = new Monster("u2u2");
      System.out.println(m4.attack());  // garbage!!!
      //!^_&^$@+%$* I don't know how to attack!
   }
}

4.4  Upcasting & Downcasting

Upcasting Instance tham chiếu tới Superclass
Việc thay thế một thể hiện của lớp con cho siêu lớp của nó được gọi là " upcasting ". Điều này là do, trong sơ đồ lớp UML, lớp con thường được vẽ bên dưới lớp cha của nó. Upcasting luôn an toàn vì một thể hiện của lớp con sở hữu tất cả các thuộc tính của siêu lớp của nó và có thể làm bất cứ điều gì mà siêu lớp của nó có thể làm. Trình biên dịch kiểm tra việc upcasting hợp lệ và phát sinh lỗi "các loại không tương thích" nếu không. Ví dụ,
Circle c1 = new Cylinder(1.1, 2.2);  // Compiler checks to ensure that R-value is a subclass of L-value.
Circle c2 = new String();            // Compilation error: incompatible types
Downcasting một tài liệu tham khảo thay thế cho lớp ban đầu của nó
Bạn có thể hoàn nguyên một thể hiện được thay thế trở lại tham chiếu lớp con. Điều này được gọi là " downcasting ". Ví dụ,
Circle c1 = new Cylinder(1.1, 2.2);  // upcast is safe
Cylinder cy1 = (Cylinder) c1;        // downcast needs the casting operator
Downcasting yêu cầu toán tử đúc kiểu rõ ràng ở dạng toán tử tiền tố Downcasting không phải lúc nào cũng an toàn và ném ra một thời gian chạy nếu thể hiện bị downcast không thuộc về lớp con chính xác. Một đối tượng lớp con có thể được thay thế cho siêu lớp của nó, nhưng điều ngược lại là không đúng.(new-type)ClassCastException
Một ví dụ khác về Upcasting và Downcasting
OOP_PolymorphismABC.png
public class A {
   public A() {  // Constructor
      System.out.println("Constructed an instance of A");
   }
   @Override
   public String toString() {
      return "This is A";
   }
}
public class B extends A {
   public B() {  // Constructor
      super();
      System.out.println("Constructed an instance of B");
   }
   @Override
   public String toString() {
      return "This is B";
   }
}
public class C extends B {
   public C() {  // Constructor
      super();
      System.out.println("Constructed an instance of C");
   }
   @Override
   public String toString() {
      return "This is C";
   }
}
Chương trình này kiểm tra quá trình upcasting và downcasting
public class TestCasting {
   public static void main(String[] args) {
      A a1 = new C();   // upcast
      //Constructed an instance of A
      //Constructed an instance of B
      //Constructed an instance of C
      System.out.println(a1);  // run C's toString()
      //This is C
      B b1 = (B)a1;     // downcast okay
      System.out.println(b1);  // run C's toString()
      //This is C
      C c1 = (C)b1;     // downcast okay
      System.out.println(c1);  // run C's toString()
      //This is C

      A a2 = new B();  // upcast
      //Constructed an instance of A
      //Constructed an instance of B
      System.out.println(a2);  // run B's toString()
      //This is B
      B b2 = (B)a2;    // downcast okay
      C c2 = (C)a2;
      //compilation okay, but runtime error:
      //java.lang.ClassCastException: class B cannot be cast to class C
   }
}
Casting Operator
Trình biên dịch có thể không thể phát hiện lỗi trong quá trình truyền rõ ràng, điều này sẽ chỉ được phát hiện khi chạy. Ví dụ,
Circle c1 = new Circle(5);
Point p1 = new Point();
 
c1 = p1;          //compilation error: incompatible types (Point is not a subclass of Circle)
c1 = (Circle)p1;  //runtime error: java.lang.ClassCastException: Point cannot be casted to Circle

4.5  Toán tử "instanceof"

Java cung cấp toán tử nhị phân được gọi là instanceof nó sẽ trả về giá trị true nếu có một đối tượng là instance (thể hiển) của particular class. Cú pháp như sau: 
anObject instanceof aClass
Circle c1 = new Circle();
System.out.println(c1 instanceof Circle);  // true
 
if (c1 instanceof Circle) { ...... }
Một thể hiện của lớp con cũng là một thể hiện của siêu lớp của nó. Ví dụ,
Circle c1 = new Circle(1.1);
Cylinder cy1 = new Cylinder(2.2, 3.3);
System.out.println(c1 instanceof Circle);    //true
System.out.println(c1 instanceof Cylinder);  //false
System.out.println(cy1 instanceof Cylinder); //true
System.out.println(cy1 instanceof Circle);   //true
 
Circle c2 = new Cylinder(4.4, 5.5);
System.out.println(c2 instanceof Circle);    //true
System.out.println(c2 instanceof Cylinder);  //true

4.6  Tóm Tắt về Đa Hình

  1. Một thể hiện của lớp con xử lý tất cả các hoạt động thuộc tính của siêu lớp của nó. Khi một cá thể siêu lớp được mong đợi, nó có thể được thay thế bằng một thể hiện của lớp con. Nói cách khác, một tham chiếu đến một lớp có thể chứa một thể hiện của lớp đó hoặc một thể hiện của một trong các lớp con của nó - nó được gọi là khả năng thay thế.
  2. Nếu một thể hiện của lớp con được gán cho một tham chiếu siêu lớp, bạn chỉ có thể gọi các phương thức được định nghĩa trong siêu lớp. Bạn không thể gọi các phương thức được định nghĩa trong lớp con.
  3. Tuy nhiên, cá thể thay thế vẫn giữ bản sắc riêng của 

4.7  Bài Tập

LINK TO EXERCISES

Post a Comment

Mới hơn Cũ hơn