[디자인패턴] 생성 패턴 - 프로토타입(Prototype) 패턴
*프로토타입 패턴
- 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법
- 복제 기능을 갖추고 있는 기존 인스턴스를 프로토타입으로 사용해 새 인스턴스를 만들 수 있다.
- Prototype, ConcretePrototypeA, ConcretePrototypeB
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class DatabaseInfo {
private String db;
private String host;
private int port;
}
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
import lombok.Getter;
import lombok.Setter;
import java.util.Objects;
@Getter @Setter
public class JdbcConnectionInfo implements Cloneable{
private int id;
private String title;
private DatabaseInfo databaseInfo;
public JdbcConnectionInfo(DatabaseInfo databaseInfo) {
this.databaseInfo = databaseInfo;
}
public String getConnection() {
return String.format("jdbc:%s://%s/%d",
this.databaseInfo.getDb(),
this.databaseInfo.getHost(),
this.databaseInfo.getPort());
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Prototype 인터페이스는 java에서 제공하는 Object 클래스에 있는 clone 메소드를 사용한다. 그러기 위해선 Cloneable 인터페이스를 구현해야 하는데 Clonable 인터페이스를 구현하면 clone 메소드를 오버라이딩 해서 사용할 수 있다. 기본 오버라이딩을 하게되면 부모 클래스에 있는 clone을 가져온다.
- Client
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
public class App {
public static void main(String[] args) throws CloneNotSupportedException {
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setDb("mysql");
databaseInfo.setHost("localhost");
databaseInfo.setPort(3306);
JdbcConnectionInfo jdbcConnection = new JdbcConnectionInfo(databaseInfo);
jdbcConnection.setId(1);
jdbcConnection.setTitle("first connection");
String connection = jdbcConnection.getConnection();
System.out.println(connection); // jdbc:mysql://localhost/3306
JdbcConnectionInfo clone = (JdbcConnectionInfo) jdbcConnection.clone();
databaseInfo.setDb("oracle");
System.out.println(clone != jdbcConnection); // true
System.out.println(clone.equals(jdbcConnection)); // false
System.out.println(clone.getClass() == jdbcConnection.getClass()); // true
System.out.println(clone.getDatabaseInfo() == jdbcConnection.getDatabaseInfo()); // true
System.out.println(jdbcConnection.getConnection());
// jdbc:oracle://localhost/3306
System.out.println(clone.getConnection());
// jdbc:oracle://localhost/3306
}
}
Object의 clone 메소드를 사용하면 복제하려고 하는 인스턴스의 정보를 가져올 수 있으며, 이 방법은 싱글톤이 아니다. 객체는 다른 객체의 레퍼런스를 참조하지만 값은 그대로 복제 할 수 있다.
여기서, clone 메소드는 기본적으로 깊은 복사(deep copy)가 아닌 얕은 복사(shallow copy)를 한다. 따라서 JdbcConnectionInfo(ConcretePrototypeA)에서 사용하고 있는 DataBaseInfo(ConcretePrototypeB)는 기존의 인스턴스와 clone 인스턴스가 같은 레퍼런스를 참조한다.
- ConcreatePrototypeA(equals, hashCode 재정의)
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
import lombok.Getter;
import lombok.Setter;
import java.util.Objects;
@Getter @Setter
public class JdbcConnectionInfo implements Cloneable{
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JdbcConnectionInfo that = (JdbcConnectionInfo) o;
return id == that.id && Objects.equals(title, that.title) && Objects.equals(databaseInfo, that.databaseInfo);
}
@Override
public int hashCode() {
return Objects.hash(id, title, databaseInfo);
}
...
}
먼저 equals 메소드와 같이 객체의 주소 값이 아닌 객체 안에 있는 값들의 비교를 원한다면 equals 메소드를 재정의 할 필요가 있다. 이 때 hashCode 메소드도 같이 변경한다.
- Client(equals, hashCode 재정의)
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
public class App {
public static void main(String[] args) throws CloneNotSupportedException {
...
System.out.println(clone != jdbcConnection); // true
System.out.println(clone.equals(jdbcConnection)); // true
System.out.println(clone.getClass() == jdbcConnection.getClass()); // true
System.out.println(clone.getDatabaseInfo() == jdbcConnection.getDatabaseInfo()); // true
...
}
}
clone과 jdbcConnection의 equals 비교값이 바뀌었다.
- ConcretePrototypeA(clone 메소드 수정)
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
import lombok.Getter;
import lombok.Setter;
import java.util.Objects;
@Getter @Setter
public class JdbcConnectionInfo implements Cloneable{
...
@Override
protected Object clone() throws CloneNotSupportedException {
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setDb(this.databaseInfo.getDb());
databaseInfo.setHost(this.databaseInfo.getHost());
databaseInfo.setPort(this.databaseInfo.getPort());
JdbcConnectionInfo jdbcConnection = new JdbcConnectionInfo(databaseInfo);
jdbcConnection.setId(this.getId());
jdbcConnection.setTitle(this.getTitle());
return jdbcConnection;
}
...
}
얕은 복사를 깊은 복사로 만드는 방법은 clone 메소드를 재정의 하는 방법을 사용할 수 있다. ConcretePrototype에서 clone을 통해 복사할 데이터의 범위를 직접 명시하면된다.
- Client(clone 메소드 수정)
package me.whiteship.designpatterns._01_creational_patterns._05_prototype.practice;
public class App {
public static void main(String[] args) throws CloneNotSupportedException {
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setDb("mysql");
databaseInfo.setHost("localhost");
databaseInfo.setPort(3306);
JdbcConnectionInfo jdbcConnection = new JdbcConnectionInfo(databaseInfo);
jdbcConnection.setId(1);
jdbcConnection.setTitle("first connection");
String connection = jdbcConnection.getConnection();
System.out.println(connection); // jdbc:mysql://localhost/3306
JdbcConnectionInfo clone = (JdbcConnectionInfo) jdbcConnection.clone();
databaseInfo.setDb("oracle");
System.out.println(clone != jdbcConnection); // true
System.out.println(clone.equals(jdbcConnection)); // false
System.out.println(clone.getClass() == jdbcConnection.getClass()); // true
System.out.println(clone.getDatabaseInfo() == jdbcConnection.getDatabaseInfo()); // false
System.out.println(jdbcConnection.getConnection());
// jdbc:oracle://localhost/3306
System.out.println(clone.getConnection());
// jdbc:mysql://localhost/3306
}
}
얕은 복사에서 깊은 복사로 변경 후에 ConcretePrototypeA(JdbcConnectionInfo)가 참조하는 ConcreatePrototypeB(DatabaseInfo)의 정보가 변경되었을 때 서로 다른 레퍼런스를 참조하기 때문에 equals 메소드나 참조하고 있는 databaseInfo의 값이 다르다는 것을 알수있다. 또한 서로 값 변경에 대해 영향을 주지 않는다.
- 장점
- 복잡한 객체를 만드는 과정을 숨길 수 있다.
- 기존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보다 비용(시간 또는 메모리)적인 면에서 효율적일 수도 있다.
- 추상적인 타입을 리턴할 수 있다.
- 단점
- 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. (특히, 순환 참조가 있는 경우)
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4
'디자인패턴' 카테고리의 다른 글
[디자인패턴] 생성 패턴 - 빌더(Builder) 패턴 (0) | 2022.08.23 |
---|---|
[디자인패턴] 생성 패턴 - 추상 팩토리(Abstract Factory) 패턴 (0) | 2022.08.23 |
[디자인패턴] 생성 패턴 - 팩토리 메소드(Factory method) 패턴 (0) | 2022.08.23 |
[디자인패턴] 생성 패턴 - 싱글톤(Singleton) 패턴 (0) | 2022.08.23 |