interface ClassicStockData {
price: number;
stockName: string;
volatility: number;
}
interface ClassicUSStockData extends ClassicStockData {
exchangeRate: number;
}
interface CryptoStockData extends ClassicStockData {
koreanPremium: number;
}
// 순수 함수 정의
const getPrice = (data: ClassicStockData): number => data.price;
const applyExchangeRate = (price: number, exchangeRate: number): number =>
price * exchangeRate;
const applyKoreanPremium = (price: number, koreanPremium: number): number =>
price * koreanPremium;
// 핸들러 매핑을 활용한 타입별 처리
const calculateHandlers = {
exchangeRate: (data: ClassicUSStockData) =>
applyExchangeRate(getPrice(data), data.exchangeRate),
koreanPremium: (data: CryptoStockData) =>
applyKoreanPremium(getPrice(data), data.koreanPremium),
};
// 핸들러를 사용한 타입 처리 함수
const calculatePrice = (
data: ClassicStockData | ClassicUSStockData | CryptoStockData
): number => {
// 각 타입별 핸들러를 실행하거나 기본 동작 수행
return "exchangeRate" in data
? calculateHandlers.exchangeRate(data as ClassicUSStockData)
: "koreanPremium" in data
? calculateHandlers.koreanPremium(data as CryptoStockData)
: getPrice(data);
};
// Example usage
const classicStock: ClassicStockData = {
price: 100,
stockName: "AAPL",
volatility: 0.2,
};
const usStock: ClassicUSStockData = {
price: 100,
stockName: "GOOGL",
volatility: 0.3,
exchangeRate: 1.2,
};
const cryptoStock: CryptoStockData = {
price: 100,
stockName: "BTC",
volatility: 0.5,
koreanPremium: 1.3,
};
// Test
console.log(calculatePrice(classicStock)); // 100
console.log(calculatePrice(usStock)); // 120
console.log(calculatePrice(cryptoStock)); // 130
하지만, 객체 지향형은 아래와 같이(물론 각각 다른 클래스로, 즉, 상속을 안쓸수도 있다) 따로 전통 주식시장, 미국 시장 그리고 신흥 코인 시장을 별개의 세계관으로 나눠서 구현할 것이다. class ClassicStockData {
constructor(
public price: number,
public stockName: string,
public volatility: number
) {}
getPrice(): number {
return this.price;
}
}
class ClassicUSStockData extends ClassicStockData {
constructor(
price: number,
stockName: string,
volatility: number,
public exchangeRate: number
) {
super(price, stockName, volatility);
}
getPrice(): number {
return this.price * this.exchangeRate;
}
}
class CryptoStockData extends ClassicStockData {
constructor(
price: number,
stockName: string,
volatility: number,
public koreanPremium: number
) {
super(price, stockName, volatility);
}
getPrice(): number {
return this.price * this.koreanPremium;
}
}
// Example usage
const classicStock = new ClassicStockData(100, "AAPL", 0.2);
const usStock = new ClassicUSStockData(100, "GOOGL", 0.3, 1.2);
const cryptoStock = new CryptoStockData(100, "BTC", 0.5, 1.3);
console.log(classicStock.getPrice()); // 100
console.log(usStock.getPrice()); // 120
console.log(cryptoStock.getPrice()); // 130
사실 인간의 머리(?)로 이해했을 때 객체 지향형 코드가 더욱 관련 세계관을 정의하여 유지보수하는 데에 있어서 명확해보인다고 생각한다. 그리고 저정도 수준의 구현을 했을 때 OOP 스타일이 오히려 코드 가독성도 깔끔해보이기도 하다.getPrice
메서드로 데이터와 로직을 함께 캡슐화.ClassicStockData
)와 로직(calculateHandlers
)을 분리.const validationHandlers = {
tag: [validateTagLength, validateTagCharacter, validateTagDuplicated],
category: [validateCategoryLength, validateCategoryCharacter],
question: [validateSimilarQuestionLength, validateSimilarQuestionDuplicated],
};
const validate = (type: keyof typeof validationHandlers, value: string, list?: any[]) => {
return validationHandlers[type].reduce(
(acc, validator) => acc.chain((v) => validator(v, list)),
Either.of(value)
);
};
이런식으로 데이터와 로직을 분리하되, 이런식의 모델링으로 관리해서 만든다.OOP는 데이터와 로직을 캡슐화해서 관리하지만, FP는 데이터와 로직을 분리해서 관리한다는 것
데이터-로직의 연결
이 끈끈해진다는게 플젝 규모가 커지면 복잡하고, 뭔가 수정할 때 상당히 고칠게 많아질 수 있다.개인 과제 유효성 검사를 OOP를 통해 만들어보고, 함수형 프로그래밍으로도 만들어보자.