inblog logo
|
LifeLog, DevLog
    Java

    SOLID 원칙

    객체 설계의 지침서
    KYJTHEYJ's avatar
    KYJTHEYJ
    Nov 06, 2025
    SOLID 원칙
    Contents
    1) SRP: 단일 책임 원칙2) OCP: 개방-폐쇄 원칙3) LSP: 리스코프 치환 원칙4) ISP: 인터페이스 분리 원칙5) DIP: 의존 역전 원칙

    1) SRP: 단일 책임 원칙

    // 주문 아이템 클래스
    class OrderItem {
        final String name; 
    	final int price; 
    	final int qty;
    
        OrderItem(String name, int price, int qty) { 
    		this.name = name;
    		this.price = price;
    		this.qty = qty; 
    	}
    }
    
    // 주문 클래스는 상태만 보유
    class Order {
        private List<OrderItem> items = new ArrayList<>();
        
    	void add(OrderItem i) { 
    		items.add(i);
    	}
    
        List<OrderItem> items() { return items; } // 상태만 보유
    }
    
    // 계산 하는 것만 보유
    class OrderCostCalculator {
        int totalCost(Order order) {
       	 	return order
    		   .items()
    		   .stream()
    		   .mapToInt(i -> i.price * i.qty)
    		   .sum();
        }
    }
    
    • 각 클래스의 기능은 단일로만 갖도록 작성한다


    2) OCP: 개방-폐쇄 원칙

    interface DiscountPolicy {
        int apply(int price);
    }
    
    class NoDiscount implements DiscountPolicy {
        public int apply(int price) {
    		return price;
    	}
    }
    
    class RateDiscount implements DiscountPolicy {
        private final double rate;
        
    	RateDiscount(double rate) {
    		this.rate = rate;
    	}
        
    	public int apply(int price) {
    		return (int) Math.round(price * (1 - rate));
    	}
    }
    
    // 확장은 새 구현 추가로 해결
    class Checkout {
        int pay(int price, DiscountPolicy policy) {
            return policy.apply(price); 
    		
        }
    }
    
    • 새로운 할인 정책이 추가 되었다면 클래스를 추가만 하면 된다
      기존 코드의 수정은 불필요하다


    3) LSP: 리스코프 치환 원칙

    // 상위 클래스 - Car
    class Car {
    	void start() {
    		System.out.println("start car");
    	}
    
    	void drive() {
    		System.out.println("drive car");
    	}
    }
    
    // 하위 클래스 - ElectricCar
    class ElectricCar extends Car {
    	void charge() {
    		System.out.println("charge..");
    	}
    }
    
    // 이 경우에 캐스팅을 통해 아래와 같이 선언했을 때 문제가 없어야한다
    Car car = new ElectricCar();
    car.drive();
    
    // 이를 갑자기 ElectricCar의 drive가 갑자기 배터리 부족의 조건을 갖는다면
    // 이는 LSP 위반이다
    
    // -----------------------------------------------------------
    // 올바르게 이 것을 지키기 위해선 공통 기능만 부모로 두고 따로
    // 인터페이스로 구현해야하는 것이 맞다
    // drive를 interface로 뽑아내어 새로 만드는 것이다
    
    class Car {
    	void start() {
    		System.out.println("start car");
    	}
    }
    
    interface Driveable() {
    	void drive();
    }
    
    class ElectricCar extends Car implements Driveable {
    	int battery = 100;
    
    	boolean canDrive() {
    		return battery > 0;
    	}	
    
    	void drive() {
    		if(canDrive) {
    			System.out.println("drive ElectricCar");
    		} else {
    			System.out.println("please charge");
    		}
    	}
    }
    • 하위는 상위 타입의 조건을 동일하게 유지한다


    4) ISP: 인터페이스 분리 원칙

    // 기능 별 인터페이스 분리
    interface Printer { void print(String doc); }
    interface ScannerDevice { String scan(); }
    interface Fax { void sendFax(String doc); }
    
    // 필요한 것만 의존
    class SimplePrinter implements Printer {
        public void print(String doc) { 
    		System.out.println("Print: " + doc); 
    	}
    }
    
    class MultiFunctionMachine implements Printer, ScannerDevice, Fax {
        public void print(String doc) { /* ... */ }
        public String scan() { /* ... */ return "scanned"; }
        public void sendFax(String doc) { /* ... */ }
    }
    
    class ReportService { // 보고서는 프린터 출력만 의존한다
        private final Printer printer;
        ReportService(Printer printer) { this.printer = printer; }
        void printMonthly() { printer.print("monthly report"); }
    }
    
    // 좀 더 정리하자면
    // 저 기능별을 Mach 라는 인터페이스를 만들어 전체 기능을 담아버리면
    // 단순 프린터에 저 모든 인터페이스를 구현해야한다
    // 이는 ISP 위반이다
    
    • 자신이 쓰는 기능의 인터페이스만 의존한다


    5) DIP: 의존 역전 원칙

    interface Notifier { void send(String to, String message); }
    
    class EmailNotifier implements Notifier {
        public void send(String to, String message) { /* EMAIL 전송 */ }
    }
    
    class SmsNotifier implements Notifier {
        public void send(String to, String message) { /* SMS 전송 */ }
    }
    
    //-----------------------------------------------------------
    
    // 이를 만약 직접 생성한다면 이 코드로는 이메일로만 알림을 보낼 수 있다
    // DIP 위반
    class SignupService {
        private final EmailNotifier notifier 
    		= new EmailNotifier(); // 직접 생성
    
        void signup(String userEmail) {
            notifier.send(userEmail, "가입 완료");
        }
    }
    
    // 의존을 인터페이스를 대상으로 한다면
    class SignupService {
        private final Notifier notifier;
    	
    	// 상위 인터페이스 의존
        SignupService(Notifier notifier) {
    		this.notifier = notifier;
    	}
    
        void signup(String userEmail) {
            notifier.send(userEmail, "가입 완료");
        }
    }
    
    // 추후 회원가입시 발송 루트의 자유도 보장
    class App {
        public static void main(String[] args) {
            Notifier notifier = new EmailNotifier(); 
    		// 필요 시 SmsNotifier로 교체, 필요시 다른 확장 가능
            
    		SignupService service = new SignupService(notifier);
            service.signup("user@example.com");
        }
    }
    
    • 상위는 구체 구현이 아니라 인터페이스에 의존한다
      구현 교체가 쉽기 때문이다

    Share article
    Contents
    1) SRP: 단일 책임 원칙2) OCP: 개방-폐쇄 원칙3) LSP: 리스코프 치환 원칙4) ISP: 인터페이스 분리 원칙5) DIP: 의존 역전 원칙

    LifeLog, DevLog - https://github.com/KYJTHEYJ

    RSS·Powered by Inblog