'캡슐화'에 해당되는 글 2건

  1. 2016.09.23 객체지향 용어 정리
  2. 2016.08.15 최소 공개의 원칙 4

객체지향 용어에 대한 글들을 찾아보니 개념의 일부만 정리되어 있거나 매우 중요한 개념이 제대로 다루어지지 않은 경우가 많아 보인다. 특히 비 객체지향 언어와의 정합성, 동일하거나 유사한 용어들에 대한 명확한 정리가 없는 점이 안타깝다. 그래서 객체지향 및 그와 연관된 개념들을 생각나는 대로 모두 정리해 보았다. 이 글은 아주 기초적인 내용들을 미리 알고 읽어 보는 것이 좋긴 하다. 하지만 객체지향을 접한지 오래 되었다고 해도 제대로 이해하지 못하는 중요한 부분들에 대해서 강조해 두었으니 충분히 도움이 될 것이라 생각한다.

일부 코드 예제나 추가적인 용어는 시간을 내어 더 작성할 예정이다. 내용이 많으니 시간을 충분히 가지고 도전하기 바란다.


객체지향 기본 요소를 이용한 코드 예제

// interface

interface IShape{ // IShape 타입

    public void draw();// 공개 추상 메소드

}

// 추상 클래스

abstract class Base{

    abstract public void init();// 추상 메소드의 프로토타입

}

// 클래스

class Subject

    extends Base// 상위 클래스, 부모 클래스를 상속한다

    implements IShape// 인터페이스를 구현한다

    // SubjectSubject 고유 타입이면서 Base 타입이고, IShape 타입이기도 하다

{

    Color color;// 변수, 참조 변수

   

    int width;// 변수, primitive 변수

   

    public void setColor(Color c){// 공개 메소드

        // csetColor 메소드의 매개 변수이다.

        this.color = c; // 여기서 cSubject의 멤버 객체가 된다.

    }

   

    @Override

    public void init(){}// 재정의. 추상 메소드를 구현한다

   

    @Override

    public void draw(){

        System.out.println("draw subject");

        color.brighter();// 위임

    }

}

public static void main(String[] args) {

    Subject subject = new Subject();// 객체화.

    // subjectSubject 객체를 참조하고 있다.

    Base base = (Base)subject;// up casting

    Subject subject2 = (Subject)base;// down casting

}


[JAVA] 자바 언어에서만 사용하는 용어

[C++] C++에서만 사용하는 용어

[정적] 코드 상에 존재하는 개념. 정적인 코드를 통해 동적인 것들을 만들어 내게 된다.

[동적] 실제 소프트웨어 동작 시 작동하는 것에 대한 개념. 메모리 상에 올라가 있는 것.

[OOP] OOP 개념이 아닌 소프트웨어 용어. OOP 용어와의 대비를 위해 사용.

~ : 대체적으로 동의어로 사용 가능(비 객체지향 용어지만, 객체지향에는 없는 용어이므로 대충 객체지향 언어로는 이와 같다는 의미)

 

[OOP] 함수(function) = 기능 ~ 행위(Operation) = 메소드(Method) = 멤버 함수 = 공개(public) 메소드 + 비공개(private) 메소드

-       속성에 대한 조작을 수행하는 명령어들을 모아 놓은 단위. 소프트웨어가 동작한다는 의미는 이 메소드가 실행된다는 것을 의미한다.

 

[OOP] 변수(variable) ~ 속성(Attribute) = 필드(field) = 데이터 = 멤버 변수 = 참조 변수 + primitive 변수

-       변경 가능한 값(value)을 저장할 수 있는 프로그래밍 요소. 메모리 상의 공간을 확보하고 있으면서 정ㅇ해진 타입을 가지고 있는 것.


[OOP] primitive 변수

-       비 객체지향 언어에서 말하는 기본 자료형. char, short, int, long 등과 같이 객체가 아닌 멤버 변수를 의미.


[정적] 참조 변수

-       객체가 다른 객체를 참조할 수 있도록 선언된 변수.

-       이 참조 변수가 멤버 객체를 가리키게 된다. 참조 변수를 가지고 있는 객체와 멤버 객체 간의 관계를 “has-a” 관계라고도 한다. 마치 한 객체가 (참조 변수를 통해) 다른 객체를 내부에 가지고 있는 것처럼 보이기 때문이다.

 

[동적] 멤버 객체 = 위임(delegation) 객체 = 참조(reference) 객체 = 내부 객체 = 레퍼런스(reference)

-       다른 객체의 필드로 선언되어 있는 객체

-       멤버 객체는 참조 변수를 통해 사용된다.

 

위임(Delegation)

-       1. A라는 객체가 가진 책임(Responsibility)을 수행하기 위해서 멤버 객체에게 책임의 일부 또는 전부를 수행하도록 시키는 것.

-       2. 멤버 객체를 이용하기 위해서 멤버 객체의 메소드를 호출하는 행위

 

책임(Responsibility)

-       객체가 설계된 기능적 목적. 객체가 해야 할 일.

 

공개(public) 메소드 = 공용 메소드 ~ API(Application Programming Interface) = 인터페이스(interface)

-       1. 객체의 외부에서 호출이 가능하도록 노출된 메소드

-       2. 객체 외부로부터의 명령을 받는 메소드. 이 특성 때문에 모든 공개 메소드의 명칭은 명령형으로 작성되어야 하고 그렇게 해석되어야 한다.

 

비공개(private) 메소드 = 전용 메소드

-       객체 내부에서만 호출이 가능하도록 감춰진 메소드.


접근성(visibility)

-       공개(public) / 보호(protected) / 비공개(private)과 같이 객체의 내부와 외부 또는 상속 관계에 따라서 멤버 변수나 메소드에 대한 접근 권한을 제어하는 키워드.

 

인터페이스(interface)

-       1. 공개 메소드와 동일한 의미로 사용되는 용어.

-       2. [JAVA] 공개 추상(abstract) 메소드를 가진 객체화 불가능한 상위 타입

 

[JAVA] interface

-       1. 순수하게 공개 추상 메소드로만 이루어진 추상 클래스.

-       2. [JAVA] 공개 추상(abstract) 메소드를 가진 객체화 불가능한 상위 타입

-       (원래 1번의 의미가 가장 강하지만 최근에는 static 필드와 default 메소드(구현이 있는 메소드)가 추가되어 엄밀하게는 1번이 정확한 의미는 아니다. 하지만 클래스와 인터페이스를 비교하는 개념으로써는 1번이 맞다.)

-       3. 인터페이스끼리의 상속(extends)을 통해 확장이 가능하고, 하나의 클래스가 여러 인터페이스를 구현(implements)하는 것이 가능하다. 따라서 C++과 같은 언어의 다중 상속 문제를 회피할 수 있는 대안으로 이해 되기도 한다.

 

추상(abstract) 클래스 = [C++]가상(virtual) 클래스

-       객체화 될 수 없는 클래스.

-       보통은 적어도 한 개 이상의 추상 메소드를 가지고 있는 클래스를 의미한다.

-       ([JAVA] interface는 모든 메소드가 공개 추상 메소드인 추상 클래스이다.)

 

추상(abstract) 메소드 = [C++]가상(virtual) 함수

-       프로토타입(prototype)만 있고 동작을 정의한 구현부가 없는 메소드.

 

[OOP] 프로토타입(prototype)

-       [리턴타입][이름][(매개변수)]

-       형식의 예 : int add(int a, int b);

 

[OOP] 매개 변수(argument) = 인자 = [OOP]파라메터(parameter)

-       메소드에 input으로 들어가는 변수.

-       객체와 primitive 변수 모두 매개 변수가 될 수 있음.

 

타입(Type) *** 중요 ***

-       [OOP] 1. 변수의 자료형(char, short, int, long, struct )

-       2. 객체가 다뤄질 수 있는 형식명. 객체가 XX 타입이라는 것은 객체가 XX 클래스 인터페이스로 참조(reference) 가능하다는 것을 의미한다.

 

참조(reference)

-       객체를 지칭하는 행위. 객체를 참조 변수에 할당하는 행위

 

참조 변수(reference variable)

-       Object aObject; à 여기서 aObject Object 타입의 객체를 지칭하는 참조 변수이다.


[정적] 클래스(Class) = 타입(Type) + 공개 메소드 + 비공개 메소드 + 필드

-       객체에 대한 명세서. 청사진.

-       객체지향 소프트웨어를 사전으로 비유하자면 클래스는 단어에 해당한다. ‘단어는 그 의미를 나타내는 설명을 가지고 있다. ‘설명에 해당하는 것이 클래스를 구현한 코드이다. 그러면 객체는? 단어로 지칭할 수 있는 실제 세계의 존재이다. ‘사과단어’, 즉 클래스이고 실제 사과는 객체이다. 따라서 사과라는 클래스는 한 개 뿐이지만, ‘실제 사과는 여러 개가 될 수 있다.

(예제)

 

-       공개 메소드는 특별히 분리할 필요가 있다. 공개 메소드는 클래스가 객체화 되었을 때 외부와 소통할 수 있는 유일한 통로이다. 객체는 수동적인 식물과 같아서 외부에서 공개 메소드를 통해 자극을 주지 않으면 아무것도 하지 않는 것이 기본이다.(동적(active) 객체는 예외.)

-       클래스가 메소드와 필드로 이루어져 있다는 말은 거짓말이다. 클래스에게 가장 중요한 것은 타입이다. 따라서 클래스는 타입과 메소드(공개 + 비공개), 그리고 필드로 이루어져 있다는 것이 정답이다. 타입은 상속을 통해 다형성과 같은 객체지향의 가장 중요한 개념을 형성해 주기 때문에 클래스가 가지고 있는 것 중에서 가장 중요한 것이다.

 

[동적] 객체(Object) = 인스턴스 = 상위 타입 + 고유 타입 + 공개 메소드 + 아이덴티티(identity)

-       실제 메모리가 할당되어 동작하는 객체지향의 최소 단위.

-       클래스의 동적 형식. 클래스가 개념이라면 객체는 실제다. 사과가 모든 사과를 나타낼 수 있도록 정의된 단어, 즉 클래스라면 내가 들고 있는 진짜사과는 그 객체이다.

-       정적 객체의 동작은 오직 공개 메소드에 의해 일어난다. 따라서 공개 메소드는 명령으로 해석되어야 하고, 공개 메소드의 내부 구현은 공개 메소드 명칭에 맞는 명령이 전달되었을 때 수행되어야 할 일을 구현해야 한다.

-       객체는 객체화가 완료된 후, 소프트웨어의 동작에 따라 자신이 가질 수 있는 모든 타입들로 변경되어 지칭되고 사용될 수 있다. 하지만 잘 설계된 객체지향 소프트웨어 내에서 자신의 고유 타입보다는 상위 타입으로 지칭되어 사용되는 경우가 일반적이다. 특히 상위 타입으로 지칭되다가 다시 그 하위 탕입, 또는 자신의 고유 타입으로 지칭되는 것은 일종의 금기이다. 고유 타입을 사용하면 상위 타입으로 지칭됨으로써 얻을 수 있는 정보 은닉(information hiding)의 장점을 잃어버린다.

 

고유 타입

-       객체가 생성될 당시에 생성의 기반이 된 클래스 타입. new Object()라는 명령을 통해 새로운 객체가 생성되었다면 Object가 고유 타입에 해당한다.

-       고유 타입 개념은 중요하다. 어떤 객체가 어떤 타입으로 참조될 수 있는지를 결정하기 때문이다.

 

아이덴티티(identity) = 고유성

-       어떤 객체를 다른 객체와 구분 짓는 고유한 특성.

-       모든 객체는 고유성을 확인할 수 있는 공개 메소드를 가지고 있어야 한다.([JAVA] equals() 메소드)

 

정적(passive) 객체 = 일반 객체

-       내부에 쓰레드를 선언하고 있지 않은 객체.

-       공개 메소드를 통해 명령하지 않으면 아무 일도 하지 않는 객체.

-       일반적으로 객체라고 하면 보통 정적 객체를 의미한다.

 

동적 객체(active) 객체

-       내부에 쓰레드를 선언하고 있고, 쓰레드의 동작에 기반하여 작동하는 객체.

-       정적 객체의 반대말.

 

추상화(Abstraction)

-     개별적인 대상들의 차이점을 배제하고 동일한 점을 추출해 내는 것. 특히 동일한 점을 모아 클래스 또는 인터페이스화 하는 것.

-    "모델링(Modeling)" 이라는 말과 동일하게 쓰인다. "수학적 모델링"이라고 하면 현실 세계의 문제들의 개별적인 차이점을 배제하고 동일한 특성들을 파악해서 오직 수치와 공식으로 표현 가능한 요소들로 바꾸는 작업이다.

-    따라서 객체지향에서 추상화란 "클래스 모델링" 또는 "객체 모델링"이라는 말로 표현할 수 있다.

-    실세계의 예를 들면 철수, 영희, 희동이는 각자 고유한 특성을 가지고 있지만 이런 특성을 배제하고 나면 이름과 나이, 성별, 사는 곳과 같은 동일한 특성을 가진 '사람'이다. 이렇게 동일한 특성들을 모아서 '사람'이라는 이름의 클래스를 만드는 과정이 추상화(Abstraction) 과정이다.


정보 은닉(information hiding) ****** 매우 중요. 클래스, 객체보다 더 중요 ******

-       정보 은닉은 객체지향 언어가 만들어진 목적에 해당하는 개념이다.(모든 개념 중에서 가장 중요한 개념이다. 믿음 소망 사랑 중 사랑이며, good, better, best 중에 best이다. 정보 은닉만 알면 나머지 개념이 왜 생겨난 것인지를 알 수 있다.)

-       정보 은닉은 객체지향 언어를 설계한 모든 목적을 달성하기 위한 특성이다. “객체화한다는 의미로서의 캡슐화와 상속(그 중에서도 타입의 상속)은 정보 은닉을 가능하게 하기 위한 수단(일 뿐)이다.

-       객체의 고유 타입 은닉

n  객체가 생성된 이후, 고유 타입이 아닌 그 상위 타입으로 지칭되도록 함으로써 생성 이후에는 객체의 고유 타입을 모르도록 하는 것.

n  객체의 고유 타입을 모른 상태에서 구현할 수 있다는 것은 그 고유 타입에 의존하는 코드가 없다는 말이다. 이는 그 고유 타입 객체가 삭제되거나 수정되더라도 코드는 전혀 변경되지 않는다는 것을 의미한다.

n  다형성 = 서로 다른 객체들의 고유 타입을 은닉하고 동일한 상위 타입을 통해 다수의 객체를 동일하게 다루는 것.(객체의 고유 타입 은닉 중 특수 케이스에 해당함)

-       객체의 필드 및 메소드 은닉 = 캡슐화

-       타입 하위 캐스팅 금지 : 상위 타입으로 지칭된 객체를 하위 타입으로 바꿔 지칭하는 것을 금기시 함으로써 온전하게 정보 은닉을 달성할 수 있다.

-       정보 은닉의 장점

n  재사용성 : 객체가 다른 객체의 고유 타입에 의존하지 않도록 함으로써 다른 소프트웨어나 다른 모듈에서도 쉽게 이용할 수 있음.

n  유연성 : 위임 객체의 고유 타입에 의존하지 않게 함으로써 위임 객체를 교체하기 쉽게 만들어 소프트웨어 기능을 교체/확장하기 용이하도록 함.

n  유지보수성 : 객체가 가져야 할 기능들을 각각 고유한 클래스에 구현하도록 함으로써 기능의 수정 시 다른 기능에 영향을 주는 것을 최소화 함. 객체가 가진 최소한의 공개 메소드만을 호출함으로써 설계 변경 시 변경할 코드의 양을 최소화 함.

-       객체지향에서 설계를 통해 좋은 특성을 얻는다고 하는 설명이나 좋은 설계를 대표하는 디자인 패턴, 아키텍쳐 패턴, 프레임워크 구조에서 볼 수 있는 패턴들은 모두 다정보 은닉 개념을 활용한 것들이다.

 

캡슐화(encapsulation)

-       1. 다루고자 하는 변수와 그 변수를 다루는 함수를 묶어 객체로 만드는 행위

-       2. 정보 은닉의 하위 개념 중 하나로써, 객체의 필드를 비공개(private)로 하고, 꼭 필요한 경우에만 메소드를 공개를 설정하는 것.

-       캡슐화의 장점은

n  필드에 대한 임의 접근을 방지하여 의도하지 않은 정보 변경을 막을 수 있다. 필드에 대한 접근을 하는 메소드에 대해서만 관리 하면 데이터의 동기화와 같이 구현이나 테스트가 어려운 특성도 상당히 구현하기 쉬워진다.

n  메소드 공개를 최소화하여 객체간의 연관 관계를 느슨하게 함으로써 잠재적인 변경 사항의 반영을 쉽게 한다.

n  객체의 동작을 이해하는데 필요한 정보를 계층화한다.(즉 더 중요한 공개 메소드를 우선 이해하고 비공개 메소드에 대해서는 그 다음 단계에 이해하는 식이다.)

 

 

상속(inheritance)

-       상위 클래스의 타입과 공개 메소드, 필드를 물려 받는 것.

-       상속에서 가장 중요한 부분은 타입을 물려 받는다는 것이다. 이 부분에 대한 강조가 부족하여 객체지향을 제대로 이해하지 못하는 경우가 너무나 많다. 상속을 통해 타입을 물려 받으면 하위 클래스는 상위 타입으로 지칭될 수 있다. 이것이 정보 은닉의 장점을 확보할 수 있도록 해준다. 공개 메소드와 필드를 물려 받아 중복 코드를 줄일 수 있다는 점이 객체지향의 장점이라면, 정보 은닉(여기서는 하위 타입 은닉)을 통해 재사용성, 유연성, 낮은 결합도, 유지보수성, 단일 책임 등의 특성을 얻을 수 있다는 점은 객체지향 언어가 만들어진 목적에 해당한다.

-       그다지 중요한 부분은 아니지만 어떤 경우에는 메소드의 구현이나 필드를 재 구현하지 않기 위해서 상속을 이용하기도 한다고 한다. “위대하신 타입 상속에 비해 중요도가 많이 떨어지는 정보이므로 참고만 하도록 하자.

 

다형성(polymorphism)

-    하나의 객체가 서로 다른 타입으로 지칭될 수 있음을 이르는 말. 클래스가 상위 클래스를 상속하면 상위 클래스의 타입까지 상속받게 된다. 이 때 상속을 받은 클래스는 상위 클래스 타입임과 동시에 자기 자신 타입이기도 하다. 이렇게 여러 타입(= 모양 = 형)을 가질 수 있는 클래스의 특성이 바로 다형성이다.(C언어의 struct와 비교해 보라. C 언어의 struct는 기존의 struct를 내부에 선언하여 사용할 수는 있어도 기존에 있는 struct 타입으로 사용 될 수는 없다.)

-       서로 다른 객체들의 고유 타입을 은닉하고 동일한 상위 타입을 통해 다수의 객체를 동일하게 다루는 것.(정보 은닉의 한가지)

-       다형성은 오케스트라 지휘로 비유할 수 있다. 각각의 고유 타입을 가진 객체를 오케스트라의 바이올린 연주자, 첼로 연주자, 플롯 연주자 등이라고 하면 상위 타입은 연주자이다. 지휘자 격인 객체는 이들 객체를 다룰 때 바이올린 연주자연주하세요, “첼로 연주자연주하세요와 같이 이야기 하지 않는다. “연주자 여러분 연주 하세요라고 한마디만 하면 끝난다. 이처럼 각각의 객체의 다른 점보다 같은점, 즉 상위 타입(여기서는 연주자)을 통해서 여러 다른 객체들을 동일하게 다루는 것이 다형성이다. 당연히 끊임없이 여러 연주자들를 외치는 것보다 단 한번만 연주자라고 부르는 것이 코드를 줄이고 객체를 대체하는데 유리하다.

-       메소드 재정의(overrinding)도 다형성의 일종이다.

 

메소드 재정의(overriding)

-       1. 추상 메소드의 구현부를 구현하는 것.

-       2. 이미 구현부가 있는 메소드의 구현부를 대체하여 구현하는 것. 보통 2의 의미로 더 많이 쓰인다.

-       3. 재정의된 메소드는 프로토타입은 같지만 동작이 다르다. 따라서 재정의된 메소드를 이용하는 객체는 재정의 되지 않은 메소드와 동일하게 취급할 수 있고, 따라서 다형성을 이용하는 수단이 될 수 있다.

-       가급적 구현부를 대체하는 방식보다는 추상 메소드를 제공하는 편이 코드를 이해하기가 훨씬 수월하다.

 

메소드 오버로딩(method overloading)

-       동일한 명칭에 다른 인자를 받는 메소드들을 여럿 구현하는 것.

-       메소드 오버로딩은 근본적으로 같지 않은 인자에 대한 취급 방법을 동일하게 하는 것이라고 생각할 수 있다.(근본적으로 같은 것이라면 여러 메소드를 만들 필요도 없다.)

 

상위 캐스팅(up casting)

-       하위 타입 객체를 상위 타입 변수로 지칭하는 것.

 

하위 캐스팅(down casting)

-       상위 타입 변수로 지칭되던 객체를 하위 타입 변수로 지칭하는 것. 객체지향 언어에서 하지 말 것으로 정해진 것 중의 하나.

 

Has-a 관계 = association = 연관 = 연관 관계

-       어떤 객체 A가 참조 변수를 가지고 있고, 그 변수를 통해서 다른 객체 B를 멤버 객체로 가지는 관계를 A has-a B 관계라고 한다.

 

Is-a 관계 = generalization(일반화) 또는 인터페이스에 대해서는 realization

-       어떤 객체 A가 다른 객체 B의 상위 타입이고, 다른 객체 B는 객체 A의 하위 타입일 때 B is-a A 관계이다.

Posted by 이세영2
,

구조적 언어가 비 구조적 언어에 비해 발전한 부분을 나열해 보면 다음과 같다.

1. 소프트웨어의 구조적 분할 가능 : 전체가 한 덩어리였던 소프트웨어를 함수 단위로 체계적으로 분할 할 수 있게 되었다.

2. 함수간의 호출 가능 : 함수가 다른 함수를 호출할 수 있게 되면서 전체 소프트웨어의 동작 순서를 일목 요연하게 알 수 있게 되었다.

3. 함수 명명 가능 : 함수에 이름을 지을 수 있게 되면서 현재 하려는 작업의 목적을 분명하게 알 수 있게 되었다.


그러나 아직도 구조적 언어에는 문제가 많이 남아 있었고, 이것이 객체지향 언어의 탄생 배경이 되었다.


구조적 언어의 문제점

1. 변수에 대한 구조적 분할이 어려움 -> 전역 변수 문제 : 전역 변수의 출현을 효율적으로 막지 못했다.

2. 변수와 함수의 연관 관계가 불분명 : 어떤 함수가 어떤 변수를 다루는지 직관적으로 알기 힘들다.

3. 함수 내부에 대한 이해 필요 : 여전히 함수 내부를 들여다 봐야 전체 소프트웨어의 흐름을 이해할 수 있다.


구조적 언어는 많은 발전에도 불구하고 여전히 위와 같은 문제점이 있었다. 위의 문제들 때문에 아래와 같은 의도하지 않은 결과물이 나오곤 했다.

1. 분명히 똑같은 절차로 동작 시킨 것 같은데 결과는 다르게 나오는 소프트웨어 : 전역 변수의 변화를 예상하지 못해서 발생하는 문제.

2. 오류를 수정할 때 다른 함수의 동작에 영향을 미침 : 역시 전역 변수가 가장 큰 문제의 원인이다.

3. 하나의 오류를 수정하기 위해 여러 함수를 고쳐야 함


이러한 문제점의 근본적인 원인은 적절한 정보 은닉이 이루어지지 않았기 때문이다. 객체지향 언어는 이와 같은 문제점을 해결하고자 다양한 문법적인 지원과 설계의 원칙을 제시하고 있다.


우선 객체지향 언어의 아버지라 불리는 앨런 케이의 관점을 먼저 이야기 해보자.


"왜 사람들은 (큰) 컴퓨터를 작은 컴퓨터로 나누려고 하지 않는가?"

비 구조적 언어에서 구조적 언어로의 이행을 촉발한 것은 소프트웨어 규모의 확대였다. 앨런 케이는 이것을 명확히 이해하고 매우 직접적인 방식으로 해결하려고 했다. 즉, 큰 컴퓨터를 작은 컴퓨터로 나누려고 했던 것이다. 앨런 케이에게 있어서 이 작은 컴퓨터가 바로 객체였다.


"객체지향 언어에 있어서 가장 중요한 것은 메시지(메시징)이다."

여기서 메시징이라는 것은 쉽게 바꿔 말하면 상대 객체가 가진 공개(public) 메소드를 호출하는 것을 말한다. 앨런 케이의 말에 따르면 객체에게 있어서 가장 중요한 것이 공개 메소드라는 말이다.


앨런 케이의 관점을 종합해 보면 객체지향 언어는 사람이 큰 컴퓨터가 하는 일을 이해하는 것보다는 작은 컴퓨터(객체)가 하는 일을 이해하는 것이 쉽고, 그 작은 컴퓨터는 다른 컴퓨터의 메시지만 이해하고 있으면 된다는 것이다. 이것은 객체가 자신의 역할을 수행하는데 필요한 최소한의 지식만을 알고 있으면 된다는 말이다.


객체가 알아야 할 것

1. 자기 내부

2. 자기와 협력하는 객체의 외부(메시지 = 공개 함수 = 인터페이스 = API)


나머지 정보들은 모두 은폐 되는 것이 바람직하다. 그럼 이제 은폐되어야 할 정보들을 살펴보자.


캡슐화

우리가 객체지향 언어의 최소 공개 원칙 중에 첫번째로 꼽을 수 있는 부분이다. 캡슐화란 일반적으로 변수와 함수를 묶어 클래스로 선언하고, 클래스에서 외부에 노출할 부분만 선택적으로 노출시키는 것을 말한다. 이를 통해서 구조적 언어가 가지고 있던 전역 변수 문제가 해결되었다. 캡슐화가 감추는 정보를 나열해 보면 다음과 같다.

1. (대부분의) 변수 : 이로써 전역 변수 문제가 거의 해결 되었다.

2. private 함수 : 외부에서 호출할 필요가 없는 함수는 은폐되었다. 이로써 외부 객체는 협력하고자 하는 객체의 공개 함수의 외형만 알고 있으면 된다.


타입화(= 구현 은폐)

이 부분은 놓치기 쉬운데, 모든 객체는 타입(= 클래스)을 가지고 있다. 외부 객체는 해당 객체의 구현 전체에 신경 쓸 것이 아니라 그 객체의 타입만 알면 된다. 즉 타입 이외의 모든 정보는 은폐되어야 한다는 말이다.

1. 인터페이스 중심의 설계(외부적 관점과 내부적 관점의 완벽한 분리 및 은폐) : 인터페이스를 중심으로 설계하라는 원칙은 구현 내부에 대해서는 신경쓰지 말라는 말이다. 객체지향 언어에서는 상대의 인터페이스만 알면 협력이 가능하다.

2. 타입 중심의 설계 : 객체지향 언어는 모든 단위가 클래스(=타입)로 이루어져 있다. 클래스는 단순한 속성과 행위의 집합이 아니다. 클래스가 가진 것 중 가장 중요한 것이 바로 타입이다. 타입은 내부 구현을 감춰준다.

구현의 은폐는 수정에 용이한 코드를 만들어 준다. 예를 들어 기존의 객체가 문제가 있거나 다른 기능을 수행하는 객체를 사용해야 한다고 했을 때, 기존의 코드가 타입에만 의존하고 있었다면 그 코드는 수정하기 용이하다. 실제 동작시에 새로 만들어진 객체를 할당해 주기만 하면 기존 코드는 그대로 사용할 수 있기 때문이다.


타입 은폐

심지어는 타입 조차도 감추는 것이 좋다. 정확하게 말해서 상위 타입이 외부에 노출되어 있다면 하위 타입을 감추는 것이 좋다는 말이다.

List<String> list = new ArrayList<String>();

많은 코드에서 이와 같은 형태로 ArrayList를 사용한다. 그 이유는 ArrayList를 코드에서 사용하는 순간 그 코드는 오직 ArrayList를 위한 코드가 되기 때문이다. 객체지향에서는 구체적인 타입을 밝히는 것을 꺼려 한다. 구체적인 타입을 밝히는 순간 그 코드는 그 구체적인 타입 "만"을 위한 코드가 되기 때문이다. 그러면 다른 구체적인 타입을 위한 코드를 또 만들어야 한다. 바로 중복이 발생한다. 또 구체적인 타입에 대해 작성한 코드를 다른 타입에 대해 작성하기 위해서는 상위 타입으로 작성된 코드에 비해 더 많은 수정이 필요하다. 그래서 가능한 한 하위 타입을 은폐하라는 것이다.


SOLID 원칙 중 LSP (Liskov Substitution Principle)은 타입 하이딩에 대한 대표적인 설계 원칙이다. 하위 타입을 구현할 때 상위 타입으로 치환 가능하도록 만들어야 한다. 그래야 하위 타입을 감출 수 있다. 감춰야 하는 이유는 이미 설명한 바와 같다.


다형성 역시 하위 타입을 감추는 목적 중 하나이다. 다형성은 다양한 하위 타입별로 작성되어야 할 코드들을 하나로 묶어준다. 이는 중복을 제거하고 간결한 코드를 만들어 준다.


Factory Method 패턴 : Factory Method 패턴은 기본적으로 생성할 객체의 구체적인 타입을 감추기 위해 만들어진 패턴이다. Factory Method 패턴에서 리턴되는 객체는 항상 상위 타입이다. 그리고 특정 하위 타입의 구체적인 생성자 호출을 하지 않는다. 따라서 객체의 생성 이후에는 그 객체가 정확히 어떤 타입인지 알 수 없다.


최근에는 반대로 (안전이 보장되는 한) 최대 노출의 원칙을 주장하는 소프트웨어 기술도 있다. 바로 단위 테스트이다. 테스트 가능 설계 중 한가지 방식으로써 충분히 의미 있는 주장이다.


단위 테스트를 위한 최대 노출 원칙(테스트 가능 설계)

- 동작에 영향을 주지 않는다면 모든 정보를 제공하라(getter) : 정보 은닉의 목적은 안전한 구현이다. 테스트 가능 설계를 위해 동작에 아무런 영향을 주지 않는 정보 제공 함수(getter)는 충분히 만들어 놓을 가치가 있다.

- 모든 의존 객체에 대한 의존성 주입 함수를 제공하라 : 다른 객체에 의존성이 있을 경우, 그 객체는 주입(setter) 함수를 꼭 만들어 주자. 그러면 단위 테스트 시에 해당 객체를 다른 객체로 대체함으로써 테스트를 용이하게 할 수 있다.




Posted by 이세영2
,