< 커피 주문 시스템 개발 >
Cashier 라는 객체에 모든 책임을 할당한다고 가정.class Cashier {
orderbook: Orderbook;
constructor(orderbook: Orderbook) {
this.orderbook = orderbook;
}
takeOrders(menuName: string, quantity: number): boolean {
const madeCoffee = this.makeCoffee(menuName, quantity);
this.deliveryOrder(this.orderbook.getUserName(), madeCoffee);
}
calculatePrice(menuName: string, quantity: number): number {}
makeCoffee(menuName: string, quantity: number): Coffee {}
deliveryOrder(toCustomer: string, coffee: Coffee): void {}
}
처음엔 문제가 없겠지만 메뉴가 추가되거나 가격 계산 방식이 변경된다고 했을때 Cashier 클래스는 변경되어야 한다.
즉, Cashier 클래스를 변경시키는 요소가 2개 이상이다. -> SRP 원칙 위배
export interface Beverage {}
export class OrderTakerService {
constructor(private readonly orderbookService: OrderbookService) {}
takeOrders(menuName: string, quantity: number): boolean {}
}
export class BeverageMakerService {
makeBeverage(menuName: string, quantity: number): Beverage {}
}
export class PriceCalculatorService {
calculatePrice(menuName: string, quantity: number): number {}
}
export class OrderDeliveryService {
deliveryOrder(toCustomer: string, beverage: Beverage): void {}
}
// Beverage 클래스
Cashier 클래스가 하던 역할을 네가지로 분리 -> 주문, 음료 제작, 계산, 전달Coffee 에서 다른 음료가 사용 될 수 있게 Beverage로 수정@Controller('orders')
export class OrdersController {
constructor(
private readonly orderTakerService: OrderTakerService,
private readonly beverageMakerService: BeverageMakerService,
private readonly priceCalculatorService: PriceCalculatorService,
private readonly orderDeliveryService: OrderDeliveryService,
) {}
@Post()
async createOrder(@Body() body: { menuName: string; quantity: number }) {
const { menuName, quantity } = body;
if (this.orderTakerService.takeOrders(menuName, quantity)) {
const beverage = this.beverageMakerService.makeBeverage(menuName, quantity);
const price = this.priceCalculatorService.calculatePrice(menuName, quantity);
this.orderDeliveryService.deliveryOrder('Customer Name', beverage);
return { beverage, price };
} else {
return { status: 'Order failed' };
}
}
}
< 결제 할인 기능 >
switch(payType){
case "telecome":
// 통신사 할인 정책 기반 할인 요금 계산 로직
case "membership":
// 맴버쉽 할인 정책 기반 할인 요금 계산 로직
case "payco":
// 페이코 할인 정책 기반 할인 요금 계산 로직
default:
return 0;
}
// 추상화 또는 인터페이스
interface DiscountPolicy {
isSatisfied(): boolean;
calculateDiscountAmount(): number;
}
// 팩토리얼
function makeDiscountPolicyBy(discountType: string): DiscountPolicy {
switch(discountType) {
case "telecome":
return new TelecomeDiscountPolicy();
case "membership":
return new MembershipDiscountPolicy();
case "payco":
return new PaycoDiscountPolicy();
default:
return new NoneDiscountPolicy();
}
}
// 호출부 예시
const discountType: string = "telecome"; // 가정: "telecome"이 입력됨
const discountPolicy: DiscountPolicy = makeDiscountPolicyBy(discountType);
function getDiscountAmount(discountPolicy: DiscountPolicy): number {
if (discountPolicy.isSatisfied()) {
return discountPolicy.calculateDiscountAmount();
}
return 0;
}
class Item {
itemName: string;
price: number;
constructor(itemName: string, price: number) {
this.itemName = itemName;
this.price = price;
}
getPrice(): number {
return this.price;
}
}
class NoDiscountItem extends Item {}
class DoubleDiscountItem extends Item {}
class OwnerCrazyItem extends Item {}
class Coupon {
discountRate: number;
constructor(discountRate: number) {
this.discountRate = discountRate;
}
calculateDiscountAmount(item: Item): number {
if (item instanceof NoDiscountItem) {
return 0;
} else if (item instanceof DoubleDiscountItem) {
return item.getPrice() * (this.discountRate * 2);
} else if (item instanceof OwnerCrazyItem) {
return item.getPrice() * (this.discountRate * 70);
} else {
return item.getPrice() * this.discountRate;
}
}
abstract class Item {
private itemName: string;
private price: number;
private isDiscount: boolean;
constructor(itemName: string, price: number, isDiscount: boolean) {
this.itemName = itemName;
this.price = price;
this.isDiscount = isDiscount;
}
isSatisfied(): boolean {
return this.isDiscount;
}
getPrice(): number {
return this.price;
}
}
class EnableDiscountItem extends Item {
constructor(itemName: string, price: number) {
super(itemName, price, true); // EnableDiscountItem 항상 할인 적용
}
}
class DisableDiscountItem extends Item {
constructor(itemName: string, price: number) {
super(itemName, price, false); // DisableDiscountItem 할인 적용 안 함
}
}
class Coupon {
private discountRate: number;
constructor(discountRate: number) {
this.discountRate = discountRate;
}
calculateDiscountAmount(item: Item): number {
if (item.isSatisfied()) {
return item.getPrice() * this.discountRate;
} else {
return 0;
}
}
}
// 사용 예시
const coupon = new Coupon(0.1); // 예시로 할인율을 10%로 설정
console.log(coupon.calculateDiscountAmount(new DisableDiscountItem("iPhone15", 1500000)));
interface Item {
isEnableDiscount(): boolean;
getPrice(): number;
}
class EnableDiscountItem implements Item {
private readonly itemName: string;
private readonly price: number;
private readonly isDiscount: boolean;
constructor(itemName: string, price: number) {
this.itemName = itemName;
this.price = price;
}
isEnableDiscount(): boolean {
return true;
}
getPrice(): number {
return this.price;
}
}
class DisableDiscountItem implements Item {
private readonly itemName: string;
private readonly price: number;
private readonly isDiscount: boolean;
constructor(itemName: string, price: number) {
this.itemName = itemName;
this.price = price;
}
isEnableDiscount(): boolean {
return false;
}
getPrice(): number {
return this.price;
}
}
class Coupon {
private discountRate: number;
constructor(discountRate: number) {
this.discountRate = discountRate;
}
calculateDiscountAmount(item: Item): number {
if (item.isEnableDiscount()) {
return item.getPrice() * this.discountRate;
} else {
return 0;
}
}
}
// 사용 예시
const coupon = new Coupon(10); // 예시로, 할인율을 10으로 설정
const enableDiscountItem = new EnableDiscountItem("EnableItem", 100);
console.log(coupon.calculateDiscountAmount(enableDiscountItem)); // 할인 적용 예시
const disableDiscountItem = new DisableDiscountItem("DisableItem", 100;
console.log(coupon.calculateDiscountAmount(disableDiscountItem)); // 할인 미적용 예시
interface Bird {
fly(): void;
walk(): void;
}
interface FlyingBird extends Bird {
fly(): void;
}
interface WalkingBird extends Bird {
walk(): void;
}
class Parrot implements Bird {
fly(): void {}
walk(): void {}
}
class Penguin implements WalkingBird {
fly(): void {} // 날지 못하지만 인터페이스 때문에 구현해야함.
walk(): void {}
}
const p: Bird = new Penguin();
p.fly(); // 실행 가능하지만, 펭귄이 실제로 날지 못해 에러(라고 가정)
interface Bird {}
interface FlyingBird extends Bird {
fly(): void;
}
interface WalkingBird extends Bird {
walk(): void;
}
class Parrot implements FlyingBird, WalkingBird {
fly(): void {}
walk(): void {}
}
class Penguin implements WalkingBird {
walk(): void {}
}

interface IGoforward {
goForward(distance: number): void;
}
interface IGobackward {
goBackward(distance: number): void;
}
interface IGoLeft {
goLeft(distance: number): void;
}
interface IGoRight {
goRight(distance: number): void;
}
class Car implements IGoforward, IGobackward, IGoLeft, IGoRight {
goForward(distance: number): void {}
goBackward(distance: number): void {}
goLeft(distance: number): void {}
goRight(distance: number): void {}
}
class Train implements IGoforward, IGobackward {
goForward(distance: number): void {}
goBackward(distance: number): void {}
}
interface PaymentMethod {
pay(amount: number): void;
}
class SamsungPay implements PaymentMethod {
connect(): boolean {
return this.auth();
}
private auth(): boolean {
return true;
}
public pay(amount: number): void {
console.log("SamsungPay: PAY", amount);
}
}
....
class PayService {
private samsungPay: SamsungPay;
private applyPay: ApplyPay;
private payco: Payco;
private naverPay: NaverPay;
constructor(s: SamsungPay, a: ApplyPay, p: Payco, n: NaverPay) {
this.samsungPay = s;
this.applyPay = a;
this.payco = p;
this.naverPay = n;
}
public pay(type: string, amount: number): void {
console.log("간편결제 서비스 호출");
switch (type) {
case "samsung":
this.samsungPay.pay(amount);
break;
case "apply":
this.applyPay.pay(amount);
break;
case "payco":
this.payco.pay(amount);
break;
case "naverpay":
this.naverPay.pay(amount);
break;
default:
throw new Error("지원하지 않는 결제수단 입니다.");
}
}
}
interface Payment {
pay(amount: number): boolean;
}
class PayService {
private payment: Payment;
constructor(payment: Payment) {
this.payment = payment;
}
pay(toAmount: number): void {
console.log("간편결제 서비스 호출");
this.payment.pay(toAmount);
}
}
class SamsungPay implements Payment {
connect(): boolean {
return this.auth();
}
private auth(): boolean {
return true;
}
pay(amount: number): boolean {
console.log("SamsungPay: PAY", amount);
return true;
}
}
https://www.nextree.co.kr/p6960/
https://i-am-your-father.notion.site/SOLID-java-e535e8a0473842f1960d418cb0610c3e#8a705566a05e4ca38ac5ebb369a91cfe
https://charming-kyu.tistory.com/35
https://levelup.gitconnected.com/the-liskov-substitution-principle-made-simple-5e69165e7ab5