6주차: 9장 데이터 조작화 #14
Replies: 6 comments
-
class 를 리팩터링 해보았습니다.고객을 관리하는 레포지토리 클래스를 두어서 동일한 아이디의 고객이 생성되지 않도록 하였고 이에 따라 고객이 직접 장바구니를 갖고 있어도 여러 장바구니 인스턴스가 존재할 수 없으므로 장바구니 관련 코드는 그대로 두었습니다. 클래스// 온라인 쇼핑몰의 장바구니 시스템
type Product = {
id: string;
name: string;
price: number;
};
class CartItem {
private product: Product;
private quantity: number;
constructor(productData: Product, quantity: number) {
this.product = productData;
this.quantity = quantity;
}
getQuantity() {
return this.quantity;
}
getProduct() {
return this.product;
}
getTotal() {
return this.product.price * this.quantity;
}
}
class Cart {
private items: CartItem[] = [];
private static LIMIT = 10;
addItem(productData: Product, quantity: number) {
if (quantity > Cart.LIMIT) {
throw new Error('Too many items!');
}
this.items.push(new CartItem(productData, quantity));
return this.getTotalPrice();
}
removeItem(index: number) {
this.items.splice(index, 1);
return this.getTotalPrice();
}
getTotalPrice() {
return this.items.reduce((sum, item) => sum + item.getTotal(), 0);
}
getItems() {
return this.items;
}
}
class Customer {
private id: string;
private cartData: Cart; // Cart 인스턴스를 값으로 직접 보유
constructor(id: string) {
this.id = id;
this.cartData = new Cart(); // 매번 새로운 Cart 인스턴스 생성
}
addToCart(productData: Product, quantity: number) {
return this.cartData.addItem(productData, quantity);
}
removeFromCart(index: number) {
return this.cartData.removeItem(index);
}
getCartTotal() {
return this.cartData.getTotalPrice();
}
}
class CustomerRepositoy {
private customers: Map<string, Customer>;
constructor() {
this.customers = new Map();
}
public registerCustomer(id: string) {
if (this.customers.has(id)) {
throw new Error(`The customer ${id} is aleady exist`);
}
const newCustomer = new Customer(id);
this.customers.set(id, newCustomer);
return newCustomer;
}
public findCustomer(id: string) {
if (!this.customers.has(id)) {
throw new Error('Customer not found');
}
return this.customers.get(id);
}
}
// 사용 예제
const product1 = {
id: 'PROD_1',
name: 'Cool Product',
price: 100,
};
const product2 = {
id: 'PROD_2',
name: 'Another Product',
price: 200,
};
// 코드 사용 예시
const customerRepository = new CustomerRepositoy();
const customer = customerRepository.registerCustomer('CUST_1')!;
customer.addToCart(product1, 2); // 장바구니에 상품 추가
customer.addToCart(product2, 1);
// 다른 곳에서 동일한 고객의 장바구니에 접근
const sameCustomer = customerRepository.findCustomer('CUST_1')!;
const total = sameCustomer.getCartTotal();
console.log(total); |
Beta Was this translation helpful? Give feedback.
-
최선을 다하여.. 이번 리팩터링 기법에 맞춰 함수형 코드를 개선해보았습니다. // 리팩토링 전 코드
type Product = {
id: string;
name: string;
price: number;
};
// 모호한 타입 이름들 - 필드 이름 바꾸기 대상
type ItemData = {
production: Product; // 불명확한 약어 -> 명확한 이름으로 바꾸기
amount: number; // 불명확한 약어 -> 명확한 이름으로 바꾸기
};
type CartData = {
items: ItemData[];
totalPrice: number; // 변수 쪼개기 대상 - 여러 용도로 사용되는 임시값 -> 한번만 대입되도록 수정
};
type CustomerData = {
id: string;
cart: CartData;
};
// 전역 상태로 관리되는 고객 데이터
const customersData: Record<string, CustomerData> = {}; //let -> const로 변경
// 장바구니 아이템 생성 함수
const createCartItem = (production: Product, amount: number): ItemData => {
return {
production,
amount,
};
};
const MAX_ITEMS = 10; // 매직 리터럴 대상
// 장바구니에 상품 추가 함수
const addToCart = (cart: CartData, production: Product, amount: number): CartData => {
if (amount > MAX_ITEMS) {
throw new Error('Too many items!');
}
const newItems = [...cart.items, createCartItem(production, amount)];
const totalPrice = calculateCartTotal(newItems);
return {
items: newItems,
totalPrice,
};
};
// 장바구니 총액 계산 함수
const calculateCartTotal = (items: ItemData[]): number => {
return items.reduce((total, item) => total + item.amount * item.production.price, 0); // 파생 변수를 질의 함수로 바꾸기!
};
// 고객 생성 함수
// 고객 데이터만 반환
const createCustomer = (id: string): CustomerData => {
return {
id,
cart: { items: [], totalPrice: 0 },
};
};
// 고객의 장바구니에 상품 추가 함수
const addProductToCustomerCart = (customerId: string, product: Product, amount: number): CustomerData => {
const customer = customersData[customerId];
if (!customer) throw new Error('Customer not found');
return {
...customer,
cart: addToCart(customer.cart, product, amount),
};
};
// 사용 예제
const product1: Product = {
id: 'PROD_1',
name: 'Cool Product',
price: 1200,
};
const product2: Product = {
id: 'PROD_2',
name: 'Hot Product',
price: 1800,
};
const CUSTOMER_ID = 'CUST_1';
customersData[CUSTOMER_ID] = createCustomer(CUSTOMER_ID); // 고객 데이터 생성 후 상태 업데이트
customersData[CUSTOMER_ID] = addProductToCustomerCart(CUSTOMER_ID, product1, 5); // 전역 상태 수동 업데이트
customersData[CUSTOMER_ID] = addProductToCustomerCart(CUSTOMER_ID, product2, 3);
console.log(`Total Price for ${CUSTOMER_ID}: ${customersData[CUSTOMER_ID].cart.totalPrice}`); // CUST_1의 총액 출력 |
Beta Was this translation helpful? Give feedback.
-
네이밍 변경하고 타입에 불필요한 필드 제거하는 조정이 있었습니다. 전역으로 관리되는 값을 제거해 보려고 했는데 완벽하지 않은 것 같습니다. interface Product {
id: string
name: string
price: number
}
interface ItemData {
product: Product
amount: number
}
interface CartData {
items: ItemData[]
totalCartItem: number
}
interface CustomerData {
id: string
cart: CartData // 값으로 관리되는 장바구니 - 값을 참조로 바꾸기 대상 ?
}
// 장바구니 아이템 생성 함수
const createCartItem = (product: Product, amount: number): ItemData => {
return {
product,
amount,
}
}
// 장바구니 총액 계산 함수
const calculateCartTotal = (cartItems: ItemData[]): number => {
const sum = (item: ItemData) => item.product.price * item.amount
return cartItems.reduce((total, item) => total + sum(item), 0)
}
// 장바구니에 상품 추가 함수
const addToCart = (
cart: CartData,
product: Product,
amount: number,
): CartData => {
const MINIMUM_AMOUNT = 10
if (amount > MINIMUM_AMOUNT) {
throw new Error('Too many items!')
}
const newItems = [...cart.items, createCartItem(product, amount)]
return {
items: newItems,
totalCartItem: calculateCartTotal(newItems),
}
}
// 고객의 장바구니에 상품 추가 함수
const addProductToCustomerCart = (
customer: CustomerData,
product: Product,
amount: number,
): CustomerData => {
return {
...customer,
cart: addToCart(customer.cart, product, amount),
}
}
const createCustomer = (productId: string, product: Product): CustomerData => {
const customersData: Record<string, CustomerData> = {}
customersData[productId] = {
id: productId,
cart: {
items: [],
totalCartItem: 0,
},
}
const updatedCustomer = addProductToCustomerCart(
customersData[productId],
product,
2,
)
customersData.CUST_1 = updatedCustomer // 전역 상태 수동 업데이트
}
// 사용 예제
const product1: Product = {
id: 'PROD_1',
name: 'Cool Product',
price: 100,
}
const newCustomer = createCustomer('CUST_1', product1) |
Beta Was this translation helpful? Give feedback.
-
예제 리팩터링 후 클래스// 1단계: 매직 리터럴 바꾸기
const MAX_QUANTITY_PER_ITEM = 10;
// 2단계: 필드 이름 바꾸기 & 파생 변수를 질의 함수로 바꾸기
class CartItem {
constructor(
private readonly product: any, // data -> product로 변경
private readonly quantity: number // qty -> quantity로 변경
) {}
// total 필드를 제거하고 getter로 변경
get totalPrice(): number {
return this.product.price * this.quantity;
}
getProduct() {
return this.product;
}
getQuantity() {
return this.quantity;
}
}
// 3단계: 변수 쪼개기 & 메서드 분리
class Cart {
private readonly items: CartItem[] = [];
addItem(product: any, quantity: number): { oldTotal: number, newTotal: number } {
if (quantity > MAX_QUANTITY_PER_ITEM) {
throw new Error("Maximum quantity exceeded");
}
// temp 변수를 목적별로 분리
const oldTotal = this.calculateTotal();
this.items.push(new CartItem(product, quantity));
const newTotal = this.calculateTotal();
return { oldTotal, newTotal };
}
removeItem(index: number): { oldTotal: number, newTotal: number } {
const oldTotal = this.calculateTotal();
this.items.splice(index, 1);
const newTotal = this.calculateTotal();
return { oldTotal, newTotal };
}
calculateTotal(): number {
return this.items.reduce((sum, item) => sum + item.totalPrice, 0);
}
getItems(): ReadonlyArray<CartItem> {
return [...this.items];
}
}
// 4단계: 값을 참조로 바꾸기
class CartRepository {
private static carts = new Map<string, Cart>();
static getCart(customerId: string): Cart {
if (!this.carts.has(customerId)) {
this.carts.set(customerId, new Cart());
}
return this.carts.get(customerId)!;
}
}
// 5단계: Customer 클래스 개선
class Customer {
constructor(private readonly id: string) {}
get cart(): Cart {
return CartRepository.getCart(this.id);
}
addToCart(product: any, quantity: number) {
return this.cart.addItem(product, quantity);
}
removeFromCart(index: number) {
return this.cart.removeItem(index);
}
getCartTotal() {
return this.cart.calculateTotal();
}
}
// 6단계: 타입 안전성 강화
interface Product {
id: string;
name: string;
price: number;
category?: string;
}
// 사용 예제
const exampleProduct: Product = {
id: "PROD_1",
name: "Cool Product",
price: 100,
category: "Electronics"
};
// 리팩토링된 코드 사용
const customer = new Customer("CUST_1");
// 장바구니에 상품 추가
const { oldTotal, newTotal } = customer.addToCart(exampleProduct, 2);
console.log(`Cart total changed from ${oldTotal} to ${newTotal}`);
// 장바구니 조회
const cartTotal = customer.getCartTotal();
console.log(`Current cart total: ${cartTotal}`); 함수// 리팩토링 후 코드
// 상수 정의
const MAX_QUANTITY_PER_ITEM = 10;
// 명확한 타입 정의
type Product = {
id: string;
name: string;
price: number;
};
type CartItem = {
product: Product; // 명확한 이름으로 변경
quantity: number; // 명확한 이름으로 변경
};
type Cart = {
items: CartItem[];
};
type Customer = {
id: string;
};
// 장바구니 저장소 - 참조로 관리
type CartStore = {
carts: Map<string, Cart>;
getCart: (customerId: string) => Cart;
updateCart: (customerId: string, cart: Cart) => void;
};
const createCartStore = (): CartStore => {
const carts = new Map<string, Cart>();
const getCart = (customerId: string): Cart => {
if (!carts.has(customerId)) {
carts.set(customerId, { items: [] });
}
return carts.get(customerId)!;
};
const updateCart = (customerId: string, cart: Cart): void => {
carts.set(customerId, cart);
};
return { carts, getCart, updateCart };
};
// 순수 함수들
const calculateItemTotal = (item: CartItem): number => {
return item.product.price * item.quantity;
};
const createCartItem = (product: Product, quantity: number): CartItem => {
return { product, quantity };
};
const calculateCartTotal = (cart: Cart): number => {
return cart.items.reduce(
(total, item) => total + calculateItemTotal(item),
0
);
};
// 장바구니 조작 함수들
const addToCart = (cart: Cart, product: Product, quantity: number): Cart => {
if (quantity > MAX_QUANTITY_PER_ITEM) {
throw new Error(`Maximum quantity of ${MAX_QUANTITY_PER_ITEM} exceeded`);
}
// 변수 용도 분리
const previousTotal = calculateCartTotal(cart);
const newItems = [...cart.items, createCartItem(product, quantity)];
const newTotal = calculateCartTotal({ items: newItems });
console.log(`Cart total changed from ${previousTotal} to ${newTotal}`);
return { items: newItems };
};
// 애플리케이션 생성
const createShoppingApp = () => {
const cartStore = createCartStore();
const addProductToCustomerCart = (
customerId: string,
product: Product,
quantity: number
): Cart => {
const currentCart = cartStore.getCart(customerId);
const updatedCart = addToCart(currentCart, product, quantity);
cartStore.updateCart(customerId, updatedCart);
return updatedCart;
};
return {
addProductToCustomerCart,
getCustomerCart: cartStore.getCart,
getCartTotal: calculateCartTotal
};
};
// 사용 예제
const app = createShoppingApp();
const product1: Product = {
id: "PROD_1",
name: "Cool Product",
price: 100
};
const customerId = "CUST_1";
const updatedCart = app.addProductToCustomerCart(customerId, product1, 2);
const total = app.getCartTotal(updatedCart); |
Beta Was this translation helpful? Give feedback.
-
클래스class CartItem {
private product: { price: number; name: string };
private quantity: number;
constructor(product: { price: number; name: string }, quantity: number) {
this.product = product;
this.quantity = quantity;
}
getTotal(): number {
return this.product.price * this.quantity;
}
getProduct(): { price: number; name: string } {
return this.product;
}
getQuantity(): number {
return this.quantity;
}
} 기존 문제
진행중.. |
Beta Was this translation helpful? Give feedback.
-
type Product = {
id: string;
name: string;
price: number;
};
type Item = {
product: Product;
amount: number;
totalPrice: number;
};
type CartData = {
items: Item[];
cartTotalPrice: { value: number };
addProduct: (product: Product, amount: number) => void;
};
type CustomerData = {
id: string;
cart: CartData;
};
const createCustomersData = () => {
const customersData = new Map<string, CustomerData>();
// 고객 생성 함수
const createCustomer = (id: string): CustomerData => {
const customer = {
id,
cart: createCart(),
};
customersData.set(id, customer);
return customersData.get(id) as CustomerData;
};
// 고객 데이터
const getCustomer = (id: string): CustomerData | undefined => {
return customersData.get(id);
};
return {
getCustomer,
createCustomer,
};
};
// 장바구니 생성 함수
const createCart = (): CartData => {
const items: Item[] = [];
const cartTotalPrice = { value: 0 };
// 장바구니 총액 계산 함수
const calculateCartTotal = (): number => {
return items.reduce((total, item) => total + item.totalPrice, 0);
};
const calculateProductTotal = (price: number, amount: number) =>
price * amount;
// 장바구니 아이템 생성 함수
const createCartItem = (product: Product, amount: number): Item => {
return {
product,
amount,
totalPrice: calculateProductTotal(product.price, amount),
};
};
// 장바구니에 상품 추가 함수
const addProduct = (product: Product, amount: number) => {
const MAX_AMOUNT = 10;
if (amount > MAX_AMOUNT) {
throw new Error("Too many items!");
}
items.push(createCartItem(product, amount));
cartTotalPrice.value = calculateCartTotal();
};
return {
items,
cartTotalPrice,
addProduct,
};
};
// 사용 예제
const product1: Product = {
id: "PROD_1",
name: "Cool Product",
price: 100,
};
const customers = createCustomersData();
const customer1 = customers.createCustomer("CUST_1");
customer1.cart.addProduct(product1, 2);
console.table(customers.getCustomer("CUST_1")); |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
클래스
네, 리팩토링 전의 코드를 더 전형적인 "악취"가 나는 형태로 작성해보겠습니다.
이 코드에는 다음과 같은 문제점들이 있습니다:
변수 쪼개기가 필요한 부분
Cart
클래스의temp
변수가 여러 용도로 사용됨필드 이름 바꾸기가 필요한 부분
CartItem
의data
필드명이 모호함qty
와 같은 불명확한 약어 사용total
과 같은 의미가 불분명한 이름 사용파생 변수를 질의 함수로 바꾸기가 필요한 부분
CartItem
의total
필드가 계산된 값을 저장값을 참조로 바꾸기가 필요한 부분
매직 리터럴 바꾸기가 필요한 부분
이 코드를 앞서 보여드린 리팩토링 후의 코드로 개선하면서, 각각의 리팩토링 기법이 어떻게 코드의 품질을 향상시키는지 실습해볼 수 있습니다. 어떤 부분을 먼저 리팩토링하고 싶으신가요?
함수
네, 동일한 장바구니 시스템을 함수형 스타일로 작성해보겠습니다. 불변성과 순수 함수를 중심으로 재작성하되, 동일한 리팩토링 포인트를 포함시켰습니다.Beta Was this translation helpful? Give feedback.
All reactions