Posted:      Updated:

자바 디자인 패턴 스터디를 하며 ‘Java 객체 지향 디자인 패턴’ 교재를 정리한 글입니다.

추상화

  • 속성이나 행동을 추출하는 작업
  • 필요한 부분에 집중 가능
  • 여러 개체들을 집합으로 파악
  • 예시) 자동차
    • 아우디의 자동차들
    • 벤츠의 자동차들
    • 아우디와 벤츠 두 곳의 자동차들을 ‘자동차’ 로 언급할 수 있음
    • 자동차는 아우디와 벤츠의 추상적 개념
  • 구체적 개념보다 추상적 개념에 의존해야 함


캡슐화

요구 사항

요구사항의 변경은 당연한 것이다.

  • 응집도: 클래스나 모듈 안의 요소들이 얼마나 밀접하게 관련되어 있는지
  • 결합도: 어떤 기능을 실행하는 데 다른 클래스나 모듈들에 얼마나 의존적인지
  • 응집도는 높게, 결합도는 낮게

캡슐화란?

  • 정보 은닉(information hiding)을 통한 높은 응집도, 낮은 결합도
  • 알 필요가 없는 정보는 외부에서 접근하지 못하도록 제한함
  • 예시) 자동차의 가속 페달을 밟았을 때 어떤 과정을 거쳐 속도가 올라가는 지 몰라도 됨

정보 은닉

  • 멤버가 public 으로 외부에 공개되어 있는 Array Stack
public class ArrayStack {
    public int top;
    public int[] itemArray;
    public int stackSize;

    public ArrayStack(int stackSize) {
        itemArray = new int [stackSize];
        top = -1;
        this.stackSize = stackSize;
    }

    public boolean isEmpty() {  // 스택이 비어있는지 검사
        return (top == -1);
    }

    public boolean isFull() {   // 스택이 꽉 차 있는지 검사
        return (top == this.stackSize -1);
    }

    public void push(int item) {    // 스택에 아이템 추가
        if(isFull()) {
            System.out.println("Inserting fail! Array stack is full!");
        }
        else {
            itemArray[++top] = item;
            System.out.println("Inserted Item: "+item);
        }
    }

    public int pop() {  // 스택의 탑에 있는 아이템 반환
        if(isEmpty()) {
            System.out.println("Deleting fail! Array Stack is empty!");
            return -1;
        }
        else {
            return itemArray[top--];
        }
    }

    public int peek() {
        if (isEmpty()) {
            System.out.println("Peeking fail! Array Stack is empty!");
            return -1;
        }
        else {
            return itemArray[top];
        }
    }
}
  • Stack Client
public class StackClient {
    public static void main(String[] args) {
        ArrayStack st = new ArrayStack(10);
        st.itemArray[++st.top] = 20;
        System.out.println(st.itemArray[st.top]);
    }
}

push 메서드, pop 메서드를 사용하지 않고 직접 배열에 값을 저장할 수 있음.
→ ArrayStack과 StackClient 클래스 간 강한 결합 발생

  • 자료구조의 형태와 관련된 항목들을 private 로 은닉
private int top;
private int[] itemArray;
private int stackSize;
  • 사용하는 코드를 수정
public class StackClient {
    public static void main(String[] args) {
        ArrayStack st = new ArrayStack(10);
        st.push(20);
        System.out.println(st.peek());
    }
}


일반화 관계

일반화는 또 다른 캡슐화

  • 객체 지향 프로그래밍 관점에서의 상속 관계
    ※ 속성이나 기능의 재사용으로 한정하면 안 됨
  • 여러 개체들이 가진 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립시키는 과정
    • 과일(바나나, 사과, 배, 오렌지)
  • 변경사항에 유연성 있게 대처할 수 있음
    • 새로운 종류의 과일이 추가되어도 코드를 수정할 필요가 없음
  • is a kind of 관계가 성립해야 함
    • 성립하지 않을 경우 상속을 사용하면 불필요한 속성, 연산도 물려받게 됨
    • ArrayList 클래스를 상속받은 Stack의 경우 → Stack is a kind of ArrayList.(거짓)

위임

  • 클래스 일부 기능만 재사용하고 싶을 경우 위임(delegation)을 사용
    • 자신이 직접 기능을 실행하지 않고 다른 클래스의 객체가 기능을 실행하도록 위임
      • 일반화 관계: 클래스 사이의 관계
      • 위임: 객체 사이의 관계
  • 위임을 사용한 일반화 대신 과정

ArrayList 클래스를 상속받은 Stack의 위임

public class MyStack<String> extends ArrayList<String> {
    public void push(String element) {
        add(element);
    }

    public String pop() {
        return remove(size() - 1);
    }
}
  • 자식 클래스에 부모 클래스의 인스턴스를 참조하는 속성을 만들고, this로 초기화
public class MyStack<String> extends ArrayList<String> {
    private ArrayList<String> arList = this;

    public void push(String element) {
        add(element);
    }

    public String pop() {
        return remove(size() - 1);
    }
}
  • 서브 클래스에 정의된 각 메서드에 1번에서 만든 위임 속성 필드를 참조하도록 변경
public class MyStack<String> extends ArrayList<String> {
    private ArrayList<String> arList = this;

    public void push(String element) {
        arList.add(element);
    }

    public String pop() {
        return arList.remove(arList.size() - 1);
    }
}
  • 서브 클래스에서 일반화 관계 선언 제거, 위임 속성 필드에 슈퍼 클래스의 객체를 생성해 대입
public class MyStack<String> {
    private ArrayList<String> arList = new ArrayList<>();

    public void push(String element) {
        arList.add(element);
    }

    public String pop() {
        return arList.remove(arList.size() - 1);
    }
}
  • 서브 클래스에서 사용된 슈퍼 클래스의 메서드에도 위임 메서드 추가
public class MyStackDelegation<String> {
    private ArrayList<String> arList = new ArrayList<>();

    public void push(String element) {
        arList.add(element);
    }

    public String pop() {
        return arList.remove(arList.size() - 1);
    }
    
    public boolean isEmpty() {
        return arList.isEmpty();
    }
    
    public int size() {
        return arList.size();
    }
}
  • 기능을 재사용할 때는 위임 사용하기

집합론 관점의 일반화 관계

  • 일반화 관계는 수학의 집합론과 매우 밀접
  • 부모 클래스 A는 전체 집합의 A, 자식 클래스 A1, A2, A3는 A의 부분 집합
  • 연관 관계 단순화 가능
  • 상호 배타적인 부분 집합으로 나누는 과정에 적용 가능
  • 다중 분류: 한 인스턴스가 동시에 여러 클래스에 속할 수 있음
    • 쇼핑몰의 회원 등급을 구매액에 따른 VIP 회원, 일반 회원으로 분류하고 동시에 쇼핑몰과 동일한 지역 주민인지 아닌지에 따라 분류하는 것
    • 변별자: 집합을 분류하는 분류 기준. UML 에서는 일반화 관계 선 옆에 표시
      • 변별자를 사용해 집합을 부분 집합으로 나눌 때 <<다중>> 스테레오 타입을 사용해야 함
  • 동적 분류: 한 클래스의 인스턴스가 다른 클래스의 인스턴스로 할당될 수 있음
    • <<동적>> 스테레오 타입 사용
  • 일반화는 자식 클래스들의 적절한 합집합과 교집합으로 이루어짐

특수화

  • 일반화의 역관계
  • 부모 클래스에서 자식 클래스를 추출하는 과정
  • 어떤 속성이나 연관 관계가 특정 자식 클래스에서만 관련 있고 다른 자식 클래스에서는 관련이 없는 경우


다형성

  • 서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 능력
  • 자식 클래스를 한 번에 처리할 수 있게 함
    • 애완동물 중 고양이, 강아지, 앵무새 등 각각의 우는 소리를 talk 연산으로 표현
public abstract class Pet {
    public abstract void talk();
}

public class Cat extends Pet {
    @Override
    public void talk() {
        System.out.println("야옹");
    }
}

public class Dog extends Pet {
    @Override
    public void talk() {
        System.out.println("멍멍");
    }
}

public class Parrot extends Pet {
    @Override
    public void talk() {
        System.out.println("안녀엉");
    }
}

public class Main {
    public static void main(String[] args) {
        Pet p = new Parrot();
        p.talk();
    }
}

실행 결과

안녀엉


피터 코드의 상속 규칙

  • 상속의 오용을 막기 위해 상속의 사용을 엄격하게 제한하는 규칙
  • 한 가지라도 만족하지 않으면 상속을 쓰면 안 됨
  1. 자식 클래스와 부모 클래스 사이는 역할 수행 관계가 아니어야 함
    • 사람과 운전자, 회사원에서 운전자는 사람이 수행하는 역할 중 하나이며 회사원도 어떤 순간에 수행하는 역할
  2. 한 클래스의 인스턴스는 다른 서브 클래스의 객체로 변환할 필요가 절대 없어야 함
    • 사람이 운전자의 역할과 회사원의 역할을 수행할 때 객체 변환 작업이 필요
  3. 자식 클래스가 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행해야 함
  4. 자식 클래스가 단지 일부 기능을 재사용할 목적으로 유틸리티 역할을 수행하는 클래스를 상속하지 않아야 함
  5. 자식 클래스가 역할, 트랜잭션, 디바이스 등을 특수화해야 함

댓글남기기