[Design Pattern] 생성(Creational) 패턴
67392 단어 Design PatternDesign Pattern
01. 싱글톤 (Singleton) 패턴
- 인스턴스를 오직 한개만 제공하는 패턴
구현 방법 1
- private 생성자에 static 메소드
- 멀티쓰레드 환경에서 안전하지 않음 (여러 쓰레드가 동시에 접근할 경우 여러 인스턴스가 생길수 있다)
public class Settings {
private static Settings instance;
private Settings() {
// 외부에서 생성하지 못하게 private 으로 지정
}
public static Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
}
구현 방법 2
- 동기화(synchronized)를 사용해 멀티쓰레드 환경에서 안전하게 만드는 방법
- synchronized 특성상 성능이슈가 생길수있다.
public static synchronized Settings getInstance() {
if (instance == null) {
instance = new Settings();
}
return instance;
}
구현 방법 3
- 이른 초기화 (eager initialization)을 사용하는 방법
- Thread safe 하다.
- 클래스 로딩 시점에 static field 가 초기화 되므로 객체 생성비용이 클 경우 단점이 될 수 있다.
private static final Settings INSTANCE = new Settings();
private Settings() {
}
public static Settings getInstance() {
return INSTANCE;
}
구현 방법 4
- double-checked locking
- volatile 키워드를 이용해서 캐시 불일치 이슈를 방지 할 수 있다. (java 1.5 이상)
- 필드에 instance 가 할당되지 않았을 경우에만 synchronized 블록을 실행하므로
메서드에 synchronized 처리를 하는것보다 성능상 유리하다. - instance 가 필요한 시점에 만들수 있다.
public class Settings {
private static volatile Settings instance;
private Settings() {
}
public static Settings getInstance() {
if (instance == null) {
synchronized (Settings.class) {
if (instance == null) {
instance = new Settings();
}
}
}
return instance;
}
}
구현 방법 5
- static nested 클래스를 사용하는 방법 (Initialization-on-demand holder)
- 클래스안에 클래스(Holder)를 두어 JVM 의 Class Loader 매커니즘과 Class 가 로드되는 시점을 이용한 방법
- Settings 클래스에는 Holder 클래스의 필드가 없기 때문에 Settings 클래스 로딩 시 Holder 클래스를 초기화하지 않음
- Lazy initialization 방식을 가져가면서 Thread 간 동기화 문제를 동시에 해결 가능
public class Settings {
private static Settings instance;
private static class SettingsHolder {
private static final Settings SETTINGS = new Settings();
}
public static Settings getInstance() {
return SettingsHolder.SETTINGS;
}
}
구현 방법 6
- enum 을 사용하는 방법
- enum 은 상수 하나당 인스턴스가 만들어지며, 각각 public static final 로 공개한다.
- 리플렉션을 통해 싱글톤을 깨트리는 공격에 안전하며, 직렬화를 보장한다.
public enum Settings {
INSTANCE
}
싱글톤 패턴을 깨트리는 방법
리플렉션 사용
Settings settings = Settings.getInstance();
Constructor<Settings> declaredConstructor = Settings.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Settings settings1 = declaredConstructor.newInstance();
System.out.println(settings == settings1); // false
직렬화 & 역직렬화 사용
Settings settings = Settings.getInstance();
Settings settings1 = null;
try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {
out.writeObject(settings);
}
try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {
settings1 = (Settings) in.readObject();
}
System.out.println(settings == settings1); // false
02. 팩토리 메소드 (Factory Method) 패턴
- 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정하는 패턴
- 다양한 구현체 (Product)가 있고, 그중에서 특정한 구현체를 만들 수 있는 다양한 팩토리 (Creator)를 제공할 수 있다.
- 팩토리들은 객체의 인스턴스를 생성하는 부분을 캡슐화 하기 위해 사용된다.
구현 방법
// Product
public interface Pizza {
void prepare();
void bake();
void box();
}
// ConcreteProduct
public class NYStyleCheesePizza implements Pizza { ... }
public class NYStylePepperoniPizza implements Pizza { ... }
public class ChicagoStyleCheesePizza implements Pizza { ... }
public class ChicagoStylePepperoniPizza implements Pizza { ... }
// Creator
public interface PizzaStore {
default Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.box();
return pizza;
}
// factory method
Pizza createPizza(String type);
}
// ConcreteCreator 1
public class NYPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
if ("cheese".equals(type)) {
return new NYStyleCheesePizza();
}
if ("pepperoni".equals(type)) {
return new NYStylePepperoniPizza();
}
throw new IllegalArgumentException();
}
}
// ConcreteCreator 2
public class ChicagoPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
if ("cheese".equals(type)) {
return new ChicagoStyleCheesePizza();
}
if ("pepperoni".equals(type)) {
return new ChicagoStylePepperoniPizza();
}
throw new IllegalArgumentException();
}
}
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
Pizza pizza1 = chicagoStore.orderPizza("pepperoni");
}
Simple Factory
- 객체를 생성하는 역할을 하나의 팩토리 클래스가 전담하게 하는 방법.
- 주어진 입력을 기반으로 다른 유형의 객체를 리턴하는 메소드가 있는 팩토리 클래스.
- 디자인 패턴이라기 보다는 자주 사용되는 프로그래밍 기법으로 보면 된다.
public class PizzaFactory {
public static Pizza createPizza(String type) {
if ("cheese".equals(type)) {
return new CheesePizza();
}
if ("pepperoni".equals(type)) {
return new PepperoniPizza();
}
throw new IllegalArgumentException();
}
}
03. 추상 팩토리 (Abstract Factory) 패턴
- 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공하는 패턴
- 구체적으로 어떤 클래스의 인스턴스를(concrete product)를 사용하는지 감출 수 있다.
구현 방법
// Product
public interface Frame {
void shape();
}
public interface Wheel {
void size();
}
// ConcreteProduct
public class AFrame implements Frame { ... }
public class BFrame implements Frame { ... }
public class AWheel implements Wheel { ... }
public class BWheel implements Wheel { ... }
// AbstractFactory
public interface CarFactory {
Frame createFrame();
Wheel createWheel();
}
// ConcreteFactory 1
public class ACarFactory implements CarFactory {
@Override
public Frame createFrame() {
return new AFrame();
}
@Override
public Wheel createWheel() {
return new AWheel();
}
}
// ConcreteFactory 2
public class BCarFactory implements CarFactory {
@Override
public Frame createFrame() {
return new BFrame();
}
@Override
public Wheel createWheel() {
return new BWheel();
}
}
// Client 에서 사용할 대상
public class Car {
private Frame frame;
private Wheel wheel;
public Car(Frame frame, Wheel wheel) {
this.frame = frame;
this.wheel = wheel;
}
public Frame getFrame() {
return frame;
}
public Wheel getWheel() {
return wheel;
}
}
// Client
public class Client {
private CarFactory carFactory;
public Client(CarFactory carFactory) {
this.carFactory = carFactory;
}
public Car createCar() {
return new Car(carFactory.createFrame(), carFactory.createWheel());
}
}
public static void main(String[] args) {
Car car = new Client(new ACarFactory());
System.out.println(car.getFrame().getClass());
System.out.println(car.getWheel().getClass());
Car car2 = new Client(new BCarFactory());
System.out.println(car2.getFrame().getClass());
System.out.println(car2.getWheel().getClass());
}
팩토리 메소드 패턴 vs 추상 팩토리 패턴
관점
- 팩토리 메소드 패턴은 "팩토리를 구현하는 방법 (inheritance)" 에 초점을 둔다.
- 추상 팩토리 패턴은 "팩토리를 사용하는 방법 (composition)" 에 초점을 둔다.
목적
- 팩토리 메소드 패턴은 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적.
- 추상 팩토리 패턴은 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적.
04. 빌더 (Builder) 패턴
- 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법.
- (복잡한) 객체를 만드는 프로세스를 독립적으로 분리할 수 있다.
구현 방법
// Builder
public interface ToyBuilder {
ToyBuilder name(String name);
ToyBuilder size(int size);
Toy build();
}
// ConcreteBuilder
public class DefaultToyBuilder implements ToyBuilder {
private String name;
private int size;
@Override
public ToyBuilder name(String name) {
this.name = name;
return this;
}
@Override
public ToyBuilder size(int size) {
this.size = size;
return this;
}
@Override
public Toy build() {
return new Toy(name, size);
}
}
// Product
public class Toy {
private String name;
private int size;
public Toy(String name, String size) {
this.name = name;
this.size = size;
}
// getter, setter...
}
// Director
public class ToyDirector {
private ToyBuilder toyBuilder;
public ToyDirector(ToyBuilder toyBuilder) {
this.toyBuilder = toyBuilder;
}
public Toy sampleToy() {
return toyBuilder.name("sample")
.size(5)
.build();
}
}
public static void main(String[] args) {
ToyDirector director = new ToyDirector(new DefaultToyBuilder());
Toy sampleToy = director.sampleToy();
System.out.prinln(sampleToy);
}
장단점
장점
- 필요한 데이터만 설정할 수 있다.
- 만들기 복잡한 객체를 순차적으로 만들 수 있다.
- 복잡한 객체를 만드는 구체적인 과정을 숨길 수 있다.
- 동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수도 있다. 불완전한 객체를 사용하지 못하도록 방지할 수 있다.
단점
- 원하는 객체를 만들려면 빌더부터 만들어야 한다.
- 구조가 복잡해 진다. (트레이드 오프)
사용하는곳
- Java 8 Stream.Builder API
- StringBuilder
- Lombok @Builder
- Spring UriComponentsBuilder
- ...
05. 프로토타입 (Prototype) 패턴
- 기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법.
- 복제 기능을 갖추고 있는 기존 인스턴스를 프로토타입으로 사용해 새 인스턴스를 만들 수 있다.
- 이 패턴은 복사를 위하여 Java 에서는 Object 의
clone
method 를 활용한다.
clone
method 를 활용한다.구현 방법
public class Employees implements Cloneable {
private final List<String> values;
public Employees() {
this(new ArrayList<>());
}
public Employees(List<String> values) {
this.values = values;
}
public List<String> getValues() {
return values;
}
public void loadData() {
values.add("AA");
values.add("BB");
values.add("CC");
values.add("DD");
}
// shallow copy 일 경우
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// deep copy 일 경우
@Override
public Object clone() throws CloneNotSupportedException {
List<String> list = new ArrayList<>(values);
return new Employees(list);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Employees that = (Employees) o;
return Objects.equals(values, that.values);
}
@Override
public int hashCode() {
return Objects.hash(values);
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Employees employees = new Employees();
employees.loadData();
Employees clone = (Employees) employees.clone();
assert employees != clone;
assert employees.equals(clone);
assert employees.getClass() == clone.getClass();
// shallow copy, depp copy 동일
assert employees.getValues().equals(clone.getValues());
// shallow copy 일 경우
assert employees.getValues() == clone.getValues();
// deep copy 일 경우
assert employees.getValues() != clone.getValues();
}
Shallow Copy vs Deep Copy
Shallow Copy
- 객체를 복사할 때, 해당 객체만 복사하여 새 객체를 생성한다.
- 복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 참조가 동일하다.
Deep Copy
- 객체를 복사 할 때, 해당 객체와 인스턴스 변수까지 복사하는 방식.
- 전부를 복사하여 새 주소에 담기 때문에 참조를 공유하지 않는다.
장단점
장점
- 복잡한 객체를 만드는 과정을 숨길 수 있다.
- 기존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보다 비용(시간 또는 메모리)적인 면에서 효율적일 수도 있다.
- 추상적인 타입을 리턴할 수 있다.
단점
- 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. (특히, 순환 참조가 있는 경우)
사용하는곳
- Java Object 클래스의 clone 메서드, Cloneable Interface
- ModelMapper
Author And Source
이 문제에 관하여([Design Pattern] 생성(Creational) 패턴), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@csh0034/Design-Pattern-01.-생성Creational-패턴저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)