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ế .
Trong ví dụ trước của chúng tôi về Circle
và Cylinder
: Cylinder
là một lớp con của Circle
. Chúng ta có thể nói rằng Cylinder
" is-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
public class Circle {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return this.radius;
}
public double getArea() {
return radius * radius * Math.PI;
}
public String toString() {
return "Circle[radius=" + radius + "]";
}
}
Cylinder.java
public class Cylinder extends Circle {
private double height;
public Cylinder(double height, double radius) {
super(radius);
this.height = height;
}
public double getHeight() {
return this.height;
}
public double getVolumne() {
return super.getArea() * height;
}
@Override
public double getArea() {
return 2.0 * Math.PI * getRadius() * height;
}
@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 Cylinder
và gán nó cho một Circle
tham chiếu (siêu lớp của nó), như sau:
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 Circle
lớp để tham chiếu c1
, (
thực tế đang giữ một Cylinder
đối tượng), vd
System.out.println(c1.getRadius());
Đ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 Cylinder
lớp để tham chiếu c1
, vd
c1.getHeight();
//compilation error: cannot find symbol method getHeight()
c1.getVolume();
//compilation error: cannot find symbol method getVolume()
Điều này là do c1
tham chiếu đến Circle
lớp, không biết về các phương thức được định nghĩa trong lớp con Cylinder
.
c1
là một tham chiếu đến Circle
lớp, nhưng giữ một đối tượng của lớp con của nó Cylinder
. Cá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 Cylinder
ghi đè 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ộ.Cylinder
Circle
c1
Cylinder
System.out.println(c1.toString());
Cylinder[height=1.1,Circle[radius=2.2]]
System.out.println(c1.getArea());
Tóm lược
- 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.
- 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.
- 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.
Superclass Shape.java
public class Shape {
private String color;
public Shape (String color) {
this.color = color;
}
@Override
public String toString() {
return "Shape[color=" + color + "]";
}
public double getArea() {
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ư Triangle
và Rectangle
, từ siêu lớp Shape
.
Subclass Rectangle.java
public class Rectangle extends Shape {
private int length, width;
public Rectangle(String color, int length, int width) {
super(color);
this.length = length;
this.width = width;
}
@Override
public String toString() {
return "Rectangle[length=" + length + ",width=" + width + "," + super.toString() + "]";
}
@Override
public double getArea() {
return length*width;
}
}
Subclass Triangle.java
public class Triangle extends Shape {
private int base, height;
public Triangle(String color, int base, int height) {
super(color);
this.base = base;
this.height = height;
}
@Override
public String toString() {
return "Triangle[base=" + base + ",height=" + height + "," + super.toString() + "]";
}
@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:
public class TestShape {
public static void main(String[] args) {
Shape s1 = new Rectangle("red", 4, 5);
System.out.println(s1);
Rectangle[length=4,width=5,Shape[color=red]]
System.out.println("Area is " + s1.getArea());
Shape s2 = new Triangle("blue", 4, 5);
System.out.println(s2);
Triangle[base=4,height=5,Shape[color=blue]]
System.out.println("Area is " + s2.getArea());
}
}
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ư Circle
, Square
, 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) {
Shape s3 = new Shape("green");
System.out.println(s3);
System.out.println("Area is " + s3.getArea());
}
}
Điều này là do Shape
lớ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ụ Shape
. Vấ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ó
Đ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 Monster
và 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
public class Monster {
private String name;
public Monster(String name) {
this.name = name;
}
public String attack() {
return "!^_&^$@+%$* I don't know how to attack!";
}
}
Subclass FireMonster.java
public class FireMonster extends Monster {
public FireMonster(String name) {
super(name);
}
@Override
public String attack() {
return "Attack with fire!";
}
}
Subclass WaterMonster.java
public class WaterMonster extends Monster {
public WaterMonster(String name) {
super(name);
}
@Override
public String attack() {
return "Attack with water!";
}
}
Subclass StoneMonster.java
public class StoneMonster extends Monster {
public StoneMonster(String name) {
super(name);
}
@Override
public String attack() {
return "Attack with stones!";
}
}
A Test Driver TestMonster.java
public class TestMonster {
public static void main(String[] args) {
Monster m1 = new FireMonster("r2u2");
Monster m2 = new WaterMonster("u2r2");
Monster m3 = new StoneMonster("r2r2");
System.out.println(m1.attack());
System.out.println(m2.attack());
System.out.println(m3.attack());
m1 = new StoneMonster("a2b2");
System.out.println(m1.attack());
Monster m4 = new Monster("u2u2");
System.out.println(m4.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);
Circle c2 = new String();
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);
Cylinder cy1 = (Cylinder) c1;
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
public class A {
public A() {
System.out.println("Constructed an instance of A");
}
@Override
public String toString() {
return "This is A";
}
}
public class B extends A {
public B() {
super();
System.out.println("Constructed an instance of B");
}
@Override
public String toString() {
return "This is B";
}
}
public class C extends B {
public C() {
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();
Constructed an instance of A
System.out.println(a1);
B b1 = (B)a1;
System.out.println(b1);
C c1 = (C)b1;
System.out.println(c1);
A a2 = new B();
System.out.println(a2);
B b2 = (B)a2;
C c2 = (C)a2;
compilation okay, but runtime error:
}
}
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;
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);
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);
System.out.println(c1 instanceof Cylinder);
System.out.println(cy1 instanceof Cylinder);
System.out.println(cy1 instanceof Circle);
Circle c2 = new Cylinder(4.4, 5.5);
System.out.println(c2 instanceof Circle);
System.out.println(c2 instanceof Cylinder);
4.6 Tóm Tắt về Đa Hình
- 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ế.
- 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.
- 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
Đăng nhận xét