1. 싱글톤(Singleton) 패턴

- 인스턴스를 오직 한개만 제공하는 클래스
- 시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다.
- 인스턴스를
오직 한개만 만들어 제공하는 클래스가 필요하다.
- 인스턴스를
- e.g. 게임의 설정화면이 여러 개 설정화면이 아닌, 오직 하나의 설정화면을 글로벌하게 제공하는 것
- 게임의 설정화면이 여러 인스턴스였다면, A라는 설정화면, B라는 설정화면 등 난잡해짐
- cf. singleton은 ‘단독 개체’, ‘독신자’라는 뜻 말고도 ‘정확히 하나의 요소만 갖는 집합’ 등의 의미가 있다
1.1 구현 방법1-가장 단순
1package me.whiteship.designpatterns._01_creational_patterns._01_singleton;23import java.io.*;45public class App {67public static void main(String[] args) {8Settings settings = new Settings();9Settings settings1 = new Settings()1011System.out.println(settings != settings1); // true12}13}
-
싱글톤 패턴을 구현하려면, new를 절대 사용하면 안된다.
-
Java에서 new을 사용해 생성자를 쓰지 못하게 만드려면,
private생성자를 만들어서,- 해당 클래스 안에서만 접근할 수 있는 생성자를 만들어주면, 클래스 밖에서는 생성자를 사용할 수 없다.
-
이렇게 되면 밖에서는 인스턴스를 만들 수 없기 떄문에,
- 해당 클래스 안에서 인스턴스를 만들어주는 방법을 글로벌하게 접근할 수 있는 방법을 제공해줘야 한다.
- 여기서 말하는 글로벌 접근이 가능한 방법은
static을 제공해주는 것이다.
1package me.whiteship.designpatterns._01_creational_patterns._01_singleton;23/**4* private 생성자와 public static 메소드를 사용하는 방법5*/6public class Settings1 {78private static Settings1 instance;910private Settings1() {}1112public static Settings1 getInstance() {13if (instance == null) {14instance = new Settings1();15}1617return instance;18}19}
1public class App {2public static void main(String[] args) {3Settings1 settings = Settings1.getInstance();4Settings1 settings1 = Settings1.getInstance();56System.out.println(settings == settings1); // true7}8}
이 방식의 심각한 문제점은, 그 중에서도 웹 앱을 만들떄, 멀티 쓰레드를 사용하게 된다.
- 대부분의 코드는 멀티쓰레드, 즉, 여러 쓰레드가 동시 접근할 수 있는 코드가 된다
- 멀티스레드 환경에서 이 코드는 안전하지 않다.
💡 복습
- 생성자를 private으로 만든 이유?
- 오직 한 개의 인스턴스에만 접근하기 위해 생성자의 노출을 막기 위해서.
- getInstance() 메소드를 static으로 선언한 이유?
- 글로벌하게 접근하게 만들기 위해 static 으로 선언하고,
- 이는 JVM에 클래스 영역에 생성되어 글로벌하게 사용할 수 있다
- getInstance()가 멀티쓰레드 환경에서 안전하지 않은 이유?
- 특히 웹 애플리케이션을 만들 때 멀티 스레드를 사용하게 된다.
- 멀티쓰레드 환경에서는 오직 한 개의 인스턴스가 아니게 됩니다.
- 새롭게 생성된 인스턴스에서도 instance가 null인지 여부를 판단하게 된다.
- 이때 새로운 스레드에는 생성된 instance 가 없기 때문에 한 개의 인스턴스를 보장할 수 없다.
1.2 구현 방법2-멀티 쓰레드 환경에서 안전하게 구현
1/**2* synchronized 키워드를 사용해서 동기화 처리3*/4public class Settings2 {56private static Settings2 instance;78private Settings2() {}910public static synchronized Settings2 getInstance() {11if (instance == null) {12instance = new Settings2();13}1415return instance;16}17}
동기화(synchronized) 키워드를 사용해 멀티쓰레드 환경에 안전하게 만드는 방법
- 다만 이 방법의 단점은
getInstance메소드로 호출할 때마다,- 동기화처리하는 작업때문에 성능 상에 약간의 불이익이 생길 수 있다.
- 왜냐하면 동기화라는 메커니즘 자체가 어떤 락(열쇠, 잠금)을 사용해,
- 그 락을 가지고 있는 쓰레드만 이 영역에 접근할 수 있게끔 해주는 매커니즘이기 떄문이다.
- 다 쓰고 나면 그 락을 해제하는 메커니즘을 처리하는 과정이 필요하기 때문에 부가적인 성능 부하가 생길 수 있다.
💡 복습
- 자바의 동기화 블럭 처리 방법은?
- 정확하게는 메서드에 synchronized하는데, 메소드 전체가 임계영역으로 설정된다.
- 임계 영역으로 설정된 부분은 쓰레드가 synchronized 메소드가 호출된 시점부터
- 해당 메소드가 포함된 객체의 Lock을 얻어 작업을 수행하다가 메소드가 종료되면 Lock을 반환한다.
- getInstance() 메소드 동기화시 사용하는 락(lock)은 인스턴스의 락인가 클래스의 락인가? 그 이유는?
- 클래스의 락이다.
- 만약 락이 인스턴스의 락이라면, 동기화시 하나의 객체를 보장할 수 없게 되기 때문
1.3 구현 방법3-이른 초기화
만약에 이 객체를 꼭 나중에 만들지 않아도 되고, 이 객체를 만드는 비용이 그렇게 비싸지 않다면, 미리 만들 수도 있다.
- 이른 초기화(eager initialization)을 사용하는 방법
1/**2* 이른 초기화(eager initialization)을 사용하는 방법3*/4public class Settings3 {56private static final Settings3 INSTANCE = new Settings3();78private Settings3() { }910public static Settings3 getInstance() {11return INSTANCE;12}1314}
💡 복습
- 이른 초기화가 단점이 될 수도 있는 이유?
- 미리 만들게 되는 것이 단점이 될 수 있는데, 만약 생성자에 많은 리소스를 사용되는 경우에는 좋지 않다.
- 만약에 생성자에서 checked 예외를 던진다면 이 코드를 어떻게 변경해야 할까요?
- 만약 생성자에서 예외를 던진다면,
- 그 안에서 try-catch 으로 예외 핸들링을 해야만 한다. 그렇지 않다면, 이른 초기화를 사용할 수 없다.
1.4 구현 방법4-double checked locking
1package me.whiteship.designpatterns._01_creational_patterns._01_singleton;23/**4* double checked locking5*/6public class Settings4 {78private static volatile Settings4 instance;910private Settings4() {11}1213public static Settings4 getInstance() {14if (instance == null) {15synchronized (Settings4.class) {16if (instance == null) {17instance = new Settings4();18}19}20}2122return instance;23}2425}
double checked locking으로 효율적인 동기화 블럭 만들기- java 1.5 이상에서 동작
💡 복습
double check locking이라고 부르는 이유?
- 2번에 걸쳐 단 하나의 객체임을 체크하기 때문이다.
- if 문이 2번 사용되는데, 첫번째는 instance가 null일 경우와,
- 동기화 클래스 synchronized(xx.class) 에서 한번 더 체크하기 때문이다.
- 동기화 매커니즘을 사용하지 않습니다.
- 만약 instance가 있다면 바로 리턴하기 때문이다.
instacne변수는 어떻게 정의해야 하는가? 그 이유는?
volatile을 사용하여, 가장 최신의 객체를 가져오도록 한다.- Multi Thread 환경에서 하나의 Thread만 read & write하고,
- 나머지 Thread가 read하는 상황에서 가장 최신의 값을 보장한다.
1.5 구현 방법5-static inner 클래스 사용 (권장)
1package me.whiteship.designpatterns._01_creational_patterns._01_singleton;23/**4* static inner 클래스 홀더5*/6public class Settings5 {78private Settings5() {9}1011private static class Settings5Holder {12private static final Settings5 INSTANCE = new Settings5();13}1415public static Settings5 getInstance() {16return Settings5Holder.INSTANCE;17}1819}
- 권장하는 방법중 하나.
- 이 방법은 static final 를 썻는데도, 왜
지연 초기화(lazy intialization)라고 볼 수 있는가?- Holder를 통해 객체를 생성하게 되는데,
- 이렇게 할 경우
getIntance()가 호출될 때 로딩되기 때문이다.
1.6 싱글톤 구현 깨트리는 방법1
1public class App {23public static void main(String[] args) throws IOException, ClassNotFoundException {4Settings settings = Settings.getInstance();56Constructor<Settings> declaredConstructor = Settings.class.getDeclaredConstructor();7declaredConstructor.setAccessible(true);8Settings settings1 = declaredConstructor.newInstance();910System.out.println(settings == settings1); // false11}1213}
💡 복습
- 리플렉션에 대해 설명하세요.
- 구체적인 클래스 타입을 알지 못해도,
- 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
setAccessible(true)를 사용하는 이유는?
- private 생성자에 접근하기 위한 목적이다.
1.7 싱글톤 구현 깨트리는 방법2
1public class App {23public static void main(String[] args) throws IOException, ClassNotFoundException {4Settings5 settings = Settings5.INSTANCE;56Settings5 settings1 = null;7try (ObjectOutput out = new ObjectOutputStream(new FileOutputStream("settings.obj"))) {8out.writeObject(settings);9}1011try (ObjectInput in = new ObjectInputStream(new FileInputStream("settings.obj"))) {12settings1 = (Settings5) in.readObject();13}1415System.out.println(settings == settings1);16}1718}
💡 복습
- 자바의 직렬화 & 역직렬화에 대해 설명하세요.
- 직렬화: 자바 시스템 내부에서 사용되는 객체 또는 데이터를 바이트(byte) 형태로 변환
- 역직렬화: 바이트로 변환된 데이터를 다시 객체로 변환
- SerializableId란 무엇이며 왜 쓰는가?
- 직렬화된 클래스의 버전을 기억하여 로드된 클래스와 직렬화된 객체가 호환되는지 확인한다.
- SerializableId가 다르면 역직렬화 할 수 없다.
- try-resource 블럭에 대해 설명하세요
- try 코드 블럭이 끝나면 자동으로 자원을 종료해주기 때문에 명시적으로 자원 반환을 하지 않아도 된다.
1.8 구현 방법6-enum (권장)
1/**2* Enum을 사용해서 싱글톤 만들기3*/4public enum Settings5 {5INSTANCE,6}
- enum 타입의 인스턴스를 리팩토링을 만들 수 있는가?
- 만들 수 없다.
- enum 타입의 클래스는 리플랙션을 통해 만들 수 없도록 제한한다.
- enum으로 싱글톤 타입을 구현할 때의 단점은?
- 단점은 이른 초기화와 같이 미리 만들어진다는 것이다.
- 그리고 상속을 사용할 수 없다.
- 직렬화 & 역직렬화 시에 별도로 구현해야 하는 메소드가 있는가?
- 별다른 장치가 없어도 Enum 클래스는 직렬화 & 역직렬화가 된다.
- 그러나 getResolves() 구현시 역직렬화시 변경을 가할 수 있다.
1.9 실무에서는 어떻게 쓰이나?
- 스프링에서 빈의 스코프 중에 하나로 싱글톤 스코프.
- 자바 java.lang.Runtime
- 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.
2. 팩토리 메서드(Factory method) 패턴

구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.
- 다양한 구현체(Product)가 있고,
- 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리 (Creator)를 제공할 수 있다.
- e.g. 어떤 배를 만든다고 가정해보면,
- 하얀 배를 만드는 공장에는 하얀배만 만들다가,
- 사업이 잘되서 추후에 검정 배를 만드는 공장도 만들었다.
- 색 칠하는 작업, 로고를 새기고, 다 만들면 알림을 울리는 등의 처리 프로세스가 있을 것이다.
- 이 과정을 한 로직에 담아두면 복잡해질 것이다.
- 그래서 개별 과정이 담긴 추상화되어 있는 공장을 만든다.
- How? 위 그림처럼 공장(factory) 역할을 할 인터페이스를 만들고,
- 이
인터페이스에 정의되어 있는 메소드는 여러 개가 있을 수 있다. - 이 구현부 중에 일부 바뀌어야 되는 것들을
추상 메서드로 빼내서 하위 클래스에서 만들게끔 한다. - 이 팩토리에서 만들어낸 다양한 객체를 만들 수 있게끔 Product라는 인터페이스를 만들고,
- 그 각각의 구체적인 팩토리 안에서 구체적인 인스턴스들을 만들게끔 설계하면,
- 유연한 확장에 용이한 구조가 된다.
- 이
- cf. factory는 ‘공장’이란 뜻이고, 공장은 물건을 만드는 곳이다. 여기서 물건에 해당되는 것이 바로 인스턴스이다
2.1 구현 방법

팩토리 메소드 구현 방법 : 확장에 열려있고 변경에 닫혀있는 구조로 만들어보자
2.2 예시 - 기존
1// Ship.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._01_before;34public class Ship {56private String name;78private String color;910private String logo;1112public String getName() {13return name;14}1516public void setName(String name) {17this.name = name;18}1920public String getColor() {21return color;22}2324public void setColor(String color) {25this.color = color;26}2728public String getLogo() {29return logo;30}3132public void setLogo(String logo) {33this.logo = logo;34}3536@Override37public String toString() {38return "Ship{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", logo='" + logo + '\'' + '}';39}40}
1// ShipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._01_before;34public class ShipFactory {56public static Ship orderShip(String name, String email) {7// validate8if (name == null || name.isBlank()) {9throw new IllegalArgumentException("배 이름을 지어주세요.");10}11if (email == null || email.isBlank()) {12throw new IllegalArgumentException("연락처를 남겨주세요.");13}1415prepareFor(name);1617Ship ship = new Ship();18ship.setName(name);1920// Customizing for specific name21if (name.equalsIgnoreCase("whiteship")) {22ship.setLogo("\uD83D\uDEE5️");23} else if (name.equalsIgnoreCase("blackship")) {24ship.setLogo("⚓");25}2627// coloring28if (name.equalsIgnoreCase("whiteship")) {29ship.setColor("whiteship");30} else if (name.equalsIgnoreCase("blackship")) {31ship.setColor("black");32}3334// notify35sendEmailTo(email, ship);3637return ship;38}3940private static void prepareFor(String name) {41System.out.println(name + " 만들 준비 중");42}4344private static void sendEmailTo(String email, Ship ship) {45System.out.println(ship.getName() + " 다 만들었습니다.");46}47}48
1// Client.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._01_before;34public class Client {56public static void main(String[] args) {7Ship whiteship = ShipFactory.orderShip("Whiteship", "keesun@mail.com");8System.out.println(whiteship);910Ship blackship = ShipFactory.orderShip("Blackship", "keesun@mail.com");11System.out.println(blackship);12}13}
2.3 예시 - 변경
1// ShipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public interface ShipFactory {5default Ship orderShip(String name, String email) {6validate(name, email);7prepareFor(name);8Ship ship = createShip();9sendEmailTo(email, ship);10return ship;11}1213void sendEmailTo(String email, Ship ship);1415Ship createShip();1617private void validate(String name, String email) {18if (name == null || name.isBlank()) {19throw new IllegalArgumentException("배 이름을 지어주세요.");20}21if (email == null || email.isBlank()) {22throw new IllegalArgumentException("연락처를 남겨주세요.");23}24}2526private void prepareFor(String name) {27System.out.println(name + " 만들 준비 중");28}29}
1// DefaultShipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public abstract class DefaultShipFactory implements ShipFactory {56@Override7public void sendEmailTo(String email, Ship ship) {8System.out.println(ship.getName() + " 다 만들었습니다.");9}10}
1// WhiteshipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public class WhiteshipFactory extends DefaultShipFactory {56@Override7public Ship createShip() {8return new Whiteship();9}10}
1// Ship.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after.Anchor;5import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after.Wheel;67public class Ship {89private String name;1011private String color;1213private String logo;1415private Wheel wheel;1617private Anchor anchor;1819public String getName() {20return name;21}2223public void setName(String name) {24this.name = name;25}2627public String getColor() {28return color;29}3031public void setColor(String color) {32this.color = color;33}3435public String getLogo() {36return logo;37}3839public void setLogo(String logo) {40this.logo = logo;41}4243@Override44public String toString() {45return (46"Ship{" + "name='" + name +47'\'' + ", color='" + color +48'\'' + ", logo='" + logo + '\'' +49'}'50);51}5253public Wheel getWheel() {54return wheel;55}5657public void setWheel(Wheel wheel) {58this.wheel = wheel;59}6061public Anchor getAnchor() {62return anchor;63}6465public void setAnchor(Anchor anchor) {66this.anchor = anchor;67}68}
1// Whiteship.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public class Whiteship extends Ship {56public Whiteship() {7setName("whiteship");8setLogo("\uD83D\uDEE5");9setColor("white");10}11}
1// BlackshipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public class BlackshipFactory extends DefaultShipFactory {56@Override7public Ship createShip() {8return new Blackship();9}10}
1// Blackship.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public class Blackship extends Ship {56public Blackship() {7setName("blackship");8setColor("black");9setLogo("⚓");10}11}
1// Client.java2package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after;34public class Client {56public static void main(String[] args) {7Client client = new Client();8client.print(new WhiteshipFactory(), "whiteship", "keesun@mail.com");9client.print(new BlackshipFactory(), "blackship", "keesun@mail.com");10}1112private void print(ShipFactory shipFactory, String name, String email) {13System.out.println(shipFactory.orderShip(name, email));14}15}
2.4 정리 및 장단점
구체적으로 어떤 것을 만들지는 서브 클래스가 정한다.
- 팩토리 메소드 패턴을 적용했을 때의 장점은? 단점은?
장점: 확장에 열려있고 변경에 닫혀있는 객체 지향 원칙을 적용해서,- 기존 코드를 변경하지 않고, 새로운 인스턴스를 다른 방법으로 확장이 가능하다
단점: 클래스가 많아 진다.
- “확장에 열려있고 변경에 닫혀있는 객체 지향 원칙”을 설명하세요.
- 기존 코드를 변경하지 않으면서, 새로운 기능을 확장할 수 있는 구조를 만드는 것
- 자바 8에 추가된 default 메소드에 대해 설명하세요.
- 인터페이스에서 기본적인 구현체를 만들 수 있다.
- 오히려 추상 클래스에서 하던 일을 인터페이스에서도 할 수 있게끔 되었기 때문에
- Java 8부터는 추상 클래스를 그렇게 많이 쓰지는 않는다.
- 정말 추상 클래스가 필요한 경우가 아니라면, 인터페이스에 있는 defaut 메서드를 사용해서 시도해보길 바란다.
- 나아가 Java 9에서는 인터페이스에 private 메소드를 정의하는 것도 있다.
2.5 실무에서는 어떻게 쓰이나?
단순한 팩토리 패턴
- 매개변수의 값에 따라 또는 메소드에 따라 각기 다른 인스턴스를 리턴하는 단순한 버전의 팩토리 패턴
java.lang.Calendar또는java.lang.NumberFormat
1package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._03_java;23import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Blackship;4import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Whiteship;56public class SimpleFactory {78public Object createProduct(String name) {9if (name.equals("whiteship")) {10return new Whiteship();11} else if (name.equals("blackship")) {12return new Blackship();13}1415throw new IllegalArgumentException();16}17}
1package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._03_java;23import java.util.Calendar;4import java.util.Locale;56public class CalendarExample {78public static void main(String[] args) {9System.out.println(Calendar.getInstance().getClass()); // 그레고리력 달력10System.out.println(11Calendar12.getInstance(Locale.forLanguageTag("th-TH-x-lvariant-TH"))13.getClass()14); // 타이완 달력15System.out.println(16Calendar17.getInstance(Locale.forLanguageTag("ja-JP-x-lvariant-JP"))18.getClass()19); // 일본 달력20}21}
스프링 BeanFactory
- Object 타입의 Product를 만드는 BeanFacotry라는 Creator!
1package me.whiteship.designpatterns._01_creational_patterns._02_factory_method._03_java;23import org.springframework.beans.factory.BeanFactory;4import org.springframework.context.annotation.AnnotationConfigApplicationContext;5import org.springframework.context.support.ClassPathXmlApplicationContext;67public class SpringBeanFactoryExample {89public static void main(String[] args) {10BeanFactory xmlFactory = new ClassPathXmlApplicationContext("config.xml");11String hello = xmlFactory.getBean("hello", String.class);12System.out.println(hello);1314BeanFactory javaFactory = new AnnotationConfigApplicationContext(15Config.class16);17String hi = javaFactory.getBean("hello", String.class);18System.out.println(hi);19}20}
3. 추상 팩토리(Abstract factory) 패턴

서로 관련있는 여러 객체(=인스턴스)를 만들어주는 인터페이스.
-
구체적으로 어떤 클래스의 인스턴스를(concrete product)를 사용하는지 감출 수 있다.
-
인터페이스로 정의하거나, 추상 클래스로 정의하거나 그래서 구체적인 Factory를 만드는 것까지는 팩토리 메서드와 비슷하지만,
- 초점이 클라이언트쪽에 있다.
- Factory를 사용하는 클라이언트 쪽에 초점을 바라보면
추상 팩토리 패턴이다. - 반대로 Factory쪽에만 초점으로 바라보면
팩토리 메서드 패턴이다.
-
팩토리 메소드의 목적 자체가 클라이언트의 코드를 인터페이스 기반으로 코딩하는 것에 있다.
-
위 그림은
팩토리 메서드 패턴에서 클라이언트만 추가된 것일 뿐이다.- 내부 구조는
팩토리 메서드 패턴과 굉장히 유사하다.
- 내부 구조는
-
e.g. 하얀 배와 검정 배를 만드는데, 비슷한 부품들이 세트로 있을 것이다. (e.g. 닻이나 배의 바퀴)
- 그러다 발전된 부품이 새로 나왔다면, 해당 제품에 모두 다 바꿔줘야 될 것이다.
- 이렇게 모두 바뀌는 것을 바뀌지 않게끔 하면서 제품군을 늘려나갈 수 있는 방법으로 추상 팩토리를 적용해본다.
3.1 구현 방법

클라이언트 코드에서 구체적인 클래스의 의존성을 제거한다.
3.2 예시 - 기존
1// Anchor.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34public interface Anchor {}
1// WhiteAnchor.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._01_before;34import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after.Anchor;56public class WhiteAnchor implements Anchor {}
1// Wheel.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34public interface Wheel {}
1// WhiteWheel.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._01_before;34import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after.Wheel;56public class WhiteWheel implements Wheel {}
1// WhiteshipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._01_before;34import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.DefaultShipFactory;5import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Ship;6import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Whiteship;78public class WhiteshipFactory extends DefaultShipFactory {910@Override11public Ship createShip() {12Ship ship = new Whiteship();13ship.setAnchor(new WhiteAnchor());14ship.setWheel(new WhiteWheel());15return ship;16}17}
3.3 예시 - 변경
1// WhiteAnchorPro.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34public class WhiteAnchorPro implements Anchor {}
1// WhiteWheelPro.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34public class WhiteWheelPro implements Wheel {}
1// ShipPartsFactory.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._01_before.WhiteAnchor;5import me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._01_before.WhiteWheel;67public class WhiteshipPartsFactory implements ShipPartsFactory {89@Override10public Anchor createAnchor() {11return new WhiteAnchor();12}1314@Override15public Wheel createWheel() {16return new WhiteWheel();17}18}
1// WhiteshipPartsFactory.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34public class WhitePartsProFactory implements ShipPartsFactory {56@Override7public Anchor createAnchor() {8return new WhiteAnchorPro();9}1011@Override12public Wheel createWheel() {13return new WhiteWheelPro();14}15}
1// WhiteshipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.DefaultShipFactory;5import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Ship;6import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Whiteship;78public class WhiteshipFactory extends DefaultShipFactory {910private ShipPartsFactory shipPartsFactory;1112public WhiteshipFactory(ShipPartsFactory shipPartsFactory) {13this.shipPartsFactory = shipPartsFactory;14}1516@Override17public Ship createShip() {18Ship ship = new Whiteship();19ship.setAnchor(shipPartsFactory.createAnchor());20ship.setWheel(shipPartsFactory.createWheel());21return ship;22}23}
1// ShipInventory.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._02_after;34import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Ship;5import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.ShipFactory;67public class ShipInventory {89public static void main(String[] args) {10ShipFactory shipFactory = new WhiteshipFactory(new WhiteshipPartsFactory());11Ship ship = shipFactory.createShip();12System.out.println(ship.getAnchor().getClass());13System.out.println(ship.getWheel().getClass());14}15}
3.4 정리
팩토리 메소드 패턴과 굉장히 흡사한데 무엇이 다른건가.
- 모양과 효과는 비슷하지만…
- 둘 다 구체적인 객체 생성 과정을
추상화한 인터페이스를 제공한다.
- 둘 다 구체적인 객체 생성 과정을
- 관점이 다르다.
팩토리 메소드 패턴은팩토리를 구현하는 방법(inheritance)에 초점을 둔다.추상 팩토리 패턴은팩토리를 사용하는 방법(composition)에 초점을 둔다.
- 목적이 조금 다르다.
팩토리 메소드 패턴은 구체적인 객체 생성 과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적.추상 팩토리 패턴은 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적
3.5 실무에서는 어떻게 쓰이나?
자바 라이브러리
- javax.xml.xpath.XPathFactory#newInstance()
- javax.xml.transform.TransformerFactory#newInstance()
- javax.xml.parsers.DocumentBuilderFactory#newInstance()
1// DocumentBuilderFactoryExample.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._03_java;34import java.io.File;5import java.io.IOException;6import javax.xml.parsers.DocumentBuilder;7import javax.xml.parsers.DocumentBuilderFactory;8import javax.xml.parsers.ParserConfigurationException;9import org.w3c.dom.Document;10import org.xml.sax.SAXException;1112public class DocumentBuilderFactoryExample {1314public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {15DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();16DocumentBuilder builder = factory.newDocumentBuilder();1718Document document = builder.parse(new File("src/main/resources/config.xml"));19System.out.println(document.getDocumentElement());20}21}
스프링
- FactoryBean과 그 구현체
1// ShipFactory.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._03_java;34import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Ship;5import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Whiteship;6import org.springframework.beans.factory.FactoryBean;78public class ShipFactory implements FactoryBean<Ship> {910@Override11public Ship getObject() throws Exception {12Ship ship = new Whiteship();13ship.setName("whiteship");14return ship;15}1617@Override18public Class<?> getObjectType() {19return Ship.class;20}21}22
1// FactoryBeanConfig.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._03_java;34import org.springframework.context.annotation.Bean;5import org.springframework.context.annotation.Configuration;67@Configuration8public class FactoryBeanConfig {910@Bean11public ShipFactory shipFactory() {12return new ShipFactory();13}14}
1// FactoryBeanExample.java2package me.whiteship.designpatterns._01_creational_patterns._03_abstract_factory._03_java;34import me.whiteship.designpatterns._01_creational_patterns._02_factory_method._02_after.Ship;5import org.springframework.context.ApplicationContext;6import org.springframework.context.annotation.AnnotationConfigApplicationContext;7import org.springframework.context.support.ClassPathXmlApplicationContext;89public class FactoryBeanExample {1011public static void main(String[] args) {12// **** XML13// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("config.xml");14// Ship whiteship = applicationContext.getBean("whiteship", Ship.class);15// System.out.println(whiteship.getName());1617// **** Java18ApplicationContext applicationContext = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);19Ship bean = applicationContext.getBean(Ship.class);20System.out.println(bean);21}22}
4. 빌더(Builder) 패턴
4.1 패턴 소개

동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법.
- (복잡한) 객체를 만드는 프로세스를 독립적으로 분리할 수 있다
- 복잡한 객체의 생성 과정과 표현 방법을 분리하여,
- 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게 하는 디자인 패턴

e.g. 빌더 패턴을 적용하여 여행 계획을 만드는 과정은 마치 여행사에서 여행 패키지를 조합하는 것과 유사하다.
- 여행 계획을 만들기 위해, 우리는 여행의 제목, 시작 날짜, 숙박 일수, 숙소, 그리고 여행 일정 등 다양한 세부 사항을 결정해야 한다.
- 이 모든 정보를 한 번에 설정하는 대신, 빌더 패턴을 사용하면 여행 계획을 단계별로 구성할 수 있다.
- 여행 계획 시작: 먼저, 여행 계획을 만들기 위한 빌더 객체를 생성한다. 이 객체는 여행 계획의 기초를 마련한다.
- 세부 사항 추가: 빌더 객체를 사용하여 여행 계획의 세부 사항을 단계별로 추가한다.
- e.g. 여행의 제목을 설정하고, 여행의 시작 날짜를 지정한다.
- 그 다음, 숙박 일수와 숙소를 결정하고, 여행 일정에 여러 활동을 추가한다.
- 이 과정에서 각 단계는 메소드 체이닝을 통해 연결되어, 코드의 가독성을 높인다.
- 여행 계획 완성: 모든 세부 사항을 추가한 후, 빌더 객체는 최종적으로 여행 계획 객체를 반환합니다.
- 이 객체는 앞서 설정한 모든 세부 사항을 포함합니다.
빌더 패턴의 장점은 여행 계획과 같은 복잡한 객체를 유연하고 직관적으로 구성할 수 있다.
- 각 단계에서 필요한 정보만을 설정할 수 있으며, 모든 세부 사항을 한 번에 설정할 필요가 없다.
- 또한, 빌더 패턴은 선택적인 세부 사항을 쉽게 처리할 수 있게 해주며, 최종 객체의 생성 과정을 더 명확하고 이해하기 쉽게 만들어 준다.
4.2 예시 - 기존
1// TourPlan.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before;34import java.time.LocalDate;5import java.util.List;67public class TourPlan {89private String title;1011private int nights;1213private int days;1415private LocalDate startDate;1617private String whereToStay;1819private List<DetailPlan> plans;2021public TourPlan() {22}2324public TourPlan(String title, int nights, int days, LocalDate startDate, String whereToStay,25List<DetailPlan> plans) {26this.title = title;27this.nights = nights;28this.days = days;29this.startDate = startDate;30this.whereToStay = whereToStay;31this.plans = plans;32}3334@Override35public String toString() {36return "TourPlan{" + "title='" + title + '\'' + ", nights=" + nights + ", days=" + days + ", startDate="37+ startDate + ", whereToStay='" + whereToStay + '\'' + ", plans=" + plans + '}';38}3940public String getTitle() {41return title;42}4344public void setTitle(String title) {45this.title = title;46}4748public int getNights() {49return nights;50}5152public void setNights(int nights) {53this.nights = nights;54}5556public int getDays() {57return days;58}5960public void setDays(int days) {61this.days = days;62}6364public LocalDate getStartDate() {65return startDate;66}6768public void setStartDate(LocalDate startDate) {69this.startDate = startDate;70}7172public String getWhereToStay() {73return whereToStay;74}7576public void setWhereToStay(String whereToStay) {77this.whereToStay = whereToStay;78}7980public List<DetailPlan> getPlans() {81return plans;82}8384public void setPlans(List<DetailPlan> plans) {85this.plans = plans;86}8788public void addPlan(int day, String plan) {89this.plans.add(new DetailPlan(day, plan));90}91}
1// DetailPlan.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before;34public class DetailPlan {56private int day;78private String plan;910public DetailPlan(int day, String plan) {11this.day = day;12this.plan = plan;13}1415public int getDay() {16return day;17}1819public void setDay(int day) {20this.day = day;21}2223public String getPlan() {24return plan;25}2627public void setPlan(String plan) {28this.plan = plan;29}3031@Override32public String toString() {33return "DetailPlan{" + "day=" + day + ", plan='" + plan + '\'' + '}';34}35}
1// App.class2package me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before;34import java.time.LocalDate;56public class App {78public static void main(String[] args) {9TourPlan shortTrip = new TourPlan();10shortTrip.setTitle("오레곤 롱비치 여행");11shortTrip.setStartDate(LocalDate.of(2021, 7, 15));1213TourPlan tourPlan = new TourPlan();14tourPlan.setTitle("칸쿤 여행");15tourPlan.setNights(2);16tourPlan.setDays(3);17tourPlan.setStartDate(LocalDate.of(2020, 12, 9));18tourPlan.setWhereToStay("리조트");19tourPlan.addPlan(0, "체크인 이후 짐풀기");20tourPlan.addPlan(0, "저녁 식사");21tourPlan.addPlan(1, "조식 부페에서 식사");22tourPlan.addPlan(1, "해변가 산책");23tourPlan.addPlan(1, "점심은 수영장 근처 음식점에서 먹기");24tourPlan.addPlan(1, "리조트 수영장에서 놀기");25tourPlan.addPlan(1, "저녁은 BBQ 식당에서 스테이크");26tourPlan.addPlan(2, "조식 부페에서 식사");27tourPlan.addPlan(2, "체크아웃");28}29}
4.3 예시 - 변경
1// TourPlanBuilder.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._02_after;34import me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before.TourPlan;56import java.time.LocalDate;78public interface TourPlanBuilder {910TourPlanBuilder nightsAndDays(int nights, int days);1112TourPlanBuilder title(String title);1314TourPlanBuilder startDate(LocalDate localDate);1516TourPlanBuilder whereToStay(String whereToStay);1718TourPlanBuilder addPlan(int day, String plan);1920TourPlan getPlan();2122}
1// DefaultTourBuilder.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._02_after;34import me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before.DetailPlan;5import me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before.TourPlan;67import java.time.LocalDate;8import java.util.ArrayList;9import java.util.List;1011public class DefaultTourBuilder implements TourPlanBuilder {1213private String title;1415private int nights;1617private int days;1819private LocalDate startDate;2021private String whereToStay;2223private List<DetailPlan> plans;2425@Override26public TourPlanBuilder nightsAndDays(int nights, int days) {27this.nights = nights;28this.days = days;29return this;30}3132@Override33public TourPlanBuilder title(String title) {34this.title = title;35return this;36}3738@Override39public TourPlanBuilder startDate(LocalDate startDate) {40this.startDate = startDate;41return this;42}4344@Override45public TourPlanBuilder whereToStay(String whereToStay) {46this.whereToStay = whereToStay;47return this;48}4950@Override51public TourPlanBuilder addPlan(int day, String plan) {52if (this.plans == null) {53this.plans = new ArrayList<>();54}5556this.plans.add(new DetailPlan(day, plan));57return this;58}5960@Override61public TourPlan getPlan() {62return new TourPlan(title, nights, days, startDate, whereToStay, plans);63}64}
1// TourDirector.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._02_after;34import java.time.LocalDate;5import me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before.TourPlan;67public class TourDirector {89private TourPlanBuilder tourPlanBuilder;1011public TourDirector(TourPlanBuilder tourPlanBuilder) {12this.tourPlanBuilder = tourPlanBuilder;13}1415public TourPlan cancunTrip() {16return tourPlanBuilder17.title("칸쿤 여행")18.nightsAndDays(2, 3)19.startDate(LocalDate.of(2020, 12, 9))20.whereToStay("리조트")21.addPlan(0, "체크인하고 짐 풀기")22.addPlan(0, "저녁 식사")23.getPlan();24}2526public TourPlan longBeachTrip() {27return tourPlanBuilder28.title("롱비치")29.startDate(LocalDate.of(2021, 7, 15))30.getPlan();31}32}
1// App.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._02_after;34import me.whiteship.designpatterns._01_creational_patterns._04_builder._01_before.TourPlan;56public class App {78public static void main(String[] args) {9TourDirector director = new TourDirector(new DefaultTourBuilder());10TourPlan tourPlan = director.cancunTrip();11TourPlan tourPlan1 = director.longBeachTrip();12}13}
4.4 장단점
- 장점
- 만들기 복잡한 객체를 순차적으로 만들 수 있다.
- 복잡한 객체를 만드는 구체적인 과정을 숨길 수 있다.
- 동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수도 있다.
- 불완전한 객체를 사용하지 못하도록 방지할 수 있다.
- 단점
- 원하는 객체를 만들려면 빌더부터 만들어야 한다.
- 구조가 복잡해 진다. (트레이드 오프)
4.5 실무에서 어떻게 쓰이나?
자바 8 Stream.Buidler API
1// StreamExample.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._03_java;34import java.util.stream.Stream;56public class StreamExample {78public static void main(String[] args) {9Stream<String> names = Stream.<String>builder().add("keesun").add("whiteship").build();10names.forEach(System.out::println);11}12}
StringBuilder는 빌더 패턴일까?
1// StringBuilderExample.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._03_java;34public class StringBuilderExample {56public static void main(String[] args) {7StringBuilder stringBuilder = new StringBuilder();8String result = stringBuilder.append("whiteship").append("keesun").toString();9System.out.println(result);10}11}12
롬복의 @Builder
1package me.whiteship.designpatterns._01_creational_patterns._04_builder._03_java;23import lombok.Builder;45@Builder6public class LombokExample {78private String title;910private int nights;1112private int days;1314public static void main(String[] args) {15LombokExample trip = LombokExample16.builder()17.title("여행")18.nights(2)19.days(3)20.build();21}22}
스프링
- UriComponentsBuilder
- MockMvcWebClientBuilder
- …Builder
1// SpringExample.java2package me.whiteship.designpatterns._01_creational_patterns._04_builder._03_java;34import org.springframework.web.util.UriComponents;5import org.springframework.web.util.UriComponentsBuilder;67public class SpringExample {89public static void main(String[] args) {10UriComponents howToStudyJava = UriComponentsBuilder11.newInstance()12.scheme("http")13.host("www.whiteship.me")14.path("java playlist ep1")15.build()16.encode();17System.out.println(howToStudyJava);18}19}
5. 프로토타입(Prototype) 패턴

기존 인스턴스를 복제하여 새로운 인스턴스를 만드는 방법
- 복제 기능을 갖추고 있는 기존 인스턴스를 프로토타입으로 사 용해 새 인스턴스를 만들 수 있다

e.g. GitHub에서는 각각의 저장소(repository)가 고유한 설정, 파일, 이슈(issue) 등을 가지고 있다.
- 특정 프로젝트를 위한 새로운 저장소를 만들 때, 기존 저장소의 설정이나 구조를 그대로 사용하기 위해,
- 기존 저장소를 복사하여 새로운 저장소를 만드는 것이 더 효율적이다.
- 프로토타입 패턴을 적용하면, 기존의 GitHub 저장소 객체를
복사(clone)하여 새로운 저장소 객체를 생성할 수 있다.- 이 과정에서 기존 저장소의 설정, 파일 구조, 이슈 템플릿 등이 새로운 저장소로 복사된다.
- 복사된 새 객체는 필요에 따라 추가적인 수정을 거쳐 사용될 수 있다.
프로토타입 패턴의 핵심은 복사와 수정이다.
- 기존 객체를 복사하여 새 객체를 만들고, 이 새 객체에 대해 필요한 수정을 수행함으로써,
- 객체 생성 과정을 간소화하고, 효율성을 높일 수 있다.
- GitHub 저장소 예시에서 볼 수 있듯,
- 프로토타입 패턴은 설정이 복잡하거나, 객체의 상태가 다양하게 변할 수 있는 상황에서 특히 유용합니다.
5.1 예시 - 기존
1// GithubRepository.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._01_before;34public class GithubRepository {56private String user;78private String name;910public String getUser() {11return user;12}1314public void setUser(String user) {15this.user = user;16}1718public String getName() {19return name;20}2122public void setName(String name) {23this.name = name;24}25}
1// GithubIssue.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._01_before;34public class GithubIssue {56private int id;78private String title;910private GithubRepository repository;1112public GithubIssue(GithubRepository repository) {13this.repository = repository;14}1516public int getId() {17return id;18}1920public void setId(int id) {21this.id = id;22}2324public String getTitle() {25return title;26}2728public void setTitle(String title) {29this.title = title;30}3132public GithubRepository getRepository() {33return repository;34}3536public String getUrl() {37return String.format(38"https://github.com/%s/%s/issues/%d",39repository.getUser(),40repository.getName(),41this.getId()42);43}44}
1// App.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._01_before;34public class App {56public static void main(String[] args) {7GithubRepository repository = new GithubRepository();8repository.setUser("whiteship");9repository.setName("live-study");1011GithubIssue githubIssue = new GithubIssue(repository);12githubIssue.setId(1);13githubIssue.setTitle("1주차 과제: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.");1415String url = githubIssue.getUrl();16System.out.println(url);17}1819}
5.2 예시 - 변경
1// GithubIssue.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._02_after;34import java.util.Objects;56public class GithubIssue implements Cloneable {78private int id;910private String title;1112private GithubRepository repository;1314public GithubIssue(GithubRepository repository) {15this.repository = repository;16}1718public int getId() {19return id;20}2122public void setId(int id) {23this.id = id;24}2526public String getTitle() {27return title;28}2930public void setTitle(String title) {31this.title = title;32}3334public GithubRepository getRepository() {35return repository;36}3738public String getUrl() {39return String.format(40"https://github.com/%s/%s/issues/%d",41repository.getUser(),42repository.getName(),43this.getId()44);45}4647@Override48protected Object clone() throws CloneNotSupportedException {49GithubRepository repository = new GithubRepository();50repository.setUser(this.repository.getUser());51repository.setName(this.repository.getName());5253GithubIssue githubIssue = new GithubIssue(repository);54githubIssue.setId(this.id);55githubIssue.setTitle(this.title);5657return githubIssue;58}5960@Override61public boolean equals(Object o) {62if (this == o) return true;63if (o == null || getClass() != o.getClass()) return false;64GithubIssue that = (GithubIssue) o;65return (66id == that.id &&67Objects.equals(title, that.title) &&68Objects.equals(repository, that.repository)69);70}7172@Override73public int hashCode() {74return Objects.hash(id, title, repository);75}76}
1// App.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._02_after;34public class App {56public static void main(String[] args) throws CloneNotSupportedException {7GithubRepository repository = new GithubRepository();8repository.setUser("whiteship");9repository.setName("live-study");1011GithubIssue githubIssue = new GithubIssue(repository);12githubIssue.setId(1);13githubIssue.setTitle("1주차 과제: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.");1415String url = githubIssue.getUrl();16System.out.println(url);1718GithubIssue clone = (GithubIssue) githubIssue.clone();19System.out.println(clone.getUrl());2021repository.setUser("Keesun");2223System.out.println(clone != githubIssue);24System.out.println(clone.equals(githubIssue));25System.out.println(clone.getClass() == githubIssue.getClass());26System.out.println(clone.getRepository() == githubIssue.getRepository());2728System.out.println(clone.getUrl());29}3031}
5.3 장단점
- 장점
- 복잡한 객체를 만드는 과정을 숨길 수 있다.
- 기존 객체를 복제하는 과정이 새 인스턴스를 만드는 것보다 비용(시간 또는 메모리)적인 면에서 효율적일 수도 있다.
- 추상적인 타입을 리턴할 수 있다.
- 단점
- 복제한 객체를 만드는 과정 자체가 복잡할 수 있다. (특히, 순환 참조가 있는 경우)
5.4 실무에서 어떻게 쓰이나?
- 자바 Object 클래스의 clone 메소드와 Cloneable 인터페이스
- shallow copy와 deep copy
- ModelMapper
1// ModelMapperExample.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._03_java;34import me.whiteship.designpatterns._01_creational_patterns._05_prototype._02_after.GithubIssue;5import me.whiteship.designpatterns._01_creational_patterns._05_prototype._02_after.GithubRepository;6import org.modelmapper.ModelMapper;78public class ModelMapperExample {910public static void main(String[] args) {11GithubRepository repository = new GithubRepository();12repository.setUser("whiteship");13repository.setName("live-study");1415GithubIssue githubIssue = new GithubIssue(repository);16githubIssue.setId(1);17githubIssue.setTitle("1주차 과제: JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.");1819ModelMapper modelMapper = new ModelMapper();20GithubIssueData githubIssueData = modelMapper.map(githubIssue, GithubIssueData.class);21System.out.println(githubIssueData);22}23}
1// JavaCollectionExample.java2package me.whiteship.designpatterns._01_creational_patterns._05_prototype._03_java;34import java.util.ArrayList;5import java.util.List;67public class JavaCollectionExample {89public static void main(String[] args) {10Student keesun = new Student("keesun");11Student whiteship = new Student("whiteship");12List<Student> students = new ArrayList<>();13students.add(keesun);14students.add(whiteship);1516List<Student> clone = new ArrayList<>(students);17System.out.println(clone);18}19}