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