CS/디자인패턴

[디자인패턴] Prototype 패턴 (프로토타입)

mabb 2023. 6. 1. 16:22
반응형

▶디자인 패턴 ( Design Pattern)
:프로그램 개발 시 문제 해결을 위하여 빈번히 사용되는 개발자들의 경험, 내적인 축적에 대하여,
GoF(Gang of Four)라 불리는 4인의 개발자들이 각각을 패턴으로 정의하고 이름을 붙였다.
이를 디자인 패턴 (Design Pattern) 이라고 한다.
 23개의 디자인 패턴을
『Elements of Reusable Object-Oriented Software』
라는 책으로 발간하였다.

Gang of Four 분들은 모두 안경을 썼다.

 
▷디자인 패턴의 용어를 빌리면 서로의 아이디어를 보다 용이하게 비교, 논의할 수 있게 된다.
▷재사용과 기능확장이 쉬운 소프트웨어를 만들기 위한 유익한 기법이 바로 디자인 패턴이다.
 
---------------------------------------------------------
1. Iterator
2. Adapter
3. Template Method
4. Factory Method
5. Singleton
6. Prototype
7. Builder
8. Abstract Factory
9. Bridge
10. Strategy
11. Composite
12. Decorator
13. Visitor
14. Chain of Responsibility
15. Facade
16. Mediator
17. Observer
18. Memento
19. State
20. Flyweight
21. Proxy
22. Command
23. Interpreter
---------------------------------------------------------

▶Prototype 패턴

"모형을 만들어놓고 필요할 때는 복사본을 사용"


-왜 사용하는가
-클래스다이어그램
-각 클래스(인터페이스)의 역할
-Java소스


▶Prototype 패턴에 대한 이해

- Java에서 인스턴스를 생성하는 일반적인 방법은 new 연산자를 사용하는 것이다.
Apple apple = new Apple();
- new 연산자를 사용하려면 클래스의 이름을 알아야 한다.
- 매번 new연산자로 인스턴스를 생성하지 않고,  만들어놓은 인스턴스의  복사체를 만들어 사용하는 것이 프로토타입패턴이다.
- "원래의 서류를 어떻게 만들었는지는 몰라도 복사기로 서류의 복사본을 만들 수 있다"
- "원래의 객체를 어떻게 만들었는지는 몰라도 clone()으로 객체의 복사본을 만들 수 있다"
- Cloneable한 프로토타입(모형, 원형)을 따르는 구상클래스라면 그 복사본을 반환할 수 있도록 한다. 자기 자신의 복사본(clone)을 반환하는 기능을 필수로 구현한다.
- Java에서는 Object.clone() 을 통해 인스턴스 자기 자신에 대한 복사체를 반환할 수 있는 기능을 만들어 놓았다.
-복사체는 원본 인스턴스의 필드값을 복사하여 원본과 똑같은 필드값을  가지고 있다.
-단 clone()은 기본적으로 필드를 shallow copy 한다.

- Object.clone()메소드는 명시적으로 Cloneable 인터페이스를 상속받은 경우에만 사용할 수 있으며 그렇지 않은 경우 예외를 발생시킨다.
- Cloneable 인터페이스에는 아무런 메소드도 없다. Clone()을 사용할 수 있다는 것을 명시하는 역할인 것이다.
->Maker Interface라고 한다.

- 인스턴스 자신의 복사체를 반환하는 clone() 메소드를 이용하여 인스턴스를 생성하는 것이 프로토타입 패턴이다.

[개발관련] OOP관점에서 클래스와 객체와 인스턴스 용어 정리 feat 붕어빵

객체와 인스턴스라는 용어를 알맞게 사용하고 있는가 싶어서 위키백과를 찾아보았다. 인스턴스 (컴퓨터 과학) - 위키백과, 우리 모두의 백과사전 위키백과, 우리 모두의 백과사전. 객체 지향 프

mabb.tistory.com

 

▶왜 사용하는가

- 복잡한 과정을 거쳐서 인스턴스를 생성하는 경우 해당 인스턴스가 필요할 때 복잡한 과정을 매번 거쳐야 할까?
 어떠한 서류가 한 장 더 필요할 때 이를 똑같이 타이핑해서 만드는 것보다 복사를 하는 것이 편하다.
- 수많은 종류의 인스턴스들을 필요할 때마다 생성해서 사용해야 할 경우 각각의 인스턴스 생성을 위한 클래스들을 만들어 놓을 수 있다. 하지만 그럴 경우 클래스를 선언한. java파일이 아주 많아질 것이다.
- 사용자 조작에 의하여 생성하는 과정을 재현하기 어려운(또는 불가한) 인스턴스가 만들어졌을 때 똑같은 인스턴스를 더 만들어 사용하려면 어떻게 해야 할까? 복사본을 만들어 사용하는것이 편리하다.
-인스턴스 생성을 위해 Class이름에 의존하게 되면 재사용성이 떨어진다. 자바에서 재사용성이 높다는 것은 소스를 수정할 필요없이 .class 파일 그 자체로도 재사용할 수 있어야 함을 의미한다.
 

▶클래스다이어그램

prototype 패턴의 클래스다이어그램

예제에서는 Client에서 PrototypeFactory를 생성하고 PrototypeFactory를 준비시키는 과정까지 수행하고 있다. 그래서 다른 모든 클래스에 의존하는 것으로 보이고 있다. 하지만 Client가 '복사할 인스턴스들이 준비된 PrototypeFactory' 객체를 사용할 수 있다면 Client는 PrototypeFactory의 기능만으로 Prototype의 인스턴스들을 생성하고 사용할 수 있게 된다.
Cloneable 한 Prototype 인터페이스의 구상클래스로 생성한 인스턴스를 복사하여 사용하는 부분까지가 프로토타입패턴이다. 
 

▶각 클래스(인터페이스)의 역할

▷프로토타입 - Prototype.java
: Cloneable 을 상속하여 자식들이 clone()을 사용할 수 있게 한다.
자식들이 자식 자신들의 인스턴스 복사본을 반환할 수 있는 createClone() 추상메소드를 정의한다.
공통으로 사용할 기능들에 대해서도 추상클래스로 정의한다.
▷프로토타입 구상클래스 - ConcretePrototype1.java , ConcretePrototype2.java
: 프로토타입의 구상클래스들로 자기자신의 복사본을 반환하는 메소드를 오버라이드한다.
▷프로토타입관리객체 - PrototypeFactory.java
: 사용할 인스턴스들을 Map으로 관리하고 필요한 인스턴스의 복사본을 반환하는 기능을 담당한다.
▷Client
:프로토타입관리객체를 이용하여 프로토타입의 인스턴스들을 반환받아 사용한다. 해당 인스턴스를 어떻게 만드는지, 클래스의 이름이 무엇인지 전혀 알지 못해도 된다.
 

▶Java 소스

*java1.8 이전 버전을 사용하는 교재를 참고 하였습니다.
Java API 문서 등을 참조하여 제네릭을 적용하는 등 소스를 개선할 필요가 있습니다.
(https://docs.oracle.com/en/java/javase/20/docs/api/)

Prototype.java

package main.java.designpattern.prototype;

public interface Prototype extends Cloneable{
    public abstract Prototype createPrototype();
    public abstract void prototypeExcute();
    public abstract void setPrototype(int n);
}

 

ConcretePrototype1.java

package main.java.designpattern.prototype;

import java.util.*;

public class ConcretePrototype1 implements Prototype {

    private String str="";
    private int n;

    public ConcretePrototype1(String str){
        this.str = str;
    }

    @Override
    public Prototype createPrototype() {
        Prototype p = null;

        try {
            p = (Prototype) clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

    @Override
    public void prototypeExcute() {
        for(int i=0; i<n; i++){
            System.out.print(str);
        }
        System.out.println();
    }

    @Override
    public void setPrototype(int n) {
        this.n = n;
    }
}

 

ConcretePrototype2.java

package main.java.designpattern.prototype;

public class ConcretePrototype2 implements Prototype {

    public String str;
    public int n;

    public ConcretePrototype2(String str){
        this.str = str;
    }

    @Override
    public Prototype createPrototype() {
        Prototype p = null;
        try {
            p = (Prototype) clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }

    @Override
    public void prototypeExcute() {
        System.out.println(str + n);
    }

    @Override
    public void setPrototype(int n) {
        this.n = n;
    }
}

 

PrototypeFactory.java

package main.java.designpattern.prototype;

import java.util.*;

public class PrototypeFactory{
    private Map<String,Prototype> prototypeMap = new HashMap<>();

    public void registerPrototype(String name, Prototype p){
        prototypeMap.put(name,p);
    }

    public Prototype create(String name){
        Prototype p = (Prototype) prototypeMap.get(name).createPrototype();
        return p;
    }
}

 

Client.java

package main.java.designpattern.prototype;

public class Client {
    public static void main(String[] args){
        PrototypeFactory pf = new PrototypeFactory();

        //A를 세 번 반복하는 기능을 수행하는 인스턴스
        //B를 두 번 반복하는 기능을 수행하는 인스턴스
        //A뒤에 3을 붙여서 출력하는 기능을 수행하는 인스턴스
        //B뒤에 2를 붙여서 출력하는 기능을 수행하는 인스턴스

        //각각의 모형을 만들기 위한 각각의 클래스를 만들어야 하나?
        //인스턴스가 필요할 때마다 인스턴스를 만들고 세팅하는 절차를 매번 작성하여야 하나?
        //인스턴스 생성 시 클래스 이름에 의존하여야 하나?

        // -> 프로토타입패턴으로 해결

        //스프링의 bean과 비슷한 느낌이라고 생각
        //bean객체를 만들어 놓고 필요할 때 주입하곤 하는데
        //프로토타입도 다양하고 만들기 번거로운 인스턴스를 미리 만들어 저장해두고
        //필요할 때 직접 클래스로 객체를 생성하는 것이 아니라
        //만들어놓은 인스턴스에서 복사본을 얻어 사용한다.

        //프로토타입패턴을 위한 준비 부분
        Prototype repeatA = new ConcretePrototype1("A");
        repeatA.setPrototype(3);
        Prototype repeatB = new ConcretePrototype1("B");
        repeatB.setPrototype(2);
        Prototype attatchA = new ConcretePrototype2("A");
        attatchA.setPrototype(3);
        Prototype attatchB = new ConcretePrototype2("B");
        attatchB.setPrototype(2);

        pf.registerPrototype("repeatA",repeatA);
        pf.registerPrototype("repeatB",repeatB);
        pf.registerPrototype("attatchA",attatchA);
        pf.registerPrototype("attatchB",attatchB);



        //프로토타입패턴으로 클래스이름에 의존하지 않고 인스턴스를 생성하여 사용하는 부분
        Prototype obj1 = pf.create("repeatA");
        Prototype obj2 = pf.create("repeatB");
        Prototype obj3 = pf.create("attatchA");
        Prototype obj4 = pf.create("attatchB");

        obj1.prototypeExcute();
        obj2.prototypeExcute();
        obj3.prototypeExcute();
        obj4.prototypeExcute();

    }
}

 
---------------------------------------------------------
※참고자료
-Java언어로 배우는 디자인 패턴 입문
-Headfirst Design Pattern
-나무위키, 위키백과, 네이버백과
-리팩토링구루(https://refactoring.guru/ko)

반응형