일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 신입 개발자 면접
- nestjs
- ECS
- VUE
- 인텔리제이 github 로그인
- index in
- 밸런스 게임
- mysql like
- index like
- 회고록
- 개발자 취업 준비
- 라즈베리바이4 mongo
- 쿠버네티스
- OSI 7계층
- mysql index 속도차이
- kubernetes
- github accesstoken
- 개발자 면접 팁
- mongo 4.4.18
- 퇴사
- 팩토리 패턴 언제
- git
- aws m2
- 팩토리 패턴 예제
- 신입 면접 팁
- docker m2
- mongo 4.4
- token 탈취
- 개발자 회고록
- 개발자 전직
- Today
- Total
주니어 개발자 1호
팩토리 패턴에 대해 쉽게 설명해보기 본문
팩토리 패턴
정의 및 설명
- 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
- 상속 관계에 있는 두 클래스에서 상위 클래스가 뼈대를 결정
- **하위 클래스**가 객체 생성에 대한 구체적인 내용을 결정
- 쉽게 설명 하기 도전
- Program에서는 실제 사물이나, 행동등을 코드로 나타냅니다. 예로 들어 프로그램상에서는 회원 가입을 new User(”회원이름”,”기타정보 ㅡ ㅡ “ ); 의 형식으로 나타내고는 하는데요.
이렇듯 복잡한 사물이나 행동등에 new User()와 같이 코드로 표현하는데, 이 관계가 복잡하면 복잡할수록 만들기가 어려워집니다. 예로들어서 생각을 해본다면 컴퓨터 제작이라는것을 해볼까요?
Computer com = new Computer();
// 본체 열기
com.openShasi();
// cpu를 장착합니다.
com.cpuClockOpen();
com.setCpu("i5-12470");
com.구리스도포();
com.cpuClockClose();
// cooler를 장착합니다.
com.adapterCooler("수냉쿨러");
// memory를 장착합니다.
com.memorySlotOpen();
com.memorySlotOpen();
com.setMemory("DDR4 4G");
com.setMemory("DDR4 4G");
com.memoryClose();
com.memoryClose();
// ... 기타 파워, 전원 꽂기, 하드디스크 장착등 무수히 많은 작업이 존재
com.closeShasi();
이렇게 보면, 컴퓨터를 제작하는 과정은 여러 복잡한 작업을 요구합니다. 각각의 부품을 장착하려면 특정한 순서와 방법을 따라야 하죠.
이럴 때 '팩토리 패턴'이라는 것을 사용하면 좋습니다. "팩토리"는 공장을 의미하는데, 이 공장에서는 복잡한 생성 과정을 대신 해줍니다. 그렇게 되면, 우리는 컴퓨터 제작의 세부 과정을 일일이 거칠 필요 없이 공장에 컴퓨터 제작을 맡길 수 있게 됩니다.
ComputerFactory factory = new ComputerFactory();
Computer com = factory.createComputer("i5-12470", "수냉쿨러", "DDR4 4G", "파워..등등"...);
이렇게 하면 ComputerFactory가 내부적으로 필요한 모든 작업을 수행하면서 컴퓨터를 만들어줍니다. 우리는 단순히 원하는 사양을 createComputer 메서드에 전달하기만 하면 됩니다.
물론, ComputerFactory 내부에서는 위에서 언급한 복잡한 생성 과정들이 숨겨져 있습니다. 팩토리의 주요 목적은 이런 복잡함을 숨기고, 사용자에게는 간편한 인터페이스만 제공하는 것입니다.
class Computer {
public void openShasi() { /* ... */ }
public void cpuClockOpen() { /* ... */ }
public void setCpu(String cpuType) { /* ... */ }
public void 구리스도포() { /* ... */ }
public void cpuClockClose() { /* ... */ }
public void adapterCooler(String coolerType) { /* ... */ }
public void memorySlotOpen() { /* ... */ }
public void setMemory(String memoryType) { /* ... */ }
public void memoryClose() { /* ... */ }
// ... 기타 메소드들
public void closeShasi() { /* ... */ }
}
class ComputerFactory {
public Computer createComputer(String cpuType, String coolerType, String memoryType) {
Computer com = new Computer();
// 본체 열기
com.openShasi();
// CPU 장착
com.cpuClockOpen();
com.setCpu(cpuType);
com.구리스도포();
com.cpuClockClose();
// Cooler 장착
com.adapterCooler(coolerType);
// Memory 장착
com.memorySlotOpen();
com.setMemory(memoryType);
com.memoryClose();
// ... 기타 부품 장착과정
com.closeShasi();
return com;
}
}
이런 식으로 ComputerFactory 내부에서는 컴퓨터를 조립하는 세부적인 과정들이 숨겨져 있고, createComputer 메서드를 통해 간단히 컴퓨터 객체를 생성할 수 있습니다.
Computer에 바로 넣으면 안되냐구요? 객체 지향적으로 코드를 작성할 때 지켜야하는 부분이 있어요.
단일 책임 원칙 (Single Responsibility Principle):
- Computer 클래스는 컴퓨터의 기능과 상태를 나타내는 것이 주요 책임입니다. 만약 조립 과정까지 포함하게 되면, 클래스는 두 가지 책임 (컴퓨터의 표현 및 조립)을 지게 됩니다.
- **ComputerFactory**를 사용하면, 조립의 책임은 팩토리에 있고, 컴퓨터의 표현과 기능은 Computer 클래스에 있어 책임이 분리됩니다.
이렇게 만들어 두면 컴퓨터를 잘 만들 수 있잖아요? 이렇게 컴퓨터 공장을 그대로 삼성, 애플, LG등이 사용한다고 하면?
아마 이런 코드가 될거에요.
abstract class ComputerFactory {
//... (기존 메소드들)
protected abstract void installOS(Computer computer);
}
class SamsungFactory extends ComputerFactory {
// ... (다른 메소드들)
@Override
protected void installOS(Computer computer) {
System.out.println("Installing Samsung's version of Windows OS...");
}
}
class AppleFactory extends ComputerFactory {
// ... (다른 메소드들)
@Override
protected void installOS(Computer computer) {
System.out.println("Installing macOS...");
}
}
class LGFactory extends ComputerFactory {
// ... (다른 메소드들)
@Override
protected void installOS(Computer computer) {
System.out.println("Installing LG's version of Windows OS...");
}
}
public class Main {
public static void main(String[] args) {
// Samsung 컴퓨터 생성
ComputerFactory samsungFactory = new SamsungFactory();
Computer samsungComputer = samsungFactory.createComputer("i7-11700", "Air Cooler", "DDR4 16G");
// 출력: Installing Samsung's version of Windows OS...
// Apple 컴퓨터 생성
ComputerFactory appleFactory = new AppleFactory();
Computer appleComputer = appleFactory.createComputer("M1", "Internal Cooler", "LPDDR4 8G");
// 출력: Installing macOS...
// LG 컴퓨터 생성
ComputerFactory lgFactory = new LGFactory();
Computer lgComputer = lgFactory.createComputer("Ryzen 7 5800X", "Liquid Cooler", "DDR4 32G");
// 출력: Installing LG's version of Windows OS...
}
}
장점
- 상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합이 이루어짐
- 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알필요가 없음
- interface나 추상 클래스로 구현
- 그렇기에 더 많은 유연성이 발생함 ( 메소드 추가 등 )
- 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알필요가 없음
- 리팩토링을 진행하더라도 한 곳만 하면 되니 유지 보수성이 증가됨
- 직접적으로 생성하는 곳에 대한 구현만 이루어지다보니, Layer를 나누어 작업하면 수정이 쉬움
단점
- Factory Pattern을 적용하다보면 Class 가 늘어남
- 왜냐하면 각자 역할에 맞는 Layer가 나누어져 추상클래스, 인터페이스로 구현이 되기 때문임
궁금한 점
- 추상 클래스, 인터페이스를 언제 써야하는지
- java 기준에서는 java8 이후에는 추상클래스보다는 인터페이스를 사용
- why? default 예약어의 등장으로 추상클래스의 역할 대부분을 interface에서 할 수 있게 됨
- java 기준에서는 java8 이후에는 추상클래스보다는 인터페이스를 사용
DB 예제
import * as dotenv from 'dotenv';
interface Config {
db_url: string;
db_user: string;
db_password: string;
db_port: number;
db_name: 'mysql' | 'mongo' | 'oracle';
}
export class DBConfig {
private config: Config;
constructor(env?: 'dev' | 'prod' | 'test' | 'staging') {
if (env) {
this.setConfig(env);
}
}
setConfig(env: 'dev' | 'prod' | 'test' | 'staging') {
const envFileName = env + '.env';
dotenv.config({ path: envFileName });
this.config = {
db_url: process.env.DB_URL!,
db_user: process.env.DB_USER!,
db_password: process.env.DB_PASSWORD!,
db_port: Number(process.env.DB_PORT),
db_name: process.env.DB_NAME as 'mysql' | 'mongo' | 'oracle',
};
// TODO: Validate
}
getConfig(): Config {
return this.config;
}
}
abstract class DBConnection {
protected config: Config;
protected constructor(config: Config) {
this.config = config;
}
abstract connect(): void;
}
class MySQLConnection extends DBConnection {
connect() {
console.log("MySQL에 연결");
// 실제 MySQL 연결 로직
}
}
class MongoConnection extends DBConnection {
private client: MongoClient;
// 연결 메소드 구현
async connect() {
try {
this.client = new MongoClient(this.config.db_url, {
useNewUrlParser: true,
useUnifiedTopology: true
});
await this.client.connect();
console.log("MongoDB에 연결됨");
} catch (err) {
console.error('MongoDB 연결 에러:', err);
}
}
// 필요한 경우 연결 해제 메서드도 추가
async disconnect() {
if (this.client && this.client.isConnected()) {
await this.client.close();
}
}
}
class DBConnectionFactory {
public static createConnection(config: Config): DBConnection {
switch (config.db_name) {
case 'mysql':
return new MySQLConnection(config);
case 'mongo':
return new MongoConnection(config);
case 'oracle':
return new OracleConnection(config);
default:
throw new Error('지원하지 않는 DB 타입');
}
}
}
// 사용 예제
const env = process.env.NODE_ENV;
const dbConfig = new DBConfig(env);
const config = dbConfig.getConfig();
const dbConnection = DBConnectionFactory.createConnection(config);
dbConnection.connect();
'개발관련 Tip' 카테고리의 다른 글
패러다임의 혼합 (0) | 2023.09.17 |
---|---|
MVP Pattern (0) | 2023.09.17 |
싱글톤 패턴 (0) | 2023.09.17 |
INDEX 테스트 해보기 (2) | 2023.07.16 |
두개의 SSH KEY 사용하기 [ SSH-KEY 분리 ] - for Window (0) | 2022.07.19 |