
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