1. 디자인 패턴의 정의 및 필요성

방법론이 만들어지는 과정


2. 프로세스 지향 방법

절차적 방법 - 1980년대 대표적인 방법론, 처리순서를 구조화

개요

  • 정의: 기능(프로세스)이 중심이 되어 시스템을 설계하는 방법.
  • 구성 요소: 기능(프로세스), 데이터.
  • 모델 기법: DFD(Data Flow Diagram).

특징

  • 프로세스와 데이터의 분리: 기능 중심의 설계로, 프로세스와 데이터가 각각 별개의 것으로 다루어짐.
  • 함수 중심의 모듈 구성: 처리 중심의 함수가 우선 결정되며, 데이터는 별도로 존재.
  • 실세계 표현의 왜곡: 실세계를 컴퓨터가 처리하는 방식으로 변환하여 표현.

장단점

  • 장점: 명확한 처리 순서와 단계 제공.
  • 단점: 데이터와 기능의 분리로 인해 복잡성이 증가하고, 유지보수가 어려움.

3. 데이터 지향 방법

개요

  • 정의: 데이터가 중심이 되어 시스템을 설계하는 방법.
  • 구성 요소: 데이터, 데이터 구조화.
  • 대표적 소프트웨어 개발 방법론: 정보공학 방법론, E-R 다이어그램.

특징
  • 데이터 중심: 시스템이 취급하는 데이터를 먼저 구조화하고, 이를 기반으로 기능을 설계.
  • 기업 비즈니스 시스템 적용: 기업의 업무 프로세스는 수시로 변하지만 데이터는 대부분 그대로이므로 데이터 중심으로 설계하면 유지보수 횟수를 줄이고 변화에 대응 가능.

장단점

  • 장점: 데이터 일관성 유지, 유지보수 용이.
  • 단점: 데이터 변경이 시스템 전체에 영향을 미침.

4. 프로세스 지향 방법과 데이터 지향 방법의 문제점

문제점

  • 변경의 영향: 프로세스와 데이터가 분리되어 있어 한쪽이 바뀌면 다른 쪽에 영향을 미침.
  • 프로그램 복잡도 증가: 함수와 데이터가 분리되어 있어 복잡성이 증가.
  • 재사용의 어려움: 프로세스와 데이터가 분리된 구조로 인해 재사용이 어려움.
  • 유지보수의 어려움: 복잡한 구조로 인해 유지보수가 쉽지 않음.

5. 객체 지향 방법

개요

  • 정의: 기능이나 데이터 대신 객체가 중심이 되어 시스템을 설계하는 방법.
  • 구성 요소: 객체, 클래스, 메서드.

특징

  • 실세계 모델링: 실세계를 사람이 생각하는 방식으로 표현.
  • 데이터 캡슐화: 데이터와 메서드를 객체로 묶어, 객체의 인터페이스만을 통해 데이터 접근.
  • 유연성: 요구사항 변경에 유연하게 대처 가능.
  • 확장성: 객체의 추가/삭제가 쉬워 확장성이 높음.
  • 재사용성: 객체 단위로 재사용 가능.
  • 추상화: 시스템 설계를 단순화하고, 추상화를 통해 생산성과 품질을 높임.

장단점

  • 장점: 실세계 모델링, 데이터 캡슐화, 요구사항 변경에 유연한 대처, 확장성과 재사용성 높음.
  • 단점: 초기 설계가 복잡할 수 있음.

6. 디자인 패턴

정의

  • 디자인 패턴: 자주 사용하는 설계 형태를 정형화해서 이를 유형별로 설계 템플릿으로 만든 것.
  • 목적: 반복되는 설계 문제를 해결할 수 있도록 체계적으로 정리된 해결책 제공, 소프트웨어 설계를 간소화하고, 코드의 재사용성을 높이며, 유지보수를 용이하게 함.

중요성

  • 전문가의 노하우: 많은 개발자들이 경험을 통해 검증된 설계 지식을 추상화하여 일반화한 템플릿.
  • 효율적 문제 해결: 설계 문제를 해결하는 데 도움을 주며, 코드의 견고성과 재사용성을 높임.

7. 디자인 패턴 사용의 장단점

장점

  • 의사소통 원활: 디자인 패턴을 통해 개발자들 간의 원활한 의사소통이 가능.
  • 구조 파악 용이: 디자인 패턴을 사용하면 소프트웨어 구조를 쉽게 파악할 수 있음.
  • 재사용성: 미리 정의된 디자인 패턴을 사용하여 개발 시간을 단축.
  • 설계 변경에 유연: 기능 추가나 환경 변화에 유연하게 대처 가능.

단점

  • 구조적 설계에 부적합: 디자인 패턴은 주로 객체지향 설계/구현에 적합하며, 구조적 설계에는 적합하지 않음.
  • 초기 투자 비용: 디자인 패턴을 사용하면 초기 투자 비용(시간과 노력)이 더 들 수 있음.

8. Code Complete의 9가지 휴리스틱

주요 내용

  1. 객체 식별: 시스템에서 사용할 객체를 식별.
  2. 일관된 추상화 적용: 일관된 추상화를 통해 설계의 명확성 유지.
  3. 캡슐화: 구현에서 결정할 상세 사항을 캡슐화.
  4. 상속보다 합성 사용: 상속보다 합성을 통해 유연한 설계.
  5. 정보 은닉: 정보를 감추고, 필요한 정보만 노출.
  6. 느슨한 결합, 강한 응집: 모듈 간의 결합은 느슨하게, 모듈 내부는 강하게 응집.
  7. 변경 부분 식별: 변경될 가능성이 높은 부분을 식별하고, 이를 설계에 반영.
  8. 디자인 패턴 사용: 적절한 디자인 패턴을 사용하여 설계.
  9. 테스트 용이성: 테스트하기 쉽게 설계.

9. 상속과 합성

상속 (Inheritance)

  • 정의: 클래스가 다른 클래스의 속성과 메서드를 상속받아 사용하는 것.
  • 장점: 코드 재사용성, 상위 클래스의 기능을 하위 클래스에서 사용 가능.
  • 단점: 상위 클래스의 변경이 하위 클래스에 영향을 미칠 수 있음, 캡슐화를 깨뜨릴 수 있음.(is-a관계 이외에는 사용을 자제해야 한다)

합성 (Composition)

  • 정의: 클래스가 다른 클래스의 인스턴스를 포함하여 사용하는 것.
  • 장점: 재사용이 용이, 클래스 라이브러리 유지 간단, 캡슐화를 유지, 결합을 약하게 함, 런타임 바인딩 가능.
  • 단점: 상속보다 구현이 복잡할 수 있음.

Gangs of Four (GoF) 패턴 요약

생성 패턴 (주상 객체 인스턴스화)

  • 추상 팩토리, 팩토리, 빌더, 프로토타입, 싱글톤

구조 패턴 (객체 결합)

  • 어댑터, 브리지, 컴포지트, 데코레이터, 퍼사드, 플라이웨이트, 프록시

행위 패턴 (객체 간 커뮤니케이션)

  • 책임 체인, 커맨드, 인터프리터, 반복자 (Iterator), 중재자, 메멘토, 옵서버, 상태, 전략 (Strategy), 템플릿 메소드, 비지터

요약

  • 목적: 너무 일반적이지도, 너무 구체적이지도 않은 형태로 소프트웨어 설계를 위한 지식이나 노하우를 공유.
  • 에릭 감마, 리차드 헬름, 랄프 존슨, 존 블리시데스가 제안한 GoF 23개의 디자인 패턴이 설계 목적에 따라 구분됨.

10. 싱글톤 패턴 (Singleton Pattern)

정의

  • 싱글톤 패턴: 클래스의 인스턴스를 하나만 생성하도록 제한하는 디자인 패턴.
  • 목적: 특정 클래스의 객체가 오직 하나만 존재하도록 보장, 자원 낭비 방지.

구현 방법

  • 생성자 private: 외부에서 생성자를 호출하지 못하도록 private으로 설정.
  • static 인스턴스: 클래스 내부에 static으로 인스턴스를 선언.
  • getInstance() 메서드: 인스턴스를 반환하는 public 메서드를 제공.
  • 동기화: 멀티스레드 환경에서 안전하게 인스턴스를 반환하도록 동기화.

사용 예

  • 난수 생성기: 난수를 생성하는 객체를 하나만 유지.
  • 프린터 스풀러: 프린터에 출력할 데이터를 관리하는 객체를 하나만 유지.
  • 점수 기록표: 점수를 기록하는 객체를 하나만 유지.



인터페이스의 개념

정의

  • 인터페이스: 클래스가 구현해야 하는 메서드들의 집합을 정의하는 것. 기능의 선언과 기능의 구현을 분리
  • 목적: 클래스들이 일관된 방식으로 특정 기능을 수행하도록 강제. 기능을 사용하는 통로

장점

  • 일관성: 인터페이스를 구현함으로써 일관된 메서드 시그니처를 유지할 수 있음.
  • 유연성: 인터페이스를 통해 다형성을 구현할 수 있어, 다양한 클래스들이 동일한 인터페이스를 구현하여 서로 교환 가능.
  • 독립성: 인터페이스만 준수하면, 클래스의 구체적인 구현 방식과 관계없이 사용할 수 있음.
  • 다중 상속 효과: 자바는 클래스의 다중 상속을 지원하지 않지만, 인터페이스를 통해 다중 상속의 효과를 얻을 수 있음.
// 인터페이스 정의
public interface Weapon {
    void attack();
}

// 인터페이스 구현 클래스
public class Sword implements Weapon {
    @Override
    public void attack() {
        System.out.println("칼 공격");
    }
}

public class Knife implements Weapon {
    @Override
    public void attack() {
        System.out.println("검 공격");
    }
}

// 인터페이스 사용
public class GameCharacter {
    private Weapon weapon;

    // 무기 교환
    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    // 공격
    public void attack() {
        if (weapon == null) {
            System.out.println("맨손 공격");
        } else {
            weapon.attack();
        }
    }
}

// 메인 클래스
public class Main {
    public static void main(String[] args) {
        GameCharacter character = new GameCharacter();

        character.setWeapon(new Sword());
        character.attack();  // 출력: 칼 공격

        character.setWeapon(new Knife());
        character.attack();  // 출력: 검 공격
    }
}



11. 전략 패턴 (Strategy Pattern)

정의

  • 전략 패턴: 여러 알고리즘을 하나의 추상적인 접근점(interface)을 통해 접근 가능하게 하여 알고리즘을 쉽게 교체할 수 있도록 하는 디자인 패턴.

구성 요소

  • 전략 인터페이스: 실행할 알고리즘을 정의.
  • 콘크리트 전략: 인터페이스를 구현하여 구체적인 알고리즘을 정의.
  • 컨텍스트: 전략을 사용하는 클래스, 전략 객체를 참조하여 메서드를 호출.

장점

  • 유연성: 알고리즘을 쉽게 교체 가능.
  • 코드 재사용성: 다양한 알고리즘을 같은 방식으로 사용할 수 있음.
  • 유지보수성: 알고리즘이 변경될 때 컨텍스트 코드를 변경할 필요 없음.

사용 예

  • 게임 캐릭터의 행동: 상황에 따라 공격 또는 방어 전략을 변경.
  • 정렬 알고리즘: 다양한 정렬 알고리즘을 전략으로 사용.










12. 어댑터 패턴 (Adapter Pattern)

정의

  • 어댑터 패턴: 호환되지 않는 인터페이스를 가진 클래스들을 함께 사용할 수 있도록 변환하는 패턴. 연관성 없는 두 객체 묶어서 사용하기

구성 요소

  • 타겟 인터페이스: 클라이언트가 사용할 수 있는 인터페이스.
  • 어댑터: 타겟 인터페이스를 구현하고, 어댑티의 메서드를 호출.
  • 어댑티: 호환되지 않는 인터페이스를 가진 기존 클래스.
  • 클라이언트: 타겟 인터페이스를 통해 어댑티를 사용하는 클래스.

장점

  • 재사용성: 기존 클래스를 수정하지 않고 재사용 가능.
  • 유연성: 새로운 인터페이스를 통해 기존 클래스의 기능을 사용할 수 있음.

사용 예

  • UI 컴포넌트: 서로 다른 UI 라이브러리를 통합하여 사용.
  • 파일 읽기: 다양한 파일 포맷을 읽기 위해 어댑터 사용.

예제

  • 두 수에 대한 연산을 수행하는 객체를 만들어야 한다:
    • 수의 두 배를 반환: twiceOf(Float):Float
    • 수의 반(1/2)을 반환: halfOf(Float):Float
  • 구현 객체 이름은 Adapter로 한다.
  • Math 클래스에서 두 배와 절반을 구하는 함수는 이미 구현되어 있다.
// 1. Math 클래스, 이미 구현된 수학적 연산을 포함하는 클래스
public class Math {
    public static double twoTime(double num) {
        return num * 2;
    }

    public static double half(double num) {
        return num / 2;
    }

    public static Double doulbed(double d) {
        return d * 2;
    }
}


// 2. Adapter 인터페이스, 필요한 연산을 정의하는 인터페이스입니다.
public interface Adapter {
    public Float twiceOf(Float f);
    public Float halfOf(Float f);
}


// 3. AdapterImpl 클래스, Adapter 인터페이스를 구현하며, Math 클래스의 기능을 어댑트합니다.
public class AdapterImpl implements Adapter {
    @Override
    public Float twiceOf(Float f) {
        return Math.doulbed(f.doubleValue()).floatValue();
    }

    @Override
    public Float halfOf(Float f) {
        System.out.println("half 함수 로그 호출");
        return (float) Math.half(f.doubleValue());
    }
}


// 4. MainClass 클래스, 클라이언트 코드로, 어댑터를 사용하여 연산을 수행합니다.
public class MainClass {
    public static void main(String[] args) {
        Adapter adapter = new AdapterImpl();
        System.out.println(adapter.twiceOf(100f)); // 출력: 200.0
        System.out.println(adapter.halfOf(100f));  // 출력: 50.0
    }
}
이 예제는 어댑터 패턴을 사용하여 기존 클래스를 수정하지 않고 필요한 인터페이스를 구현함으로써, 기존 클래스의 기능을 재사용할 수 있게 합니다 .


13. 템플릿 메서드 패턴 (Template Method Pattern)

정의

  • 템플릿 메서드 패턴: 알고리즘의 구조를 메서드에 정의하고, 하위 클래스에서 알고리즘의 세부 단계를 재정의하는 패턴입니다.
  • 목적: 전체적인 알고리즘 구조는 변하지 않으면서, 부분적으로 다른 기능을 구현할 수 있도록 합니다.

사용 시기

  • 알고리즘이 일정한 프로세스를 가질 때: 여러 단계로 나눌 수 있는 알고리즘을 구현할 때 유용합니다.
  • 알고리즘이 변경 가능성이 있을 때: 알고리즘의 일부가 변경될 가능성이 있을 때, 공통된 부분을 재사용하면서 세부 부분만 변경할 수 있습니다.

구현 단계

  1. 알고리즘을 여러 단계로 나눔: 각 단계를 메서드로 선언합니다.
  2. 템플릿 메서드 생성: 여러 단계를 호출하는 템플릿 메서드를 만듭니다.
  3. 하위 클래스에서 메서드 구현: 하위 클래스에서 나눠진 메서드들을 구현합니다.

구성 요소

  • 추상 클래스: 알고리즘의 구조를 정의하는 템플릿 메서드를 포함.
  • 콘크리트 클래스: 추상 클래스에서 정의한 템플릿 메서드를 상속받아 구체적인 알고리즘을 구현.

장점

  • 코드 재사용성: 알고리즘 구조를 재사용할 수 있음.
  • 유지보수성: 알고리즘의 변동 부분만 하위 클래스에서 재정의하면 됨.
  • 단순화: 공통적인 알고리즘 부분은 상위 클래스에서 구현.

사용 예

  • 게임 접속 처리: 보안, 인증, 권한 확인, 접속의 단계적 처리를 템플릿 메서드로 구현.
  • 엘리베이터 제어 시스템: 모터 제어의 공통 알고리즘을 템플릿 메서드로 구현.
예제: 엘리베이터 제어 시스템의 모터 구동
//1. Door 클래스
public class Door {
    private DoorStatus doorStatus;

    public Door() {
        doorStatus = DoorStatus.CLOSED;
    }

    public DoorStatus getDoorStatus() {
        return doorStatus;
    }

    public void close() {
        doorStatus = DoorStatus.CLOSED;
    }

    public void open() {
        doorStatus = DoorStatus.OPENED;
    }
}

//2. Motor 추상 클래스
public abstract class Motor {
    private Door door;
    private MotorStatus motorStatus;

    public Motor(Door door) {
        this.door = door;
        motorStatus = MotorStatus.STOPPED;
    }

    public MotorStatus getMotorStatus() {
        return motorStatus;
    }

    private void setMotorStatus(MotorStatus motorStatus) {
        this.motorStatus = motorStatus;
    }

    public void move(Direction direction) {
        MotorStatus motorStatus = getMotorStatus();
        if (motorStatus == MotorStatus.MOVING) return;

        DoorStatus doorStatus = door.getDoorStatus();
        if (doorStatus == DoorStatus.OPENED) door.close();

        moveMotor(direction);
        setMotorStatus(MotorStatus.MOVING);
    }

    protected abstract void moveMotor(Direction direction);
}

//3. HyundaiMotor 클래스
public class HyundaiMotor extends Motor {
    public HyundaiMotor(Door door) {
        super(door);
    }

    @Override
    protected void moveMotor(Direction direction) {
        // Hyundai Motor를 구동시킨다.
    }
}

//4. LGMotor 클래스
public class LGMotor extends Motor {
    public LGMotor(Door door) {
        super(door);
    }

    @Override
    protected void moveMotor(Direction direction) {
        // LG Motor를 구동시킨다.
    }
}