
"객체를 어떻게 생성할 것인가?"
시스템으로부터 객체의 생성/합성 방법을 분리해내기 위해 인스턴스를 만드는 절차를 추상화하는 패턴
시스템이 어떤 구체 클래스를 사용하는지, 또한 인스턴스를이 어떻게 만들어지고 어떻게 합성되는지에 대한 정보를 완전히 가려줌 (캡슐화)
'누가, 언제, 어떻게, 무엇을 생성하는지 알 수 없음"
객체 생성 과정이 복잡하거나 변경 가능성이 있을 때 유용하게 사용 (유연성)
인스턴스를 하나만 만들어 사용하기 위한 패턴
한 개의 클래스에 오직 한 개의 인스턴스만을 갖도록 하여, 이에 대한 전역적인 접근을 허용

카페에서 와이파이를 사용하는 경우
사용자에게 와이파이를 할당할 때 마다 와이파이를 새롭게 만들지 않음
-> 기존에 있던 네트워크를 공유해서 사용
사용자로부터 와이파이 정보 요청이 오면,
public class Wifi {
private static Wifi wifi;
private Wifi(){}
public static Wifi requestWifi(){
if (wifi == null)
wifi = new Wifi();
return wifi;
}
}
public class Main {
public static void main(String[] args) {
Wifi wifi1 = Wifi.requestWifi();
Wifi wifi2 = Wifi.requestWifi();
Wifi wifi3 = Wifi.requestWifi();
System.out.println(wifi1);
System.out.println(wifi2);
System.out.println(wifi3);
}
}
/*
org.example.singleton.Wifi@28a418fc
org.example.singleton.Wifi@28a418fc
org.example.singleton.Wifi@28a418fc
*/
-> 모두 같은 Wifi 인스턴스의 참조값을 할당
이처럼 고정된 메모리 영역을 받기 때문에 단 하나의 객체만 생성되어 메모리 낭비 방지
테스트 여러움
상태를 가질 경우 전역으로 공유되기 때문에, 독립적인 테스트 작성이 어려움
숨은 의존성 증가
의존성을 명시적으로 주입하지 않고도 어디서든 접근 가능
결합도 증가
✅ 의존성 주입(DI)를 사용하여 싱글톤을 필요로 하는 클래스에 명시적 주입
멀티스레드 환경에서 동기화 문제
싱글톤인 것을 보장할 수 있어야 함
인스턴스를 처음 생성하는 과정에서 여러 개의 스레드가 동시로 접근하여 객체를 생성하게 된다면 2개 이상의 객체 생성 가능
확장성 부족 / 상속 어려움
생성자를 private로 설정하기 때문에 (혹시라도 외부에서 생성되는 것을 제지) 상속이 불가능, 다형성 구현 불가능
싱글톤 패턴은 굉장히 많이 활용되는 패턴이지만, 객체지향 프로그래밍의 기본 사상을 침해한다는 이유로 많은 비판도 받는 패턴
따라서 개선된 방식으로 객체의 싱글톤 방식을 구현하여 사용
객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
객체를 생성하기 위해 인터페이스를 정의하지만 어떤 클래스의 인스턴스를 생성할지는 서브 클래스가 결정
어떤 인터페이스를 사용할지는 알지만, 구체적으로 어떤 클래스의 인스턴스가 쓰일지는 예측 불가

Coffee 인터페이스를 생성하고 상속받는 클래스를 제작
abstract class Coffee {
public abstract int getPrice();
@Override
public String toString(){
return "Hi this coffee is " + this.getPrice();
}
}
public class Americano extends Coffee {
private int price;
public Americano(int price) {
this.price = price;
}
@Override
public int getPrice(){
return this.price;
}
}
public class Latte extends Coffee {
private int price;
public Latte(int price) {
this.price = price;
}
@Override
public int getPrice(){
return this.price;
}
}
Coffee 인터페이스에 대한 인스턴스를 제작하는 Factory 클래스 생성
public enum CoffeeType {
LATTE, AMERICANO
}
public class CoffeeFactory {
public static Coffee getCoffee(CoffeeType type, int price){
switch (type){
case LATTE -> {
return new Latte(price);
}
case AMERICANO -> {
return new Americano(price);
}
}
return null;
}
}
public class CoffeeMain {
public static void main(String[] args) {
Coffee americano = CoffeeFactory.getCoffee(CoffeeType.AMERICANO, 2000);
Coffee latte = CoffeeFactory.getCoffee(CoffeeType.LATTE, 3000);
//Coffee라는 인터페이스를 사용하는 것은 알지만 구체적으로 어떠한 클래스가 생성되는지는 알 수 없음. CoffeeFactory 메서드 내부에서만 확인 가능
System.out.println(americano);
System.out.println(latte);
}
}
/*
Hi this coffee is 2000
Hi this coffee is 3000
*/
유연성
상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없음 (유연성)
유지보수 용이
객체 생성 로직이 분리되어 있기 때문에 리팩토링 과정에서 한 곳만 고칠 수 있으니 유지보수 용이
결합도 낮음
클라이언트가 직접 new를 사용하여 생성하지 않음 (결합도 낮음)
캡슐화
객체 생성이 복잡해도 은닉 가능 (캡슐화)
OCP(개방 폐쇄 원칙) 만족
클래스는 닫고, 새로운 구현체에 대한 확장을 열려 있음
객체를 생성하는 클래스 또는 인터페이스(Coffee)가 있지만,
정확히 어떤 구체 클래스의 인터페이스가 생성되는지(Americano, Latte) 모르거나
또는 유연성을 제공하고 싶을 때(이후 메뉴가 더 추가된다면)
서브 클래스에게 결정권을 넘겨준다.
https://velog.io/@devch96/%EC%83%9D%EC%84%B1-%ED%8C%A8%ED%84%B4Creational-Pattern%EC%9D%B4%EB%9E%80
https://www.hanbit.co.kr/channel/view.html?cmscode=CMS8616098823