Java多态实战:如何用Shape接口计算三角形、矩形和圆的周长(附完整代码)
Java多态实战:如何用Shape接口计算三角形、矩形和圆的周长(附完整代码)
在面向对象编程的世界里,多态性就像一把瑞士军刀,它能让你用统一的接口处理不同类型的对象。想象一下,你正在开发一个图形处理系统,需要计算各种形状的周长。如果没有多态,你可能需要写一堆if-else语句来判断图形类型;而有了多态,代码会变得优雅而富有扩展性。
让我们从一个实际案例出发:设计一个能够计算三角形、矩形和圆周长的系统。这个案例完美展示了多态如何简化代码结构,提高可维护性。我们将使用Java的接口特性来实现这一目标,同时确保代码足够健壮,能够处理各种边界情况。
1. 设计Shape接口与实现类
1.1 定义Shape接口
多态的核心在于定义一个统一的接口。对于图形计算系统,我们首先创建一个Shape接口:
public interface Shape { double calculatePerimeter(); }这个简单的接口声明了一个calculatePerimeter()方法(我们使用更具语义化的方法名,而不是简单的length()),所有实现该接口的类都必须提供这个方法的具体实现。
1.2 实现具体图形类
三角形类实现:
public class Triangle implements Shape { private final double sideA; private final double sideB; private final double sideC; public Triangle(double a, double b, double c) { if (!isValidTriangle(a, b, c)) { throw new IllegalArgumentException("Invalid triangle sides"); } this.sideA = a; this.sideB = b; this.sideC = c; } private boolean isValidTriangle(double a, double b, double c) { return a > 0 && b > 0 && c > 0 && a + b > c && a + c > b && b + c > a; } @Override public double calculatePerimeter() { return sideA + sideB + sideC; } }矩形类实现:
public class Rectangle implements Shape { private final double length; private final double width; public Rectangle(double length, double width) { if (length <= 0 || width <= 0) { throw new IllegalArgumentException("Length and width must be positive"); } this.length = length; this.width = width; } @Override public double calculatePerimeter() { return 2 * (length + width); } }圆形类实现:
public class Circle implements Shape { private static final double PI = 3.141592653589793; private final double radius; public Circle(double radius) { if (radius <= 0) { throw new IllegalArgumentException("Radius must be positive"); } this.radius = radius; } @Override public double calculatePerimeter() { return 2 * PI * radius; } }每个类都实现了Shape接口,但提供了不同的calculatePerimeter()实现,这就是多态的魅力所在。
2. 构建健壮的输入处理系统
2.1 输入验证与图形创建
我们需要一个能够根据用户输入创建相应图形对象的工厂类:
public class ShapeFactory { public static Shape createShape(double... dimensions) { if (dimensions == null || dimensions.length == 0 || dimensions.length > 3) { throw new IllegalArgumentException("Invalid number of dimensions"); } try { switch (dimensions.length) { case 1: return new Circle(dimensions[0]); case 2: return new Rectangle(dimensions[0], dimensions[1]); case 3: return new Triangle(dimensions[0], dimensions[1], dimensions[2]); default: throw new AssertionError("Unexpected number of dimensions"); } } catch (IllegalArgumentException e) { return new NullShape(); // 处理无效输入 } } private static class NullShape implements Shape { @Override public double calculatePerimeter() { return 0; } } }2.2 处理用户输入
下面是处理用户输入的主类实现:
import java.util.Scanner; public class ShapeCalculator { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Enter dimensions for shapes (1 for circle, 2 for rectangle, 3 for triangle):"); while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); if (line.isEmpty()) continue; String[] parts = line.split("\\s+"); double[] dimensions = new double[parts.length]; try { for (int i = 0; i < parts.length; i++) { dimensions[i] = Double.parseDouble(parts[i]); } Shape shape = ShapeFactory.createShape(dimensions); System.out.printf("Perimeter: %.2f%n", shape.calculatePerimeter()); } catch (NumberFormatException e) { System.out.println("Invalid input: please enter numbers only"); } catch (IllegalArgumentException e) { System.out.println("Error: " + e.getMessage()); } } scanner.close(); } }3. 多态的高级应用技巧
3.1 使用策略模式增强灵活性
我们可以进一步扩展系统,使其能够计算其他图形的周长,而无需修改现有代码:
public class Pentagon implements Shape { private final double side; public Pentagon(double side) { if (side <= 0) { throw new IllegalArgumentException("Side length must be positive"); } this.side = side; } @Override public double calculatePerimeter() { return 5 * side; } }只需在ShapeFactory中添加相应的case,系统就能支持五边形计算,这体现了开闭原则(对扩展开放,对修改关闭)。
3.2 使用集合处理多个图形
多态真正发挥威力是在处理图形集合时:
List<Shape> shapes = Arrays.asList( new Circle(5), new Rectangle(3, 4), new Triangle(3, 4, 5) ); double totalPerimeter = shapes.stream() .mapToDouble(Shape::calculatePerimeter) .sum(); System.out.printf("Total perimeter of all shapes: %.2f%n", totalPerimeter);这段代码展示了多态的强大之处——我们可以用统一的方式处理不同类型的对象。
4. 测试与异常处理
4.1 单元测试示例
良好的测试是健壮代码的保障。以下是使用JUnit的测试示例:
import org.junit.Test; import static org.junit.Assert.*; public class ShapeTest { private static final double DELTA = 0.0001; @Test public void testCirclePerimeter() { Shape circle = new Circle(1); assertEquals(2 * Math.PI, circle.calculatePerimeter(), DELTA); } @Test public void testRectanglePerimeter() { Shape rectangle = new Rectangle(3, 4); assertEquals(14, rectangle.calculatePerimeter(), DELTA); } @Test public void testTrianglePerimeter() { Shape triangle = new Triangle(3, 4, 5); assertEquals(12, triangle.calculatePerimeter(), DELTA); } @Test(expected = IllegalArgumentException.class) public void testInvalidTriangle() { new Triangle(1, 1, 3); } }4.2 性能优化考虑
对于高性能场景,我们可以做一些优化:
- 使用
final类和方怯避免虚方法调用开销 - 对于简单图形,考虑值对象模式
- 缓存常用计算结果(如圆周率倍数)
public final class OptimizedCircle implements Shape { private static final double TWO_PI = 2 * Math.PI; private final double radius; public OptimizedCircle(double radius) { this.radius = radius; } @Override public double calculatePerimeter() { return TWO_PI * radius; } }5. 实际项目中的应用建议
在实际项目中应用这种多态设计时,有几个经验值得分享:
- 文档很重要:为每个图形类添加清晰的JavaDoc,说明参数单位和边界条件
- 考虑国际化:周长计算可能因单位系统而异(米 vs 英尺)
- 日志记录:在工厂方法中添加适当的日志,便于调试
- 防御性编程:验证所有输入参数,避免后续计算错误
/** * Represents a geometric shape that can calculate its perimeter. */ public interface Shape { /** * Calculates the perimeter of the shape. * @return the perimeter in meters * @throws IllegalStateException if the shape is in invalid state */ double calculatePerimeter() throws IllegalStateException; }在团队协作中,确保所有实现类都遵循相同的约定和规范,这样多态才能真正发挥其价值。
