Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

DeFacto-Standard IT

추상화, 캡슐화 본문

Design Pattern/References

추상화, 캡슐화

defacto standard 2017. 10. 28. 17:54

추상화

사람의 속성이나 행동을 생각해보면, 사람이라고 판단할 수 있는 속성이나 사람이 할 수 있는 행동은 너무 많으므로 사람의 모든 속성과 행동을 기술할 수는 없을 것이다. 또한 사람이라고 판단되는 속성이나 행동은 필요에 따라 선별해서 사용할 수 있다.


추상화란 어떤 영역에서 필요로 하는 속성이나 행동을 추출하는 작업을 의미한다. 추상화 덕에 관심이 쏠리는 부분에 더욱 집중할 수 있다.
구체적인 사물들의 공통적인 특징을 파악해서 이를 하나의 개념으로 다루는 수단이 추상화다.


추상화가 없다면 각각의 개체를 구분해야 할 것이다. 자동차 종류마다 엔진 오일을 교환하는 방식이 다르다고 한다면 다음과 같은 코드가 작성될 수 있다.

switch(자동차 종류)
case 아우디: //아우디 엔진 오일을 교환하는 과정을 기술
case 벤츠; //벤츠 엔진 오일을 교환하는 가정을 기술
end switch


이때 새로운 종류의 자동차 엔진 오일을 교환하는 기능을 추가하라는 요구사항이 있을 경우 case문을 더 추가해야 한다.

switch(자동차 종류)
case 아우디: //아우디 엔진 오일을 교환하는 과정을 기술
case 벤츠; //벤츠 엔진 오일을 교환하는 가정을 기술
case BMW; //BMW 엔진 오일을 교환하는 과정을 기술
end switch


3가지 자동차와 같은 구체적인 자동차 대신 이들의 추상화 개념인 자동차를 이용할 경우라면 코드는 다음과 같다.

void changeEngineOil(Car c) {
c.changeEngineOil();
}


프로시저 changeEngineOil의 인자로 아우디, 벤츠의 추상화 개념인 Car를 사용한다. 인자 어느 곳에도 구체적인 자동차 종류와 연관된 부분을 찾을 수 없다.

따라서 이 코드는 어떤 새로운 자동차가 추가되더라도 변경할 필요가 없다. 물론 인자 c가 가리키는 구체적인 자동차의 종류에 따라 changeEngineOil 메서드가 다르게 실행될 필요는 있다.(다형성)


캡슐화

SW개발자가 가장 많이 불평하는 사항은 요구사항의 변경인데, 항상 SW를 설계/구현 중에 요구사항이 변경되기 때문이다. 그러나 요구사항의 변경은 지극히 당연하다. 따라서 요구사항의 변경을 당연히 받아들이고 이에 대처하는 법을 터득해두어야 스트레스를 덜 받을 수 있다.


SW 공학에서 요구사항 변경에 대처하는 고전적인 설계 원리로는 응집도와 결합도가 있다.

응집도는 클래스나 모듈 안의 요소들이 얼마나 밀접하게 관련되어 있는지를 나타낸다.

결합도는 어떤 기능을 실행하는 데 다른 클래스나 모듈들에 얼마나 의존적인지를 나타낸다.

높은 응집도와 낮은 결합도를 유지할 수 있도록 설계해야 요굿항을 변경할 때 유연하게 대처할 수 있다.


캡슐화는 특히 낮은 결합도를 유지할 수 있도록 해주는 객체지향 설계 원리다. 정보은닉을 통해 높은 응집도와 낮은 결합도를 갖도록 한다. 정보은닉이란, 알 필요가 없는 정보는 외부에서 접근하지 못하도록 제한하는 것이다.

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];
}
}
}
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]);
}
}

ArrayStack 클래스는 배열을 사용해 스택을 구현. 자료구조에 모두 public 키워드를 붙여 외부에 공개되어 있다.

즉, StackClient 클래스처럼 push 메서드나 pop 메서드를 사용하지 않고 직접 배열에 값을 저장할 수 있다. 이런 경우 ArrayStack과 StackClient 클래스는 강한 결합이 발생한다.


ArrayList 클래스를 사용해 스택 구현이 변경되면 StackClient 클래스도 따라서 변경되어야 하는데, 이는 StackClient 클래스가 은닉된 정보를 직접 사용했기 때문이다. 따라서 은닉 정보가 변경되면 해당 정보를 사용한 쪽도 모두 변경되어야 한다.


*위 코드를 ArrayList 클래스를 이용하도록 변경

public class ArrayListStack {
public int stackSize;
public ArrayList<Integer> items;

public ArrayListStack(int stackSize) {
items = new ArrayList<Integer>(stackSize);
this.stackSize = stackSize;
}

public boolean isEmpty() {
return items.isEmpty();
}

public boolean isFull() {
return (items.size() >= this.stackSize);
}

public void push(int item) {
if (isFull()) {
System.out.println("Inserting fail");
} else {
items.add(new Integer(item));
System.out.println("Inserted Item :" + item);
}
}

public int pop() {
if (isEmpty()) {
System.out.println("Deleting fail");
return -1;
} else {
return items.remove(items.size() - 1);
}
}

public int peek() {
if (isEmpty()) {
System.out.println("Peeking fail, Array Stack is Empty");
return -1;
} else {
return items.get(items.size() - 1);
}
}
}


스택의 자료구조가 ArrayList 클래스로 인해 변경될 때 코드 2.4의 main 메서드는 더이상 유효하지 않다. 따라서 다음과 같이 은닉 내용(자료구조 형태)의 변화에 맞게 변경해주어야 한다.

public class StackClient {
public static void main(String[] args) {
ArrayListStack st = new ArrayListStack(10);
st.items.add(new Integer(10));
System.out.println(st.items.get(st.items.size() - 1));
}
}


하지만 이렇게 변경했더라도 자료구조는 필요에 따라 계속 변경될 수 있다. 이는 자료구조가 변경될 때마다 코드도 계속 변경해야 한다는 의미로, 매우 번거로운 일이며 오류가 발생하는 원인을 제공할 것이다.


이 문제를 해결하려면 변경되는 곳을 파악해 이를 은닉한다.


스택의 예에서는 자료구조가 변경될 가능성이 크므로 자료구조의 형태와 관련이 있는 top, itemArray, stakcSize 클래스를 다음과 같이 외부에서 접근하지 못하도록 private 키워드를 붙여 은닉한다.

private int top;

private int itemArray;

private int stackSize;

지금부터는 push, pop, peek 메서드의 연산으로만 스택을 사용할 수 있다. 하지만 세 메서드가 어떤 방식으로 어떤 자료구조를 사용해 작업을 실행하는지는 알 수가 없다.

즉, 스택과 이를 사용하는 코드의 결합이 낮아지는 것이다.

 

수정된 ArrayStack 클래스를 사용하려면 StackClient 클래스를 다음과 같이 변경해야 한다.

public class StackClient {
public static void main(String[] args) {
ArrayListStack st = new ArrayListStack(10);
st.push(20);
System.out.println(st.peek());
}
}


'Design Pattern > References' 카테고리의 다른 글

피터 코드의 상속 규칙  (0) 2017.10.28
일반화 관계, 다형성  (0) 2017.10.28
인터페이스, 실체화 관계  (0) 2017.10.28
의존관계  (0) 2017.10.28
집합관계  (0) 2017.10.28
Comments