본문 바로가기
Java 강의

자바 Cloneable 사용방법 / 자바 디자인 패턴 - 프로토 타입 패턴 / java design pattern , prototype pattern

by 자유코딩 2017. 9. 29.

안녕하세요 이번 글에서는 Cloneable의 사용방법과 프로토 타입 패턴에 대해서 알아보도록 하겠습니다

 

먼저 Cloneable의 사용방법과 배경에 대해서 알아보겠습니다

 

Cloneable의 배경

 

여기 다음과 같은 소스코드가 있습니다

 

프로그래머는 객체의 주소가 아닌 값들만 복사하고 싶은 상황을 가정해보겠습니다

 

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package BackGroundCloneable;
 
public class Background {
    public static void main(String[] args) {
        ClassForCopy c1 = new ClassForCopy(0null0false0000null00false00);
        
        ClassForCopy c2 = new ClassForCopy(100"possible"200true300400500600"programming"700080000true9000010000);
        
        c1.setA(c2.getA());//c1의 setA메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setAaa(c2.getAaa());//c1의 setAaa메소드를 호출하고 c객체의 힙영역의 값을 대입한다
        c1.setB(c2.getB());//c1의 setB메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setBbb(c2.getBbb());//c1의 setBbb메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setC(c2.getC());//c1의 setC메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setD(c2.isD());//c1의 setD메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setDdd(c2.isDdd());//c1의 setDdd메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setE(c2.getE());//c1의 setE메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setEee(c2.getEee());//c1의 setEee메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setF(c2.getF());//c1의 setF메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setFff(c2.getFff());//c1의 setFff메소드를 호출하고 c2 객체의 힙영역의 값을 대입한다
        c1.setG(c2.getG());//c1의 setG메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
        c1.setGgg(c2.getGgg());//c1의 setGgg메소드를 호출하고 c2객체의 힙영역의 값을 대입한다
    }
}
cs

 

main 함수의 ClassForCopy 에 해당하는 ClassForCopy클래스 입니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package BackGroundCloneable;
 
public class ClassForCopy {
    private int a;//ClassForCopy 클래스의 변수를 선언한다
    private String b;//ClassForCopy 클래스의 변수를 선언한다
    private int c;//ClassForCopy 클래스의 변수를 선언한다
    private boolean d;//ClassForCopy 클래스의 변수를 선언한다
    private int e;//ClassForCopy 클래스의 변수를 선언한다
    private long f;//ClassForCopy 클래스의 변수를 선언한다
    private double g;//ClassForCopy 클래스의 변수를 선언한다
    private int aaa;//ClassForCopy 클래스의 변수를 선언한다
    private String bbb;//ClassForCopy 클래스의 변수를 선언한다
    private int ccc;//ClassForCopy 클래스의 변수를 선언한다
    private int eee;//ClassForCopy 클래스의 변수를 선언한다
    private boolean ddd;//ClassForCopy 클래스의 변수를 선언한다
    private long fff;//ClassForCopy 클래스의 변수를 선언한다
    private double ggg;//ClassForCopy 클래스의 변수를 선언한다
    public ClassForCopy(int a, String b, int i, boolean d, int j, long f, double g, int aaa, String bbb, int k, int l, boolean ddd, long fff, double ggg) {
        super();
        this.a = a;//main함수에서 입력할 값으로 변수 a를 초기화한다
        this.b = b;//main함수에서 입력할 값으로 변수 b를 초기화한다
        this.c = i;//main함수에서 입력할 값으로 변수 c를 초기화한다
        this.d = d;//main함수에서 입력할 값으로 변수 d를 초기화한다
        this.e = j;//main함수에서 입력할 값으로 변수 e를 초기화한다
        this.f = f;//main함수에서 입력할 값으로 변수 f를 초기화한다
        this.g = g;//main함수에서 입력할 값으로 변수 g를 초기화한다
        this.aaa = aaa;//main함수에서 입력할 값으로 변수 aaa를 초기화한다
        this.bbb = bbb;//main함수에서 입력할 값으로 변수 bbb를 초기화한다
        this.ccc = k;//main함수에서 입력할 값으로 변수 ccc를 초기화한다
        this.eee = l;//main함수에서 입력할 값으로 변수 eee를 초기화한다
        this.ddd = ddd;//main함수에서 입력할 값으로 변수 ddd를 초기화한다
        this.fff = fff;//main함수에서 입력할 값으로 변수 fff를 초기화한다
        this.ggg = ggg;//main함수에서 입력할 값으로 변수 ggg를 초기화한다
    }
    public int getA() {//ClassForCopy 클래스의 getter와 setter 선언
        return a;
    }
    public void setA(int a) {//ClassForCopy 클래스의 getter와 setter 선언
        this.a = a;
    }
    public String getB() {//ClassForCopy 클래스의 getter와 setter 선언
        return b;
    }
    public void setB(String b) {//ClassForCopy 클래스의 getter와 setter 선언
        this.b = b;
    }
    public int getC() {//ClassForCopy 클래스의 getter와 setter 선언
        return c;
    }
    public void setC(int c) {//ClassForCopy 클래스의 getter와 setter 선언
        this.c = c;
    }
    public boolean isD() {//ClassForCopy 클래스의 getter와 setter 선언
        return d;
    }
    public void setD(boolean d) {//ClassForCopy 클래스의 getter와 setter 선언
        this.d = d;
    }
    public int getE() {//ClassForCopy 클래스의 getter와 setter 선언
        return e;
    }
    public void setE(int e) {//ClassForCopy 클래스의 getter와 setter 선언
        this.e = e;
    }
    public long getF() {//ClassForCopy 클래스의 getter와 setter 선언
        return f;
    }
    public void setF(long f) {//ClassForCopy 클래스의 getter와 setter 선언
        this.f = f;
    }
    public double getG() {//ClassForCopy 클래스의 getter와 setter 선언
        return g;
    }
    public void setG(double g) {//ClassForCopy 클래스의 getter와 setter 선언
        this.g = g;
    }
    public int getAaa() {//ClassForCopy 클래스의 getter와 setter 선언
        return aaa;
    }
    public void setAaa(int aaa) {//ClassForCopy 클래스의 getter와 setter 선언
        this.aaa = aaa;
    }
    public String getBbb() {//ClassForCopy 클래스의 getter와 setter 선언
        return bbb;
    }
    public void setBbb(String bbb) {//ClassForCopy 클래스의 getter와 setter 선언
        this.bbb = bbb;
    }
    public int getCcc() {//ClassForCopy 클래스의 getter와 setter 선언
        return ccc;
    }
    public void setCcc(int ccc) {//ClassForCopy 클래스의 getter와 setter 선언
        this.ccc = ccc;
    }
    public int getEee() {//ClassForCopy 클래스의 getter와 setter 선언
        return eee;
    }
    public void setEee(int eee) {//ClassForCopy 클래스의 getter와 setter 선언
        this.eee = eee;
    }
    public boolean isDdd() {//ClassForCopy 클래스의 getter와 setter 선언
        return ddd;
    }
    public void setDdd(boolean ddd) {//ClassForCopy 클래스의 getter와 setter 선언
        this.ddd = ddd;
    }
    public long getFff() {//ClassForCopy 클래스의 getter와 setter 선언
        return fff;
    }
    public void setFff(long fff) {//ClassForCopy 클래스의 getter와 setter 선언
        this.fff = fff;
    }
    public double getGgg() {//ClassForCopy 클래스의 getter와 setter 선언
        return ggg;
    }
    public void setGgg(double ggg) {//ClassForCopy 클래스의 getter와 setter 선언
        this.ggg = ggg;
    }
}
cs

 

다음과 같은 코드에서 객체가 가진 힙영역의 값을 복사 하려면 어떻게 해야 할까요?

 

아마도 그림처럼 양쪽에 소스코드 창을 띄워놓고 손으로 계속 입력해야 할 것입니다.

 

 

이렇게 긴 입력을 하지 않고 간편하게 객체1의 힙영역에 저장된 값을 객체 2의 힙영역에 저장된 값과 같게 해주는 방법이 있습니다

 

Cloneable 과 프로토타입 패턴을 사용하면 됩니다

 

먼저 Cloneable을 사용해서 객체의 값을 복사하는 방법에 대해서 알아보겠습니다

 

Cloneable의 사용 방법

 

Car클래스를 선언하고 정의합니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package Proto;
 
public class Car implements Cloneable{
    private int maximumSpeed;//Car객체의 maximumSpeed 속성
    private int minimumSpeed;//Car객체의 minimumSpeed 속성
    private String name;//Car객체의 이름속성
    private double fuelByDistance;//Car객체의 연비 속성
    private String Color;//Car객체의 색상 속성
    private int year;//Car객체의 출시년도 속성
    private double lamp;//Car객체의 램프의 밝기 속성
    private double weight;//Car객체의 무게 속성
    private double height;//Car객체의 크기 속성
    private int sizeOfWindow;//Car객체의 창문크기 속성
    @Override
    protected Object clone() throws CloneNotSupportedException {//Cloneable 인터페이스로부터 오버라이드해서 clone()메소드를 정의한다
        // TODO Auto-generated method stub
        return super.clone();//객체가 힙영역에 가지고 있는 값이 복사되지만  main에서 형변환을 해줘야한다
    }
    public Car copy() throws CloneNotSupportedException {//clone()메소드의 경우에는 Object타입이다.Car타입에 사용할 수 있는 copy()메소드를 생성한다
        Car car = (Car)clone();//clone을 사용하고 있다 이렇게 형변환을 해주면 main에서 형변환을 하지 않아도 된다
        return car;
        // TODO Auto-generated method stub
 
    }
    //아래 getter와 setter대신에 함수를 이용해서 값을 설정하고 출력했다
    public Car(int maximumSpeed, int minimumSpeed, String name, double fuelByDistance, 
            String color, int year,double lamp, double weight, double height, int sizeOfWindow) {
        super();
        this.maximumSpeed = maximumSpeed;//Car클래스의 maximumSpeed 변수를 초기화한다
        this.minimumSpeed = minimumSpeed;//Car클래스의 minimumSpeed 변수를 초기화한다
        this.name = name;//Car클래스의 name 변수를 초기화한다
        this.fuelByDistance = fuelByDistance;//Car클래스의 fuelByDistance 변수를 초기화한다
        Color = color;//Car클래스의 color 변수를 초기화한다
        this.year = year;//Car클래스의 year 변수를 초기화한다
        this.lamp = lamp;//Car클래스의 lamp 변수를 초기화한다
        this.weight = weight;//Car클래스의 weight 변수를 초기화한다
        this.height = height;//Car클래스의 height 변수를 초기화한다
        this.sizeOfWindow = sizeOfWindow;//Car클래스의 sizeOfWindow 변수를 초기화한다
    }
    public void printCarAttribute() {//객체의 속성을 출력할 printCarAttribe 함수를 선언하고 정의한다
        System.out.println("최대 속력"+maximumSpeed);
        System.out.println("최소 속력"+minimumSpeed);
        System.out.println("이름 : "+name);
        System.out.println("연비 : "+fuelByDistance);
        System.out.println("색상 : "+Color);
        System.out.println("출시년도 : "+year);
        System.out.println("램프 밝기 : "+lamp);
        System.out.println("무게 : "+weight);
        System.out.println("높이 : "+height);
        System.out.println("창문크기 : "+sizeOfWindow);
        
    }
}
cs

 

Car클래스의 인스턴스를 만드는 main함수를 선언하고 정의합니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Proto;
 
public class Proto {
    public static void main(String[] args) throws CloneNotSupportedException {
        Car car = new Car(1000"benz"10"black"20171001000500200);
        //객체  car를 생성하고 초기화한다
        Car car2 = (Car) car.clone();//아래 copy는 타입을 Car로 지정했기 때문에 형변환이 필요없다
        //Cloneable인터페이스의 clone 메소드를 사용해서 Car 객체의 힙영역의 값을 car2객체의 힙영역에 저장한다
        Car car3 = car.copy();//copy메소드를 이용해서 car객체의 힙영역의 값을 car3객체의 힙영역에 저장한다
        
        car.printCarAttribute();//객체의 힙영역의 데이터를 출력하는 함수 호출
        System.out.println();//console 창의 줄을 바꿔주기 위해 사용
        car2.printCarAttribute();//객체의 힙영역의 데이터를 출력하는 함수 호출
        System.out.println();//console 창의 줄을 바꿔주기 위해 사용
        car3.printCarAttribute();//객체의 힙영역의 데이터를 출력하는 함수 호출
        
        System.out.println(car);//car 객체의 주소 출력
        System.out.println(car2);//car2 객체의 주소 출력
        System.out.println(car3);//car3 객체의 주소 출력
    }
}
cs

 

첫번째 소스코드보다 main부분이 많이 간소화 되었습니다

 

소스코드의 주석에 설명을 적었습니다

 

clone함수를 main 에서 사용하기 위해서는 throws CloneNotSupportedException 을 적어 주어야 합니다

 

cloneable interface로부터 override한 후에 Car타입으로 형변환을 해주지 않으면 main에서 형변환을 해줘야 합니다

 

그래서 copy()에서는 메소드의 정의 부분에서 형변환을 해주고 main에서는 형변환을 생략했습니다

 

두 방법 모두 객체의 힙영역의 값을 복사한다는 것은 같습니다

 

그러면 이제 Cloneable을 사용한 프로토 타입 패턴에 대해서 알아보도록 하겠습니다

 

프로토 타입 패턴의 정의는 다음과 같습니다

 

어떤 클래스의 인스턴스를 만드는게 자원/시간이 많이 소요되거나 복잡한 경우에 사용하는 디자인 패턴

 

만약에 하나의 객체가 가진 속성들이 굉장히 많은데 객체의 값을 복사 해야 할때 인스턴스를 복제하여 사용하는 구조입니다

 

생성할 객체들의 타입이 프로토타입 인스턴스로부터 결정되도록 하는 디자인 패턴입니다

 

인스턴스는 새 객체를 만들기 위해 자신을 복제(clone)합니다

 

프로토 타입 패턴의 핵심은 생산비용이 높은 인스턴스를 복사하는 것을 말합니다

 

인스턴스 생산 비용이 높은 경우는 다음과 같습니다

 

 종류가 너무 많아서 클래스로 정리되지 않는 경우

 클래스로부터 인스턴스 생성이 어려운 경우

 

프로토 타입 패턴은 다음과 같은 경우에 사용합니다

 

소스코드는 다음과 같습니다

 

1
2
3
4
5
6
7
8
9
10
11
12
13
package Prototype;
 
public class Shape implements Cloneable{//도형 클래스 선언 후 Cloneable 인터페이스 implements
    private String id;
    private void setid(String id) {
        this.id =id;
        // TODO Auto-generated method stub
    }
    private String getid() {
        return id;
        // TODO Auto-generated method stub
    }
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package Prototype;
 
public class Circle extends Shape{
    int x,y,r;
    public Circle copy() throws CloneNotSupportedException{//Circle타입의 copy함수를 선언한다
        Circle circle  =(Circle)clone();//clone을 사용해서 객체 힙 영역의 값을 복사한다
        return circle;
    }
    public int getX() {//게터 선언
        return x;
    }
    public void setX(int x) {//세터 선언
        this.x = x;
    }
    public int getY() {//getter 선언
        return y;
    }
    public void setY(int y) {//setter 선언
        this.y = y;
    }
    public int getR() {//getter 선언
        return r;
    }
    public void setR(int r) {//setter선언
        this.r = r;
    }
    public Circle(int x, int y, int r) {//Circle 생성자 선언
        super();
        this.x = x;
        this.y = y;
        this.r = r;
    }
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Prototype;
 
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Circle circle1 = new Circle(113);
        Circle circle2 = circle1.copy();
        
        System.out.println(
                circle1.getX()+","+circle1.getY()+
                ","+circle1.getR());
        System.out.println(circle2.getX()+","
                +circle2.getY()+","+circle2.getR());
        circle2.setX(2);
        System.out.println(circle2.getX());//circle2.getX가 2로 바뀐다
        System.out.println(circle1.getX());//circle1.getX는 그대로 1이다
        
        System.out.println(circle2);//주소를 출력해보면 서로 다른 주소를 가지고 있다
        System.out.println(circle1);//copy 함수를 이용해서 깊은 복사를 했음을 확인 할 수 있다
        
    }
}
cs

 

Circle 클래스의 copy메소드에서 CloneNotSupportedException을 사용할 수 있는 이유는

Circle클래스의 상위 클래스인 Shape 클래스가 Cloneable을 implements 하고 있기 때문입니다

 

클래스의 구조도를 보겠습니다

 

소스코드에 대한 설명은 주석으로 달아 두었습니다

Cloneable 인터페이스를 Shape 클래스가 implements 하고 Circle 클래스는 Shape클래스를 상속 받습니다

단순한 Cloneable의 사용 하는 방법을 보여드리기 위한 예시에서는 클래스의 개수가 더 적었습니다

그러나 조금 더 객체지향적으 소스코드의 예시를 만들었습니다

 

댓글