4. Luyện Tập về Thừa Kế Trong Java

4.1  Ex: The Circle and Cylinder Classes

Bài tập này sẽ hướng dẫn bạn thông qua các khái niệm quan trọng trong thừa kế.
ExerciseOOP_CircleCylinder.png
Trong bài tập này, một lớp con được gọi Cylinderlà bắt nguồn từ siêu lớp Circlenhư được hiển thị trong sơ đồ lớp (trong đó một mũi tên hướng lên từ lớp con đến siêu lớp của nó). Nghiên cứu cách lớp con Cylindergọi các hàm tạo của lớp cha (thông qua super()và super(radius)) và kế thừa các biến và phương thức từ lớp cha Circle.
Bạn có thể sử dụng lại Circlelớp mà bạn đã tạo trong bài tập trước. Đảm bảo rằng bạn giữ " Circle.class" trong cùng thư mục.
public class Cylinder extends Circle {  // Save as "Cylinder.java"
   private double height;  // private variable
   
   // Constructor with default color, radius and height
   public Cylinder() {
      super();        // call superclass no-arg constructor Circle()
      height = 1.0; 
   }
   // Constructor with default radius, color but given height
   public Cylinder(double height) {
      super();        // call superclass no-arg constructor Circle()
      this.height = height;
   }
   // Constructor with default color, but given radius, height
   public Cylinder(double radius, double height) {
      super(radius);  // call superclass constructor Circle(r)
      this.height = height;
   }
   
   // A public method for retrieving the height
   public double getHeight() {
      return height; 
   }
  
   // A public method for computing the volume of cylinder
   //  use superclass method getArea() to get the base area
   public double getVolume() {
      return getArea()*height; 
   }
}
Viết chương trình kiểm tra (TestCylinder) để kiểm tra lớp Cylinder đã tạo, như sau:
public class TestCylinder {  // save as "TestCylinder.java"
   public static void main (String[] args) {
      // Declare and allocate a new instance of cylinder
      //   with default color, radius, and height
      Cylinder c1 = new Cylinder();
      System.out.println("Cylinder:"
            + " radius=" + c1.getRadius()
            + " height=" + c1.getHeight()
            + " base area=" + c1.getArea()
            + " volume=" + c1.getVolume());
   
      // Declare and allocate a new instance of cylinder
      //   specifying height, with default color and radius
      Cylinder c2 = new Cylinder(10.0);
      System.out.println("Cylinder:"
            + " radius=" + c2.getRadius()
            + " height=" + c2.getHeight()
            + " base area=" + c2.getArea()
            + " volume=" + c2.getVolume());
   
      // Declare and allocate a new instance of cylinder
      //   specifying radius and height, with default color
      Cylinder c3 = new Cylinder(2.0, 10.0);
      System.out.println("Cylinder:"
            + " radius=" + c3.getRadius()
            + " height=" + c3.getHeight()
            + " base area=" + c3.getArea()
            + " volume=" + c3.getVolume());
   }
}
Method Overriding and "Super": Lớp con lớp Cylinder kế thừa getArea()phương thức từ Vòng tròn siêu lớp của nó. Hãy thử cách ghi đè các getArea()phương pháp trong các lớp con Cylinderđể tính diện tích bề mặt (= 2π × bán kính × chiều cao + 2 × cơ sở khu vực) của xi lanh thay vì của vùng căn cứ. Đó là, nếu getArea()được gọi bởi một Circlethể hiện, nó trả về khu vực. Nếu getArea()được gọi bởi một Cylinderthể hiện, nó trả về diện tích bề mặt của hình trụ.
Nếu bạn ghi đè lên getArea()trong lớp con Cylinder, nó getVolume()không còn hoạt động. Điều này là do getVolume()sử dụng phương thức ghi đè được tìm thấy trong cùng một lớp. (Thời gian chạy Java sẽ chỉ tìm kiếm siêu lớp nếu nó không thể định vị phương thức trong lớp này). Sửa lỗi .getArea()getVolume()
Gợi ý: Sau khi ghi đè lên getArea()lớp con Cylinder, bạn có thể chọn gọi getArea()siêu lớp Circlebằng cách gọi super.getArea().
THỬ:
Cung cấp một toString()phương thức cho Cylinderlớp, ghi đè lên toString()kế thừa từ siêu lớp Circle, ví dụ:
@Override
public String toString() {      // in Cylinder class
   return "Cylinder: subclass of " + super.toString()  // use Circle's toString()
          + " height=" + height;
}
Hãy thử toString()phương pháp trong TestCylinder.
Lưu ý: @Overrideđược gọi là chú thích (được giới thiệu trong JDK 1.5), yêu cầu trình biên dịch kiểm tra xem có phương thức nào như vậy trong siêu lớp được ghi đè không. Điều này giúp rất nhiều nếu bạn viết sai tên của toString()Nếu @Overridekhông được sử dụng và toString()bị sai chính tả ToString(), nó sẽ được coi là một phương thức mới trong lớp con, thay vì ghi đè lên lớp cha. Nếu @Overrideđược sử dụng, trình biên dịch sẽ báo hiệu lỗi. @Overridechú thích là tùy chọn, nhưng chắc chắn tốt đẹp để có.

4.2 Ví dụ: Superclass Person và các lớp con của nó

ExerciseOOP_PersonAndSubclasses.png

4.3  Ex: Point2D and Point3D

ExerciseOOP_Point2DPoint3D.png

4.4  Ex: Point and MovablePoint

ExerciseOOP_PointMovablePoint.png

4.5  Ex: Superclass Shape and its subclasses CircleRectangle and Square

ExerciseOOP_ShapeAndSubclasses.png
Viết một siêu lớp được gọi Shape(như thể hiện trong sơ đồ lớp), chứa:
  • Hai biến đối tượng colorString) và filledboolean).
  • Hai hàm tạo: một hàm tạo không có đối số (không đối số) khởi tạo colorthành "xanh" và filledđến truevà một hàm tạo khởi tạo colorvà filledcho các giá trị đã cho.
  • Getter và setter cho tất cả các biến thể hiện. Theo quy ước, getter cho một booleanbiến xxxđược gọi isXXX()(thay vì getXxx()cho tất cả các loại khác).
  • Một toString()phương thức trả về " A Shape with color of xxx and filled/Not filled".
Viết chương trình kiểm tra để kiểm tra tất cả các phương thức được định nghĩa trong Shape.
Viết hai lớp con Shapeđược gọi Circlevà Rectangle, như thể hiện trong sơ đồ lớp.
Các Circlelớp học bao gồm:
  • Một biến đối tượng radiusdouble).
  • Ba Constructor như hình. Trong constructor không có đối số thì khởi tạo bán kính tới 1.0.
  • Getter và setter cho biến thể hiện (instance variable) radius.
  • Phương thức getArea()và getPerimeter().
  • Ghi đè toString()phương thức được kế thừa, để trả về " A Circle with radius=xxx, which is a subclass of yyy", đây yyylà đầu ra của toString()phương thức từ lớp cha.
Các lớp Rectangle bao gồm:
  • Hai biến đối tượng widthdouble) và lengthdouble).
  • Ba Constructor như hình. Trong constructor không có đối số khởi tạo width và length bằng 1.0.
  • Getter và setter cho tất cả các biến thể hiện.
  • Phương thức getArea()và getPerimeter().
  • Ghi đè toString()phương thức được kế thừa, để trả về " A Rectangle with width=xxx and length=zzz, which is a subclass of yyy", đây yyy là đầu ra của toString()phương thức từ lớp cha.
Viết một lớp được gọi Square, như là một lớp con của RectangleTự thuyết phục bản thân Square có thể được mô hình hóa như là một lớp con của RectangleSquare không có biến đối tượng, nhưng kế thừa chiều rộng và chiều dài của biến đối tượng từ hình chữ nhật siêu lớp của nó.
  • Cung cấp các constructor thích hợp (như thể hiện trong sơ đồ lớp). Dấu:
  • public Square(double side) {
       super(side, side);  // Call superclass Rectangle(double, double)
    }
  • Ghi đè toString()phương thức để trả về " A Square with side=xxx, which is a subclass of yyy", đây yyylà đầu ra của toString()phương thức từ lớp cha.
  • Bạn có cần ghi đè lên getArea()và getPerimeter()Thử chúng và in ra kết quả ;)
  • Ghi đè setLength()và setWidth()thay đổi cả widthvà length, để duy trì hình dạng vuông.

5.  Exercises on Composition vs Inheritance

Chúng có hai cách để sử dụng lại một lớp trong các ứng dụng của bạn: thành phần và kế thừa .

5.1  Ex: The Point and Line Classes

Chúng ta hãy bắt đầu với thành phần với tuyên bố "một dòng gồm hai điểm".
Hoàn thành định nghĩa của hai lớp sau: Pointvà LineLớp Linebao gồm 2 trường hợp của lớp Point, đại diện cho điểm bắt đầu và điểm kết thúc của dòng. Cũng viết các lớp kiểm tra cho Pointvà Line(nói TestPointvà TestLine).
public class Point {
   // Private variables
   private int x;    // x co-ordinate
   private int y;    // y co-ordinate
   
   // Constructor
   public Point (int x, int y) {......}
   
   // Public methods
   public String toString() {
      return "Point: (" + x + "," + y + ")";
   }
   
   public int getX() {......}
   public int getY() {......}
   public void setX(int x) {......}
   public void setY(int y) {......}
   public void setXY(int x, int y) {......}
}
public class TestPoint {
   public static void main(String[] args) {
      Point p1 = new Point(10, 20);   // Construct a Point
      System.out.println(p1);
      // Try setting p1 to (100, 10).
      ......
   }
}
public class Line {
   // A line composes of two points (as instance variables)
   private Point begin;    // beginning point
   private Point end;      // ending point
   
   // Constructors
   public Line (Point begin, Point end) {  // caller to construct the Points
      this.begin = begin;
      ......
   }
   public Line (int beginX, int beginY, int endX, int endY) {
      begin = new Point(beginX, beginY);   // construct the Points here
      ......
   }
   
   // Public methods
   public String toString() { ...... }
   
   public Point getBegin() { ...... }
   public Point getEnd() { ...... }
   public void setBegin(......) { ...... }
   public void setEnd(......) { ...... }
   
   public int getBeginX() { ...... }
   public int getBeginY() { ...... }
   public int getEndX() { ...... }
   public int getEndY() { ...... }
   
   public void setBeginX(......) { ...... }
   public void setBeginY(......) { ...... }
   public void setBeginXY(......) { ...... }
   public void setEndX(......) { ...... }
   public void setEndY(......) { ...... }
   public void setEndXY(......) { ...... }
   
   public int getLength() { ...... } // Length of the line
                                     // Math.sqrt(xDiff*xDiff + yDiff*yDiff)
   public double getGradient() { ...... } // Gradient in radians
                                          // Math.atan2(yDiff, xDiff)
}
public class TestLine {
   public static void main(String[] args) {
      Line l1 = new Line(0, 0, 3, 4);
      System.out.println(l1);
   
      Point p1 = new Point(...);
      Point p2 = new Point(...);
      Line l2 = new Line(p1, p2);
      System.out.println(l2);
      ...
   }
}
Sơ đồ lớp cho thành phần như sau (trong đó một mũi tên đầu kim cương rỗng chỉ vào thành phần của nó):
ExerciseOOP_PointLineComp.png
Thay vì thành phần , chúng ta có thể thiết kế một Linelớp bằng cách sử dụng inheritanceThay vì "một dòng gồm hai điểm", chúng ta có thể nói rằng "một dòng là một điểm được mở rộng bởi một điểm khác", như thể hiện trong sơ đồ lớp sau:
ExerciseOOP_PointLineInherit.png
Chúng ta hãy thiết kế lại Linelớp (được gọi LineSub) là một lớp con của lớp PointLineSubkế thừa điểm bắt đầu từ siêu lớp của nó Pointvà thêm điểm kết thúc. Hoàn thành định nghĩa lớp. Viết một lớp kiểm tra được gọi TestLineSubđể kiểm tra LineSub.
public class LineSub extends Point {
   // A line needs two points: begin and end.
   // The begin point is inherited from its superclass Point.
   // Private variables
   Point end;               // Ending point
   
   // Constructors
   public LineSub (int beginX, int beginY, int endX, int endY) {
      super(beginX, beginY);             // construct the begin Point
      this.end = new Point(endX, endY);  // construct the end Point
   }
   public LineSub (Point begin, Point end) {  // caller to construct the Points
      super(begin.getX(), begin.getY());      // need to reconstruct the begin Point
      this.end = end;
   }
   
   // Public methods
   // Inherits methods getX() and getY() from superclass Point
   public String toString() { ... }
   
   public Point getBegin() { ... }
   public Point getEnd() { ... }
   public void setBegin(...) { ... }
   public void setEnd(...) { ... }
   
   public int getBeginX() { ... }
   public int getBeginY() { ... }
   public int getEndX() { ... }
   public int getEndY() { ... }
   
   public void setBeginX(...) { ... }
   public void setBeginY(...) { ... }
   public void setBeginXY(...) { ... }
   public void setEndX(...) { ... }
   public void setEndY(...) { ... }
   public void setEndXY(...) { ... }
   
   public int getLength() { ... }       // Length of the line
   public double getGradient() { ... }  // Gradient in radians
}
Tóm tắt: Có hai cách tiếp cận mà bạn có thể thiết kế một dòng, composition hoặc inheritance"Một dòng gồm hai điểm" hoặc "Một dòng là một điểm được mở rộng bằng một điểm khác". So sánh Linevà LineSub thiết kế: Line sử dụng thành phần và LineSub sử dụng kế thừa . Thiết kế nào tốt hơn?

5.2  Ex: The Circle and Cylinder Classes Using Composition

ExerciseOOP_CircleCylinderComp.png
Hãy thử viết lại Circle-Cylindercác bài tập trước sử dụng thành phần (như thể hiện trong sơ đồ lớp) thay vì thừa kế . Đó là, "một hình trụ bao gồm một vòng tròn cơ sở và chiều cao".
public class Cylinder {
   private Circle base;   // Base circle, an instance of Circle class
   private double height;
   
   // Constructor with default color, radius and height
   public Cylinder() {
      base = new Circle(); // Call the constructor to construct the Circle
      height = 1.0; 
   }
   ......
}
Thiết kế nào (kế thừa hoặc thành phần) là tốt hơn?

Post a Comment

Mới hơn Cũ hơn