要了解有关OOP的更多信息,@ nhgrif要求我实现Shape抽象类(以下代码中的更多详细信息。这是我的操作方法。任何和所有建议都值得赞赏!

Shape.java

/* nhgrif says:
 * Phrancis ready for inheritance/polymorphism?
 * Given the following abstract class:
 *
 *  public abstract class Shape {
 *      public abstract double area();
 *      public abstract double perimeter();
 *  }
 *
 * Implement a Circle, Triangle, and Rectangle class which extend the class Shape.
 * Ex: public class Circle extends Shape ... etc
 */

public abstract class Shape {
    public abstract double area();
    public abstract double perimeter();
}


Rectangle.java

public class Rectangle extends Shape {
    private final double width, length; //sides

    public Rectangle() {
        this(1,1);
    }
    public Rectangle(double width, double length) {
        this.width = width;
        this.length = length;
    }

    @Override
    public double area() {
        // A = w * l
        return width * length;
    }

    @Override
    public double perimeter() {
        // P = 2(w + l)
        return 2 * (width + length);
    }

}


Circle.java

public class Circle extends Shape {
    private final double radius;
    final double pi = Math.PI;

    public Circle() {
        this(1);
    }   
    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        // A = π r^2
        return pi * Math.pow(radius, 2);
    }

    public double perimeter() {
        // P = 2πr
        return 2 * pi * radius;
    }
}


Triangle.java

public class Triangle extends Shape {
    private final double a, b, c; // sides

    public Triangle() {
        this(1,1,1);
    }
    public Triangle(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public double area() {
        // Heron's formula:
        // A = SquareRoot(s * (s - a) * (s - b) * (s - c)) 
        // where s = (a + b + c) / 2, or 1/2 of the perimeter of the triangle 
        double s = (a + b + c) / 2;
        return Math.sqrt(s * (s - a) * (s - b) * (s - c));
    }

    @Override
    public double perimeter() {
        // P = a + b + c
        return a + b + c;
    }
}


这就是我用来测试所有内容的东西,所有这些都按预期工作:

TestShape.java

public class TestShape {
    public static void main(String[] args) {

        // Rectangle test
        double width = 5, length = 7;
        Shape rectangle = new Rectangle(width, length);
        System.out.println("Rectangle width: " + width + " and length: " + length
                + "\nResulting area: " + rectangle.area()
                + "\nResulting perimeter: " + rectangle.perimeter() + "\n");

        // Circle test
        double radius = 5;
        Shape circle = new Circle(radius);
        System.out.println("Circle radius: " + radius
            + "\nResulting Area: " + circle.area()
            + "\nResulting Perimeter: " + circle.perimeter() + "\n");

        // Triangle test
        double a = 5, b = 3, c = 4;
        Shape triangle = new Triangle(a,b,c);
        System.out.println("Triangle sides lengths: " + a + ", " + b + ", " + c
                + "\nResulting Area: " + triangle.area()
                + "\nResulting Perimeter: " + triangle.perimeter() + "\n");
    }
}


评论

现在执行棘手的操作:椭圆和正方形。

好的正方形可以扩展矩形。椭圆肯定会很有趣。

是的,并且矩形会一直延伸到矩形。

正方形绝对不能延伸矩形,反之亦然。这直接违反了Liskov替代原理,并可能导致意想不到的副作用。

仅当@PeteBecker Square不可变时才允许使用它们。禁止使用其他方法。

#1 楼

通常,我对实现的一致性,简洁性等印象深刻。

我对基本前提有一个评论。 Shape不应是抽象类,而应该是接口。它没有任何方法的具体实现,并且使其成为抽象类也使得很难从其他地方继承。相反,请考虑以下问题:

public interface Shape {
    public double area();
    public double perimeter();
}


除了这种担忧之外,您还应考虑以下几点:您的pi类别上的字段应为私有字段。无需以其他方式重新公开已经公开的常量。
我会避免使用“ unit”默认构造函数。他们没有任何帮助。什么时候有人要调用Circle而不想要尺寸?
您的类缺少new Triangle()方法。由于许多原因,这些功能很有用,尤其是在调试时。负,NaN或无限输入将如何处理?


评论


\ $ \ begingroup \ $
我什至建议不要使用pi字段,而直接使用Math.PI。或者做一个导入静态java.lang.Math.PI;并直接使用PI
\ $ \ endgroup \ $
–伊沃·贝克斯(Ivo Beckers)
15年3月11日在11:25

\ $ \ begingroup \ $
接口方法默认情况下是公共的。 (我的宠儿之一是多余的修饰符-Java已经足够冗长了)。
\ $ \ endgroup \ $
–蜘蛛鲍里斯(Boris)
15年3月11日在12:19

\ $ \ begingroup \ $
@BoristheSpider-是的,默认情况下它们是公开的,我理解您的关注,您是对的,应该指出。碰巧的是,我在从事代码风格准则要求的工作时学习了Java,这是我一直保持的习惯,现在我更喜欢将其明确。我并不是说我是对的,但是我声称如果我将它编辑出来,那不是我的答案;-)
\ $ \ endgroup \ $
–rolfl
15年3月11日在12:24

#2 楼

名称上的小问题:矩形的尺寸应为widthheight而不是widthlength。 Triangle的构造函数应使用描述性参数名称,例如side1Length。您有更多借口将ab用于私有字段(尤其是在此简短的纯数学代码中),但是通常不赞成使用单字符名称。在您的抽象类/接口中。 Shape类的注释几乎是一个Javadoc,但缺少一个*

为了获得额外的荣誉,用3个单元测试(1个测试类和3个方法)替换main()方法很容易从IDE运行。

评论


\ $ \ begingroup \ $
我建议为每个实现使用一个单元测试类-这是公认的标准方法。除此之外,所有的优点都很好。
\ $ \ endgroup \ $
–蜘蛛鲍里斯(Boris)
2015年3月11日在12:21

\ $ \ begingroup \ $
@BoristheSpider没错,但是我个人不会在这种琐碎的情况下坚定地坚持这一规则,而是希望减少测试类的数量并使测试代码重用只是私有方法testShape(Shape ShapeShape,类ShapeTest中的shape,double ExpectedArea,Double ExpectedPerimiter)而不是为其构建继承层次结构。
\ $ \ endgroup \ $
–亚历山大·杜宾斯基(Aleksandr Dubinsky)
2015年3月11日15:31

#3 楼

我很高兴您这样做,这使我有机会进一步执行/更正某些错误信息。 br />

静态,因为pi的值不依赖于Circle的任何实例
最终,因为您当然不希望对其进行更改
大写,因为这两个东西都被认为是常量,因此是命名约定。

但是,当您可以直接使用final double pi = Math.PI;时,不需要占用名称空间或内存。例如,您的PI方法的实现可能是:

Math.PI

出于相同的原因,而不是使用其他地方没有使用的占位符变量:

double a = 5, b = 3, c = 4;
Shape triangle = new Triangle(a,b,c);


简单地使用它们

Shape triangle = new Triangle(5, 3, 4);


如果您选择,a,b和c开头的名称就不太合适要使用它们,在这种情况下,请尝试使用更具描述性的名称,例如base,side1和side2。未来,只要确保您了解,当您依赖仅存在于子类中的特定方法时,就必须使用该子类进行声明。

评论


\ $ \ begingroup \ $
占位符变量提高了可读性。
\ $ \ endgroup \ $
–亚历山大·杜宾斯基(Aleksandr Dubinsky)
2015年3月10日23:21



\ $ \ begingroup \ $
我不同意a,b和c不是好名字。虽然对字段使用多个连续的单字母名称通常会是一种代码味道,但在这种情况下,它们是适当的。它们是三角形三个边的标准名称,并且在三角形几何图形中无处不在。称它们为base,side1和side2错误地表明这三个具有不同的含义。
\ $ \ endgroup \ $
– jwg
15年3月11日在11:41

#4 楼

实际上,一个较小的nitpick:

// where s = (a + b + c) / 2, or 1/2 of the perimeter of the triangle 
double s = (a + b + c) / 2;


在注释中,您指的是三角形的周长,但是在您的代码中,您并未使用实际的周长。这是非常小的代码重复。如果您愿意的话,您的代码会更加自我记录:

double s = perimeter() / 2;



我个人对a,b,c名称一无所知案件。在数学中经常会给双方起名字,我认为在这里也给它们起名字并不可怕。

评论


\ $ \ begingroup \ $
这是一个小问题,但非常好。
\ $ \ endgroup \ $
– jwg
15年3月11日在11:42

#5 楼

我个人认为,如果这些是不可变的类,则默认的无参数构造函数是非常不必要的。默认情况下,宽度为1且高度为1的矩形有意义吗?在适当的方法中。

我还认为方法的命名可能会更好。代替area(),我将使用getArea()

修改后的Rectangle如下所示(注意Shape类也需要编辑):

public class Rectangle extends Shape {
  private final double width, height, area, perimeter;

  public Rectangle(double width, double height) {
    this.width = width;
    this.height= height;
    this.area = width * height;
    this.perimeter = 2 * (width + height);
  }

  @Override
  public double getArea() {
    return this.area;
  }

  @Override
  public double getPerimeter() {
    return this.perimeter;
  }

}


评论


\ $ \ begingroup \ $
getX通常是该方法返回预定义值的标志。此处是这种情况,但不适合其实施。我确实没有看到名称的问题,但是如果他必须更改名称,我可能会选择computeX
\ $ \ endgroup \ $
–莱加托
15年3月11日在12:43

\ $ \ begingroup \ $
对..或者也许是calculateArea()?
\ $ \ endgroup \ $
– juunas
15年3月11日在13:20

\ $ \ begingroup \ $
同样有效。
\ $ \ endgroup \ $
–莱加托
15年3月11日在13:21

\ $ \ begingroup \ $
从Effective Java:例如,返回调用对象的非布尔函数或属性的方法通常以名词,名词短语或以动词get开头的动词短语来命名。 ,大小,hashCode或getTime。有一个声音特遣队声称只有第三种形式(以get开头)是可以接受的,但是这种主张没有任何依据。前两种形式通常会导致代码更具可读性。很少使用非getter方法的值类可以使用诸如area()之类的名词。 computeArea建议该函数具有副作用或明显较慢。
\ $ \ endgroup \ $
–亚历山大·杜宾斯基(Aleksandr Dubinsky)
2015年3月11日15:47