Contents
■ 객체 지향 설계 원칙 (SOLID 원칙)전략 패턴은 다양한 알고리즘(혹은 행동)을 정의하고, 각각의 알고리즘을 별도의 클래스로 캡슐화하여 상호 교환이 가능하도록 하는 디자인 패턴입니다.
전력 패턴을 사용하면 클라이언트는 구체적인 알고리즘에 의존하지 않고, 추상적인 인터페이스를 통해 다양한 알고리즘을 사용할 수 있습니다.
- 장점
- 유연성: 코드의 유연성을 높여줍니다. 새로운 알고리즘이 추가되더라도 기존 코드를 수정할 필요 없이 쉽게 확장할 수 있습니다.
- 유지보수성: 알고리즘을 개별 클래스로 분리하여, 각 알고리즘이 독립적으로 변경될 수 있어 유지보수가 용이해집니다.
- 예시
-
모자 장수의 재판 상황을 추상화하여
재판관
,진행자
,증인
과 같은 역할로 분리하면, 새로운 캐릭터가 추가되더라도 기존 코드를 수정하지 않고 쉽게 확장할 수 있습니다. -
attack()
메서드를 추상화하면, 어떤 캐릭터든 해당 메서드를 재정의하여 동적 바인딩을 통해 다양한 공격 방식을 구현할 수 있습니다.
■ 예시 코드1
// 역할 인터페이스 정의
interface Role {
void performRole();
}
// 재판관 역할 클래스
class Judge implements Role {
@Override
public void performRole() {
System.out.println("재판관이 재판을 주재합니다.");
}
}
// 진행자 역할 클래스
class Moderator implements Role {
@Override
public void performRole() {
System.out.println("진행자가 재판을 진행합니다.");
}
}
// 증인 역할 클래스
class Witness implements Role {
@Override
public void performRole() {
System.out.println("증인이 증언을 합니다.");
}
}
// 새로운 캐릭터 추가: 변호사 역할 클래스
class Lawyer implements Role {
@Override
public void performRole() {
System.out.println("변호사가 변론을 합니다.");
}
}
// 재판 클래스
class Court {
// 역할을 수행하는 메서드
void startTrial(Role role) {
role.performRole();
}
}
// 테스트 클래스
public class Test {
public static void main(String[] args) {
Court court = new Court();
// 재판 시스템에서 역할 수행
Role judge = new Judge();
Role moderator = new Moderator();
Role witness = new Witness();
Role lawyer = new Lawyer(); // 새로 추가된 변호사 역할
court.startTrial(judge); // "재판관이 재판을 주재합니다."
court.startTrial(moderator); // "진행자가 재판을 진행합니다."
court.startTrial(witness); // "증인이 증언을 합니다."
court.startTrial(lawyer); // "변호사가 변론을 합니다."
}
}
■ 예시 코드2
// Character 클래스: 모든 캐릭터들이 상속받을 기본 클래스
class Character {
// attack 메서드: 기본적으로 아무 동작도 하지 않는 공격 메서드
// 이 메서드는 자식 클래스에서 재정의(Override)될 예정입니다.
void attack() {}
}
// Zilot 클래스: Character 클래스를 상속받아 공격 방식을 재정의한 클래스
class Zilot extends Character {
@Override
void attack() {
// Zilot 캐릭터는 지상 공격을 수행합니다.
System.out.println("지상 공격");
}
}
// Mutalisk 클래스: Character 클래스를 상속받아 공격 방식을 재정義한 클래스
class Mutalisk extends Character {
@Override
void attack() {
// Mutalisk 캐릭터는 지상과 공중을 모두 공격할 수 있습니다.
System.out.println("지상, 공중 공격");
}
}
// Medic 클래스: Character 클래스를 상속받아 공격 방식을 재정의한 클래스
class Medic extends Character {
@Override
void attack() {
// Medic 캐릭터는 공격할 수 없기 때문에, 이 메서드에서는 공격 불가능을 출력합니다.
System.out.println("공격을 못해..");
}
}
// Test 클래스: 프로그램의 실행을 담당하는 메인 클래스
public class Test {
// 기능 메서드: Character 타입의 객체를 받아서 해당 객체의 attack 메서드를 호출하는 메서드
// 동적 바인딩을 통해 자식 클래스에서 재정의된 attack 메서드가 실행됩니다.
static void 기능(Character character) {
character.attack();
}
// main 메서드: 프로그램의 진입점으로, 여러 캐릭터 객체를 생성하고 기능 메서드를 호출합니다.
public static void main(String[] args) {
// Zilot, Mutalisk, Medic 캐릭터 객체를 생성
Character z1 = new Zilot();
Character mu1 = new Mutalisk();
Character me1 = new Medic();
// 각 캐릭터의 공격 방식 실행
// 동적 바인딩에 의해 실제 객체의 타입에 맞는 attack 메서드가 호출됩니다.
기능(z1); // "지상 공격" 출력
기능(mu1); // "지상, 공중 공격" 출력
기능(me1); // "공격을 못해.." 출력
}
}
■ 객체 지향 설계 원칙 (SOLID 원칙)
- DIP는 고수준 모듈이 저수준 모듈에 의존하지 않도록 합니다.
- 추상화된 인터페이스나 상위 클래스에 의존하도록 설계하는 원칙입니다.
- 변화 가능성이 적은 추상적인 것에 의존하고,
- 구체적인 것에 의존하지 않도록 하여 유연성과 유지보수성을 높입니다.
■ 의존 역전 원칙 (Dependency Inversion Principle, DIP)
- 예시
static void 레이싱(Car car)
메서드에서Car
에 의존하며,Sonata
와 같은 구체적인 클래스에 의존하지 않습니다.- 구체적인 클래스가 아닌 추상적인 클래스나 인터페이스에 의존하도록 함으로써 코드의 유연성을 확보합니다.
■ 개방-폐쇄 원칙 (Open-Closed Principle, OCP)
- OCP는 소프트웨어 모듈은 확장에는 열려 있어야 하고
- 수정에는 닫혀 있어야 한다는 원칙
- 새로운 기능을 추가할 때 기존 코드를 수정하지 않고, 새로운 클래스를 추가함으로써 확장할 수 있어야 합니다.
- 예시
instanceof
를 사용하는 타입 검사는 OCP를 위반합니다.레이싱(Car car)
메서드에서instanceof
를 사용하지 않고, 자식 클래스들이 메서드를 재정의(Override)하여 동작을 다르게 구현하도록 함으로써 OCP를 준수할 수 있습니다.
■ 단일 책임 원칙 (Single Responsibility Principle, SRP)
- SRP는 클래스는 하나의 책임만 가져야 하며
- 그 책임은 하나의 변경 이유만 있어야 한다는 원칙입니다.
- 책임을 명확히 분리함으로써 코드의 이해도와 유지보수성을 높일 수 있습니다.
- 예시
- 자동차 클래스에서 엔진은 엔진의 역할만 담당해야 하며,
- 다른 역할은 별도의 클래스로 분리해야 합니다.
- 책임을 분리하면, 각각의 클래스가 독립적으로 변경될 수 있어 코드가 더 명확하고 관리하기 쉬워집니다.
■ 예시 코드
// 결제 전략 인터페이스 (DIP 적용)
interface PaymentStrategy {
void pay(int amount);
}
// 구체적인 결제 전략들 (OCP 적용)
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println(amount + "원을 신용카드로 결제합니다.");
}
}
class PaypalPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println(amount + "원을 페이팔로 결제합니다.");
}
}
class BitcoinPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println(amount + "원을 비트코인으로 결제합니다.");
}
}
// 결제 처리 클래스 (SRP 적용)
class PaymentProcessor {
// 결제 전략을 필드로 보유 (DIP 적용)
private PaymentStrategy paymentStrategy;
// 결제 전략 설정 메서드
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 결제 처리 메서드
public void processPayment(int amount) {
if (paymentStrategy != null) {
paymentStrategy.pay(amount);
} else {
System.out.println("결제 전략이 설정되지 않았습니다.");
}
}
}
// 테스트 클래스
public class Test {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor();
// 신용카드 결제 처리
processor.setPaymentStrategy(new CreditCardPayment());
processor.processPayment(10000); // "10000원을 신용카드로 결제합니다."
// 페이팔 결제 처리
processor.setPaymentStrategy(new PaypalPayment());
processor.processPayment(20000); // "20000원을 페이팔로 결제합니다."
// 비트코인 결제 처리
processor.setPaymentStrategy(new BitcoinPayment());
processor.processPayment(30000); // "30000원을 비트코인으로 결제합니다."
}
}
■ 전략 패턴의 의도
- 전략 패턴의 목적은 다양한 알고리즘이나 행동들을 정의하고,
- 이들을 별도의 클래스로 캡슐화한 후, 이들 클래스를 클라이언트 코드에서 상호 교환 가능하게 만드는 것입니다.
- 이를 통해 클라이언트 코드는 구체적인 알고리즘에 의존하지 않고, 더 유연하게 설계할 수 있습니다.
정리 사이트 참고
Share article