Project_01 : 상속과 인터페이스 예제 만들기
상속과 인터페이스의 관계를 이해하기 위한 Shape 예제를 만들어 보자.
abstract class Shape | ||
class Circle | class Quadrangle | interface Shape2D |
class Cylinder | class Cube | interface Shape3D |
<표 1> 예제 초기 설계
- abstract class Shape
👉 Shape 클래스는 모든 도형의 최상위 부모 클래스로,
직접적인 인스턴스 생성이 필요 없는 클래스이기 때문에 추상 클래스로 구현한다.
👉 모든 도형은 넓이를 구하는 메서드가 필요로 하지만 구현부가 제각기이기 때문에 추상 메서드로 구현한다.
- class Circle, class Quadrangle
👉 원은 반지름을 멤버로 가지고 있고, 사각형은 가로와 세로를 멤버로 가지고 있다.
👉 평면 도형은 각각 둘레와 넓이를 구하는 메서드가 존재해야 한다.
- class Cylinder, class Cube
👉 Shape 클래스를 상속 받은 자식 클래스 중 각각의 입체 도형은 평면 도형을 포함하고 있다.
👉 입체 도형은 각각 부피와 넓이를 구하는 메서드가 존재해야 한다.
👉 입체 도형은 높이를 나타내는 멤버를 가지고 있다.
- interface Shape2D
👉 평면 도형 클래스는 공통적으로 둘레를 구하는 메서드가 필요하지만 구현 내용은 다르다.
- interface Shape3D
👉 입체 도형 클래스는 공통적으로 부피를 구하는 메서드가 필요하다.
초기 설계 내용을 바탕으로 구현해 본 코드는 다음과 같다.
- abstract class Shape
public abstract class Shape {
// 넓이
public abstract double area();
}
- class Circle
public class Circle extends Shape implements Shape2D {
// 반지름
private int r;
// 둘레
@Override
public double circumference() {
return 2 * r * Math.PI;
}
// 넓이
@Override
public double area() {
retrun r * r * Math.PI;
}
}
- class Quadrangle
public class Quadrangle extends Shape implements Shape2D {
// 가로
private int x;
// 세로
private int y;
// 둘레
@Override
public double circumference() {
return (x + y) * 2;
}
// 넓이
@Override
public double area() {
retrun x * y;
}
}
- class Cylinder
public class Cylinder extends Shape implements Shape3D {
// 높이
private int h;
// 원
private Circle circle;
// 겉넓이
@Override
public double area() {
return (circle.area() * 2) + (circle.circumference() * h);
}
// 부피
@Override
public double volume() {
return circle.area() * h;
}
}
- class Cube
public class Cube extends Shape implements Shape3D {
// 높이
private int h;
// 사각형
private Quadrangle quadrangle;
// 겉넓이
@Override
public double area() {
return (quadrangle.circumference() * h) + (quadrangle.area() * 2);
}
// 부피
@Override
public double volume() {
return rectangle.area() * h;
}
- interface Shape2D
public interface Shape2D {
double circumference();
}
- interface Shape3D
public interface Shape3D {
double volume();
}
하지만 이렇게 구현을 하고 보니 입체 도형의 클래스에 구현한 메서드의 형태가 굉장히 유사하다는 것을 알게 되었다.
메서드에서 달라지는 부분은 포함하고 있는 평면 도형에 있었다.
이로 인해 입체 도형의 겉넓이와 부피를 구할 때마다 평면 도형이 유동적으로 달라지려면,
어떻게 구현해야 할지 고민하게 되었다.
1) interface Shape3D, class Cylinder, class Cube 삭제 ➡️ class Shape3D
일단 입체 도형의 클래스 구현은 동일하기 때문에 동일한 부분은 하나의 클래스로 구현하는 것으로 변경하였다.
2) interface Shape2D 멤버 변수화
그리고 평면 도형의 인터페이스였던 Shape2D 를 멤버 변수로 작성하였다.
자바의 다형성을 이용하면 부모 클래스 또는 인터페이스 타입으로 자식 클래스 인스턴스를 사용할 수 있는데,
이러한 특성을 바탕으로 다양한 평면 도형을 입체 도형에서 사용할 수 있도록 구현하였다.
Shape 클래스를 멤버 변수로 하지 않은 이유는, 입체 도형 또한 Shape 클래스를 상속하고 있기 때문에
인스턴스 생성 시 입체 도형이 자리하게 될 수도 있는 가능성이 있기 때문이었다.
이를 통해 재구성한 클래스는 다음과 같다.
abstract class Shape | ||
class Circle | class Quadrangle | interface Shape2D |
class Shape3D |
<표 2> 예제 수정 설계
public class Shape3D extends Shape {
private int h;
private Shape2D shape2D;
// 입체 도형 생성 시 평면 도형을 넣는다.
public Shape3D(Shape2D shape2d, int h) {
this.shape2D = shape2d;
this.h = h;
}
// 겉넓이
@Override
public double area() {
// 평면 도형의 넓이는 인터페이스에서 구현하지 않았기 때문에
// Shape 타입으로 형변환 해줘야 한다.
return ((Shape)shape2D).area() * 2 + shape2D.circumference() * h;
}
// 부피
public double volume() {
return shape2D.circumference() * h;
}
}