{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 05. Interface exercise\n", "\n", "예전에 다뤘던 `Obj2D` 클래스의 이름을 `Shape`으로 구성된 2차원 객체라는 의미에서\n", "`ShapeObj2D`로 클래스의 이름을 바꾸어 정의하자.\n", "\n", "그리고 이번 강의 노트에서는 여러 개의 `ShapeObj2D`를 포함하는 `GroupObj2D`이라는 클래스를 정의하려고 한다.\n", "`GroupObj2D`는 `ShapeObj2D` 배열을 인스턴스 변수로 갖도록 설계하자.\n", "\n", "`ShapeObj2D`와 `GroupObj2D`는 상속 관계(is-a 관계)가 아닌 포함 관계(has-a 관계)이므로\n", "상위(부모)클래스/하위(자녀)클래스 관계로는 나타낼 수 없지만, 공통적으로\n", "SVG 형식으로 화면에 내용을 나타낼 수 있는 동작을 하도록 설계하고자 한다.\n", "이럴 때 활용할 수 있는 것이 바로 지난 시간에 배웠던 인터페이스다.\n", "`ShapeObj2D`와 `GroupObj2D` 모두`ToSVG`라는 인터페이스를 구현하도록 하면 될 것이다.\n", "\n", "또한 `Displayable`이라는 `display` 메소드를 구현하도록 요구하는 인터페이스를 정의하였다.\n", "`ShapeObj2D`와 `GruopObj2D` 뿐만 아니라 `Shape` 클래스도 `Displayable`을 구현한다." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import org.apache.commons.lang3.tuple.*;\n", "\n", "interface Displayable {\n", " public void display();\n", "}\n", "\n", "interface ToSVG {\n", " public String toSVG();\n", " \n", " // points of the bounding rectangle\n", " public Integer minX();\n", " public Integer minY();\n", " public Integer maxX();\n", " public Integer maxY();\n", " // default methods using above four abstract methods\n", " default public Pair minPoint() {\n", " return Pair.of( this.minX(), this.minY() );\n", " }\n", " default public Pair maxPoint() {\n", " return Pair.of( this.maxX(), this.maxY() );\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import io.github.spencerpark.ijava.runtime.*;\n", "\n", "abstract class Shape implements Displayable {\n", " // 인스턴스 변수\n", " int width; // 양과 음의 정수값 모두 가능\n", " int height; // 양과 음의 정수값 모두 가능\n", " String fill; // 도형의 안쪽을 채우는 색깔\n", " double opacity; // 도형을 그렸을 때 투명도\n", " \n", " Shape(int width, int height, String fill, double opacity) {\n", " this.width = width;\n", " this.height = height;\n", " this.fill = fill;\n", " this.opacity = opacity;\n", " }\n", "\n", " // 추상 메소드 \n", " abstract double area(); // 넓이 계산\n", " abstract String toSVGshape(Pair point); // SVG 기본 도형 태그 생성\n", " \n", " @Override\n", " public String toString() {\n", " return super.toString()\n", " + String.format(\"(width=%d, height=%d, fill=%s, opacity=%f)\",\n", " width, height, fill, opacity );\n", " }\n", " \n", " @Override\n", " public void display() { // 이미지 형태로 보여주기 위한 메소드\n", " Pair point = Pair.of( (width<0)? Math.abs(width) :0,\n", " (height<0)? Math.abs(height):0 );\n", " String svgStr = String.format(\n", " \"%s\",\n", " Math.abs(width), Math.abs(height), this.toSVGshape(point) );\n", " Display.display(svgStr,\"text/html\");\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class RightTri extends Shape {\n", " RightTri(int width, int height, String fill, double opacity) {\n", " super(width, height, fill, opacity);\n", " }\n", " \n", " @Override\n", " double area() { return Math.abs(width * height) / 2; } // 삼각형 넓이공식에 맞게\n", " \n", " @Override\n", " String toSVGshape(Pair point) {\n", " int x0 = point.getLeft();\n", " int y0 = point.getRight();\n", " return\n", " String.format(\"\",\n", " x0,y0, fill, opacity)\n", " +\n", " String.format(\"\",\n", " x0,y0, x0+width,y0, x0,y0+height, fill, opacity );\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "class Rectangle extends Shape {\n", " Rectangle(int width, int height, String fill, double opacity) {\n", " super(width, height, fill, opacity);\n", " }\n", " \n", " @Override\n", " double area() { return Math.abs(width * height); } // 직사각형 넓이공식에 맞게\n", " \n", " @Override\n", " String toSVGshape(Pair point) {\n", " int x0 = point.getLeft();\n", " int y0 = point.getRight();\n", " return\n", " String.format(\"\",\n", " x0,y0, fill, opacity)\n", " +\n", " String.format(\"\",\n", " Math.abs(width), Math.abs(height), fill, opacity );\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new RightTri(20,40,\"red\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new RightTri(-20,40,\"green\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new RightTri(20,-40,\"purple\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new RightTri(-20,-40,\"brown\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new Rectangle(20,40,\"red\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new Rectangle(-20,40,\"green\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new Rectangle(20,-40,\"purple\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "new Rectangle(-20,-40,\"brown\",0.3).display()" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "class ShapeObj2D implements Displayable, ToSVG {\n", " Pair point;\n", " Shape shape;\n", " \n", " ShapeObj2D(Pair point, Shape shape) {\n", " this.point = point;\n", " this.shape = shape;\n", " }\n", " \n", " @Override\n", " public String toString() {\n", " return\n", " super.toString()\n", " +\n", " String.format(\"( point=%s, shape=%s )\",\n", " point.toString(), shape.toString() );\n", " }\n", " \n", " @Override // 지금은 무조건 원점부터 1사분면만 그리고 있다고 생각하고 작성하고 있음\n", " public void display() { // 이미지 형태로 보여주기 위한 메소드\n", " String svgStr = String.format(\n", " \"%s\",\n", " maxX(), maxY(), this.toSVG() );\n", " Display.display(svgStr,\"text/html\");\n", " }\n", "\n", " @Override\n", " public String toSVG() { return shape.toSVGshape(point); } \n", " // points of the bounding rectangle\n", " @Override\n", " public Integer minX() { return Math.min(point.getLeft(), point.getLeft()+shape.width); }\n", " @Override\n", " public Integer minY() { return Math.min(point.getRight(), point.getRight()+shape.height); }\n", " @Override\n", " public Integer maxX() { return Math.max(point.getLeft(), point.getLeft()+shape.width); }\n", " @Override\n", " public Integer maxY() { return Math.max(point.getRight(), point.getRight()+shape.height); }\n", "}" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(150,100)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Pair.of(150,100);" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "class org.apache.commons.lang3.tuple.ImmutablePair" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Pair.of(150,100).getClass()" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "ShapeObj2D otri1 = new ShapeObj2D( Pair.of(150,100), new RightTri(30,40,\"red\",0.3) );\n", "ShapeObj2D otri2 = new ShapeObj2D( Pair.of(150,100), new RightTri(-30,-40,\"red\",0.3) );" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$27$ShapeObj2D@fdb02d5( point=(150,100), shape=REPL.$JShell$17$RightTri@3e75d50f(width=30, height=40, fill=red, opacity=0.300000) )" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri1" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$27$ShapeObj2D@55722bd0( point=(150,100), shape=REPL.$JShell$17$RightTri@2554f7e6(width=-30, height=-40, fill=red, opacity=0.300000) )" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri2" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "REPL.$JShell$27$ShapeObj2D@4384f7d( point=(150,100), shape=REPL.$JShell$18$Rectangle@16b7f0ee(width=30, height=40, fill=blue, opacity=0.300000) )" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ShapeObj2D orect1 = new ShapeObj2D( Pair.of(150,100), new Rectangle(30,40,\"blue\",0.3) );\n", "\n", "orect1" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "orect1.toSVG()" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri1.toSVG()" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "otri1.display()" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri2.toSVG()" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "otri2.display()" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "ShapeObj2D otri3 = new ShapeObj2D( Pair.of(20,30), new RightTri(-30,-40,\"red\",0.3) );" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri3.toSVG()" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "otri3.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하나의 `Shape` 오브젝트로부터 여러 개의 `ShapeObj2D` 오브젝트를 만들 수 있다." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "Shape s = new RightTri(30,40,\"blue\",0.3);\n", "\n", "ShapeObj2D o1 = new ShapeObj2D( Pair.of(10,10), s );\n", "ShapeObj2D o2 = new ShapeObj2D( Pair.of(60,20), s );\n", "ShapeObj2D o3 = new ShapeObj2D( Pair.of(20,60), s );" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "o1.display()" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "o2.display()" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "o3.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "`Shape` 오브젝트와 `ShapeObj2D` 오브젝트도 `Displayable`이라는 공통된 인터페이스가 있으므로\n", "이들을 함께 모아 놓고 각각을 `display`하도록 일괄 처리가 가능하다." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s instanceof Shape" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s instanceof Displayable" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "o2 instanceof ShapeObj2D" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "o2 instanceof Displayable" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri1 instanceof ShapeObj2D" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "true" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "otri1 instanceof Displayable" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "Displayable[] ds = { s, otri1, o2 };" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ds[0].display()" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ds[1].display()" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ds[2].display()" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for (int i = 0; i < ds.length; ++i)\n", " ds[i].display()" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for (var d : ds)\n", " d.display()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "위에서는 배열 `ds` 여러 개의 Displayable에 대한 각각의 SVG이미지를 따로따로 반복해서 display했다.\n", "\n", "이제는 여러 개의 2차원 오브젝트(`Obj2D`)를 한데 그룹으로 묶어 같은 하나의 SVG 이미지안에 한꺼번에 나타내는\n", "`GroupObj2D` 클래스를 정의해 보라.\n", "\n", "(실습으로 진행하고 관련 내용으로 HW4 과제 진행 예정)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class GroupObj2D implements Displayable, ToSVG {\n", " ShapeObj2D[] objects;\n", " \n", " GroupObj2D(ShapeObj2D[] objects) {\n", " this.objects = objects;\n", " }\n", " \n", " /*\n", " @Override\n", " public String toString() { ... }\n", " */\n", " \n", " /*\n", " // Displayable에서 오버라이드하라고 요구하는 메소드를 구현\n", " */\n", "\n", " /*\n", " // ToSVG에서 오버라이드하라고 요구하는 메소드들을 구현\n", " */\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "GroupObj2D의 클래스를 완성하고 간한한 예제를 몇 개 시험해 보라.\n", " \n", "ShapeObj2D의 인스턴스 여러 개를 모아 하나의 GroupObj2D의 인스턴스를 만들어 SVG 이미지로 나타낼 수 있다.\n", "\n", "예를 들면,\n", " - 삼각형 모양의 ShapeObj2D 오브젝트 두 개로 이루어진 나비 형태의 GroupObj2D 오브젝트\n", " - 삼각형 모양의 ShapeObj2D 오브젝트 두 개와 사각형 모양의 ShapeObj2D 오브젝트 한 개로 이루어진 집 형태의 GroupObj2D 오브젝트." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "위와 같은 GruopObj2D를 코드를 완성하더라도 여전히 뭔가 아쉬운 점이 있다.\n", "\n", "왜냐하면 GroupObj2D 오브젝트를 여러 개 모아서 하나의 그룹으로 생각하는 것도 유용할 수 있기 때문이다.\n", "\n", "예를 들면,\n", " - 나비 형태의 GroupObj2D 오브젝트 여러 개로 모아서 나비떼를 나타내기\n", " - 집 형태의 GroupObj2D 오브젝트 여러 개로 마을을 나타내기\n", "\n", "그리고 일반적으로 GroupObj2D와 ShapeObj2D를 한꺼번에 모을 수도 있으면 더 편리할 것 같다.\n", "\n", "예를 들면, 집 형태의 GroupObj2D 오브젝트 여러 개와 큰 사각형 모양의 ShapeObj2D 오브젝트 하나로\n", "여러 개의 집들이 큰 사각형 모양의 마을 경계 안에 위치한 그림을 나타낼 수도 있다.\n", "\n", "\n", "이런 것까지 가능하도록 하려면 GroupObj2D 클래스의 정의를 어떻게 바꾸면 될지 생각해 보라." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Java", "language": "java", "name": "java" }, "language_info": { "name": "java" } }, "nbformat": 4, "nbformat_minor": 4 }