'정보 은닉'에 해당되는 글 2건

  1. 2016.09.24 객체지향의 올바른 이해 : 5. 정보 은닉(information hiding) 8
  2. 2016.09.23 객체지향 용어 정리

우리가 어떻게 해서 유연성을 확보할 수 있었는가?

그것은 추상화(Abstraction)에서부터 시작되었다. 추상화를 통해 우리는 여러 요구사항들 중에서 공통점을 찾고, 이 공통점에서 목표한 것과 관련 없는 것들을 제거하였다. 이를 기반으로 공통점을 캡슐화할 수 있었고, 이 캡슐화된 대상에 타입을 부여할 수 있었다.

이 추상화의 과정을 비유적으로 이야기 해보면 이렇다. 개별적인 것들은 다들 개성이 강하고 다른 듯 하지만 멀리서 보면 대개 비슷하다. 우리 두뇌는 이런 일들을 잘 해낸다. 소위 "패턴"은 이와 맥락을 같이 하는 단어이다. 디자인 패턴이든 건축 패턴이든 패턴이라는 것은 개별적인 시도가 가지는 공통된 맥락을 의미한다. 추상화란 다들 서로 다른 듯 보이는 것들이 내제하고 있는 일반적인 모습, 바로 "패턴"을 찾아내는 과정이다. 사실 세상에는 "패턴"으로 정의할 수 있는 것들이 무수하게 많다. 어느 분야의 대가라고 불리는 사람들은 그 분야에서 벌어지는 다양한 시도들이 어떤 "패턴"을 가지고 있는지를 이해하고 있다. 그들은 그러한 패턴들을 알고 있기 때문에 새로운 시도를 할 때에도 마치 이전에 경험했던 일을 하는 것처럼 쉽게 해낼 수 있다. 

당대에 유명한 사랑꾼으로 통했던 카이사르는 자신이 어떤 후보를 추천하기 위해 추천서를 썼던 것처럼, 이미 작성해 놓은 똑같은 내용의 연애 편지에 이름만 다르게 써서 여자들에게 보냈을지도 모를 일이다. 안타깝게도 카이사르의 연애 편지들은 모두 그 후계자인 아우구스투스가 없애버렸기 때문에 이제는 확인할 길이 없지만 말이다. 어쨌든 똑같은 연애 편지에는 대상자 이름이 적혀 있지는 않았을 것이다. 그래야 연애 편지가 유연성을 가질테니까 말이다. 이것을 좀 더 일반적으로 표현해 보자면 공통된 정보를 모아 놓되 구체적인 정보는 숨겼다는 말이다. 이것을 객체지향에서는 정보 은닉(Information Hiding)이라고 부른다.

진짜 객체지향은 정보 은닉에서부터 시작된다.

객체지향 언어를 통해서 얻고자 하는 것이 유연성(기능의 확장, 교체, 변경)이라면 정보 은닉은 그것을 가능하게 하는 전략이다. 객체, 상속, 캡슐화 등은 정보 은닉의 수단에 불과하다. 그리고 좋은 정보 은닉은 잘 된 추상화를 통해 얻어진다.

많은 개발자들이 객체지향에 들어서면서 캡슐화를 정보 은닉이라고 배운다. 몇몇 훌륭한 블로그들을 제외하고는 대부분의 블로그들이 정보 은닉 = 캡슐화로 설명하고 있다. 매우 안타까운 일이다. 정보 은닉을 캡슐화로만 알고 있으면 아직 객체지향 입구에도 못들어 온 것이다.

정보 은닉과 관련하여 인터넷을 검색해 본 결과, 정확하게 정보 은닉을 설명한 것은 아래 글 밖에 없었다.

http://egloos.zum.com/aeternum/v/1232020


정보 은닉의 정의

- 모든 객체지향 언어적 요소를 활용하여 객체에 대한 구체적인 정보를 노출시키지 않도록 하는 기법.


소프트웨어의 유연성을 확보하는 단 한가지 방법만 있다면 그것은 무엇일까? 그것은 "객체(또는 클래스) 간에 서로를 모르게 하는 것"이다. 어떤 객체가 다른 객체를 생성하든, 다른 객체의 메소드를 호출하든, 다른 객체가 가진 정보를 조회하든, 다른 객체의 타입을 참조하든, 어떤 행위라도 상관이 없이, 안하는 것이 가장 좋다. 두 객체(또는 클래스)가 서로를 모른다는 것은 서로의 코드에 상대 객체나 클래스에 대한 코드가 단 한 줄도 없다는 의미이다. 만약 두 객체간에 전혀 관계가 없다면 두 객체 중 어느 하나가 수정되거나 사라지더라도 다른 객체는 전혀 영향을 받지 않는다. 따라서 두 객체간에는 유연성이 확보된다. 이것은 매우 자명한 이치지만 이런 원칙을 전체 시스템에 확장시킬 수는 없다. 객체지향 언어에서 어떤 목적을 달성하기 위해서는 필연적으로 다른 객체와의 협력이 있어야 하기 때문이다. 이 필연성은 "어떤 객체도 섬이 아니다"라는 워드 커닝헴과 켄트 벡의 말로 대변된다. 객체지향 시스템에 참여하는 모든 객체들은 어떤 형태로든 관계들로 엮여 있다. 객체를 노드로 하고 관계를 엣지로 나타내면 단 한 덩어리의 연결 그래프가 되어야만 한다. 만약 어떤 객체가 다른 어떤 객체와도 관계를 갖지 않는다면 그 객체는 별도의 시스템이다.

일단 발생한 관계는 유연성을 발휘하지 못하게 만든다. A가 B에 책임을 위임한 경우라면 B의 수정은 A의 위임 목적을 해칠 수 있다. 원래 B로 부터 얻고자 했던 결과를 더 이상 얻을 수 없을지 모른다. 같은 관계에서 A의 수정은 B의 책임을 더욱 강화 시킬 수도 있고, 반대로 전혀 필요 없는 객체로 만들어 버릴 수도 있다. 일단 관계가 발생하면 언제라도 관계가 있는 객체에 수정을 발생 시킬 여지가 있다. 그리고 어떤 객체든 적어도 하나 이상의 다른 객체와 관계를 맺어야만 한다. 이것이 어쩔 수 없는 현실이라면, 즉 어떻게든 관계가 있을 수 밖에 없다면, 똑같은 관계라도 더 좋은 관계로 변경해야 한다. 그렇다면 어떤 관계가 좋은 관계일까?


1. 자주 변경될 가능성이 있는 것에는 의존하지 않는다.

2. 외부로 노출된 메소드를 최소한으로 줄인다. 노출된 메소드가 최소인 객체는 노출된 메소드가 많은 객체에 비해 메소드가 적게 호출되고, 이는 다른 객체와의 관계가 발생할 가능성을 줄인다.

3. 객체의 책임을 최소한으로 줄인다. 책임이 작은 객체는 다른 객체와의 관계가 작아진다. 책임이 작아진 객체는 또한 수정될 가능성이 줄어든다. 따라서 다른 객체에 수정의 영향을 줄 가능성도 줄어든다.


정보 은닉의 종류

- 객체의 구체적인 타입 은닉(= 상위 타입 캐스팅)

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

- 구현 은닉(= 인터페이스 및 추상 클래스 기반의 구현)


정보 은닉의 목적

- 코드가 구체적인 것들(타입, 메소드, 구현)에 의존하는 것을 막아줌으로써 객체 간의 구체적인 결합도를 약화시켜 기능의 교체나 변경이 쉽도록 함.

- 동일한 타입의 다른 구현 객체들을 교체함으로써 동적 기능 변경이 가능함.

- 연동할 구체적인 구현이 없는 상태에서도 (인터페이스 만으로) 정확한 연동 코드의 생성이 가능함.


자 그러면 본격적으로 구체적인 구현을 통해서 정보 은닉 방법과 이점을 살펴보도록 하자.


"생성부터 은닉질이냐!?"

그렇다. 객체의 생성시부터 정보 은닉을 해야 한다. 아래 코드를 보자.

class Rectangle{

    public void rectangle(){ System.out.println("rectangle"); }

}


public static void main(String[] args) {

    Rectangle rectangle = new Rectangle(); // --- 1

    rectangle.rectangle(); // Rectangle 클래스에 의존적인 코드

}

위의 코드에서 1과 같이 객체를 생성했다고 하자. 객체는 생성 이후에 rectangle이라는 Rectangle 클래스 변수로 참조된다. 따라서 Rectangle에 선언된 모든 메소드를 사용할 수 있게 된다. 따라서 rectangle.rectangle()의 호출이 가능해진다. 이것은 Rectangle라는 객체에 전적으로 의존하게 되는 코드이다.

만약 우리가 좀 더 생각해서 Rectangle과 유사한 기능, 즉 Circle을 구현하게 될지도 모르고, 이에 따라서 Rectangle을 대신해서 Circle을 사용하게 될지도 모른다고 하자. 그래서 Rectangle과 Circle을 모두 지칭할 수 있는 상위 클래스인 Shape을 만들고, 각각의 모양을 그릴 수 있는 메소드(draw() 메소드)를 구현하도록 정의했다고 하자. 

그러면 아래의 2와 같은 코드를 만들 수 있다.

abstract class Shape{

    abstract public void draw();

}


class Rectangle extends Shape{

    public void draw(){ rectangle();}   

    public void rectangle(){ System.out.println("rectangle"); }

}


public static void main(String[] args) {


    Shape shape = new Rectangle();         // --- 2

    shape.draw();  // Shape 클래스에 의존적인 코드

}

코드 2는 동일하게 Rectangle을 생성했지만 곧바로 그 상위 타입인 Shape 클래스 참조 변수인 shape으로 객체를 참조한다. 따라서 이후에 shape 참조 변수를 통해 사용할 수 있는 메소드는 Shape 클래스의 메소드로만 제한된다. 그래서 생성 이후에는 Rectangle 클래스와 관련된 어떤 메소드도 호출되지 않는다. 이것이 구체적인 타입 은닉에 해당된다.

이를 통해서 얻을 수 있는 이점은 다음과 같다.

- Rectangle의 생성 코드 이후에는 어떤 코드도 Rectangle 클래스에 의존하지 않는다.

- 따라서 Rectangle 대신에 Circle을 사용하고 싶어졌을 때에는 Rectangle 대신 Circle을 생성하도록 변경하기만 하면 된다. 그러면 그 이후의 코드들은 전혀 수정될 필요가 없다.

- 만약 Rectangle을 사용하다가 Circle을 사용해야 할 경우도 발생할 수 있다. 바로 동적으로 기능을 교체해야 할 경우이다. 이 때에도 선언되어 있는 shape 참조 변수에 새로 생성한 Circle 객체만 참조로 할당해 주기면 하면 된다. 이런 방법으로 동적인 기능 전환도 쉽게 할 수 있다.


위의 코드에도 문제가 있다고 해서 다양한 디자인 패턴들이 생겨났다. Abstract Factory 패턴, Factory Method 패턴과 같은 경우가 바로 그것이다. 이들 생성 패턴들은 생성과 동시에 구체적인 타입 은닉을 수행하도록 되어 있다. 

- Abstract Factory 패턴

- Factory Method 패턴


아래는 각 생성 패턴들이 공통으로 추구하는 방향만을 간략하게 구현해 본 것이다.

class ShapeFactory{

    public Shape createRectangle(){ return new Rectangle(); }

    public Shape createCircle(){ return new Circle(); }

}


public static void main(String[] args) {

    ShapeFactory factory = new ShapeFactory();


    Shape shape = factory.createCircle();

    shape.draw();

}

위에서 구체적인 객체, 즉 Rectangle과 Circle 객체를 생성하는 책임을 담당하는 클래스가 ShapeFactory 클래스이다. 그리고 이 클래스는 객체의 생성과 함께 객체를 리턴하는데, 리턴하는 타입은 동일하게 Shape 타입으로 객체를 리턴한다.

이것이 어떤 효과를 가져 오는가? main() 메소드에서 ShapeFactory를 사용하게 되어 있는데, main() 메소드가 ShapeFactory의 createCircle() 메소드를 호출해서 객체를 받아 올 때 타입은 이미 Shape으로 변경되어 있다. 따라서 main() 메소드에서는 Rectangle이라는 클래스나 Circle이라는 클래스는 전혀 모르는 상태다. main()이 알고 있는 것은 오직 Shape 객체 뿐이다. 따라서 이 ShapeFactory를 이용해서 객체를 생성하면 생성된 이후의 모든 코드와 Rectangle 또는 Circle과는 전혀 무관한 코드가 된다. 오직 Shape만 이용하게 되기 때문이다.

그러면 어떤 장점이 있을까? 당연히 객체의 교체나 변경이 쉬워지게 된다. 또 다른 Shape 타입을 추가하는 것도 손쉬워진다. ShapeFactory를 거친 이후에는 모두 다 같은 Shape으로 취급될 것이기 때문이다.


캡슐화를 통한 정보 은닉

이 부분에 대해서는 다른 여러 블로그나 책들에서도 언급을 하고 있다. 하지만 상대적으로 덜 강조되고 있는 부분은 짚고 넘어가야 겠다.

일단 변수(필드)에 private 키워드를 이용해서 외부 노출을 줄이는 부분에 대해서는 어떤 책이든 강조를 하고 있다. 이를 통해서 필드를 외부에서 임의로 접근해서 발생할 수 있는 문제들을 없앨 수 있다.

메소드에 대해서는 상대적으로 강조가 적은 편인데 아래 예를 보면서 이야기를 해보자.

class Process{

    public void init(){}

    public void process(){}

    public void release(){}

}

위의 클래스는 public 메소드가 모두 3개이다. 즉, 외부에서 이 클래스의 객체를 사용하는 코드들에서는 모두 3개의 메소드에 의존하게 된다. 이는 혹시라도 Process 객체를 수정하거나, 아예 제거를 하는 등의 수정이 발생했을 때 3개의 메소드보다 적은 수의 메소드에 의존하는 코드들에 비해 수정이 더 많이 되어야 함을 의미한다.

또한 불필요하게 많은 수의 메소드를 노출시키면 여러가지 나쁜 면이 있다. 첫째로 메소드의 호출 순서를 제대로 알지 못해서 발생하는 문제점이 있을 수 있다. 메소드들이 서로 시간적 연관 관계가 있어서 순서대로 호출되어야 하는데 여러 메소드로 나누어져 있을 경우 이를 알지 못해 오류가 발생할 수 있다. 둘째로 구현의 구체적인 사항을 외부에 노출시킨다는 점이다. 이 Process의 세부 단계에 대해서 외부 객체들이 알게 됨으로써 구현을 유추할 수 있거나 유추해야만 하는 문제가 발생한다. 세번째로 어떤 메소드가 중요한지를 알 수 없게 된다. 적절한 수준에서 정보를 숨겨줌으로써 객체를 이해하는 입장에 도움을 주어야 하는데 모든 메소드가 노출되어 있으면 무엇이 중요한 메소드인지를 알 수 없다.

그럼 아래 코드를 보자.

class Process{

    private void init(){}

    private void process(){}

    private void release(){}

    public void work(){

        init();

        process();

        release();

    }

} 

위와 동일한 기능을 하지만 좀 더 나은 모습이다. 일단 이전에 보여졌던 메소드들이 모두 비공개(private) 메소드로 변경되었다. 따라서 외부 객체에서는 이들 메소드를 호출할 수가 없다. 대신에 외부에서 호출이 가능하도록 work() 메소드를 공개하고 있다. 따라서 외부 메소드들은 work() 메소드만을 이용할 수 있다.

이를 통해 얻는 장점은 다음과 같다. 우선 적절한 수준에서 메소드들이 공개와 비공개로 나누어져 있기 때문에 어떤 메소드를 우선 살펴야 할지를 알 수 있다. 또한 개별 메소드들의 호출 순서를 work() 메소드에서 정해주고 있기 때문에 Process 객체 사용에 대한 정보를 더 적게 알아도 된다. 마지막으로 work()라는 메소드만 노출 되었을 때에는 Process 객체가 하는 일의 세부 내용을 덜 노출시킨다. 즉, 외부에서는 Process 객체가 init - process - release 단계를 거친다는 점을 알 수 없다.


오직 인터페이스에만 의존하도록 한다

만일 객체를 잘 설계하여 변수를 private으로 선언하고, 꼭 필요한 메소드만 외부로 공개하였다고 하자. 그러면 외부 객체와 잘 설계된 객체간에 의존성은 오직 공개 메소드에 의해서만 발생하게 된다. 그래서 공개 메소드를 비공개 또는 보호 메소드들과는 구분하기 위해서 인터페이스라는 별도의 용어를 부여하게 되었다. 그만큼 공개 메소드가 개념적으로 중요하기 때문이다.

JAVA 언어에서는 이 개념을 더욱 강화하여 클래스와 유사하게 상속 가능한 타입이면서 구체적인 구현을 배제한 interface 라는 개념을 만들어 냈다. 예제에서는 interface라는 추상화 요소를 사용하게 될텐데 혹시 JAVA가 아닌 다른 객체지향 언어를 사용하고 있다면 interface를 "공개 추상 메소드만을 가지고 있는 추상 클래스" 정도로만 이해하면 된다.

앞서 이야기 했듯이 객체와 외부와의 소통은 오직 공개 메소드만으로 이루어진다. 그렇다면 어떤 객체가 공개 메소드의 모양, 즉 프로토타입(= 공개 추상 메소드)만 가지고 있는 상위 클래스를 상속 받았고, 오직 그 상위 클래스가 제공하는 공개 메소드만을 외부로 공개하였다면, 이 클래스는 상위 클래스로 지칭될 수 있다. 이 때 상위 클래스는 오직 공개 메소드를 선언하는 선언부 역할만을 하고, 하위 클래스는 이를 구체적으로 구현하는 역할만 가지게 된다.

이를 코드로 나타내 보면 아래와 같다.

interface Interface{

    public void method();

}

class ConcreteClass implements Interface{

    public void method(){ System.out.println("method"); }

}

Interface는 오직 공개 추상 메소드만을 정의하고 있다. ConcreteClass는 이 Interface가 제공하는 공개 추상 메소드를 구체적으로 구현하고 있다.

그러면 객체를 사용하는 입장에서는 어떠한가? 객체를 사용하는 입장에서는 ConcreteClass를 Interface 타입으로 지칭할 수 있게 된다. 그리고 모든 기능이 Interface가 정의한 공개 메소드를 통해 실행 가능하므로 ConcreteClass를 이용하는데 전혀 문제가 없다.

이처럼 인터페이스와 구현을 분리하면 다음과 같은 이점이 있다.

- Interface만으로 객체를 다룰 수 있으므로 구체적인 구현에 대해서 전혀 모르더라도 동작이 가능하다. 즉 구현에 대해 관심을 둘 필요가 없다.

- 좀 더 나가보면 Interface만 알고 있어도 Interface에 의존하는 코드를 작성할 수가 있다. 즉, Interface를 상속 받는 임시 객체를 만들어 두고 이를 이용하는 코드들을 만들었다가 추후에 Interface를 구현한 구체 클래스가 완성되었을 때 연결만 시켜주면 된다.

- 모든 클래스들이 오직 Interface에만 의존하게 되므로 구체적인 의존 관계가 없어지면서 각 객체가 서로 분리되어 있기 때문에 구체적인 객체를 다른 객체로 교체한다거나 Interface를 구현한 새로운 객체를 만들어서 제공함으로써 기능을 확장하는 것이 가능해진다.

이러한 인터페이스의 장점을 이용한 디자인 패턴은 모두 열거하기 힘들 정도로 많다. 가장 대표적인 것만 꼽으라면 아래와 같다.

- State 패턴 : 상태를 객체화하고, 인터페이스를 통해 상태화 된 객체를 지칭하게 함으로써 상태가 추가되기 용이하도록 한다.

- Bridge 패턴 : 연관관계가 있는 두 부류의 객체들을 두 개의 인터페이스 간의 연관관계로 바꾸고, 구체적인 객체들을 인터페이스 상속을 통해 구현 함으로써 각 부류의 객체들에 추가/삭제가 발생하더라도 다른쪽 부류에는 영향을 미치지 않도록 한다.

- Stragegy 패턴 : 기능을 담은 객체를 인자로 넘겨 줌으로써 이를 받는 객체의 기능이 변경될 수 있도록 한다. 이 역시 인터페이스를 중심으로 기능을 담은 객체를 지칭함으로써 기능의 확장이나 변경이 용이하도록 한다.

- Observer 패턴 : 관찰자 객체들을 인터페이스로 추상화하고, 관찰 대상 객체에 이벤트가 발생했을 때 인터페이스만을 활용하여 이벤트를 전달함으로써 관찰자와 관찰 대상 간의 구체적인 결합을 제거한다. 이를 통해서 관찰자에 해당하는 구체적인 객체들의 종류가 늘어나더라도 같은 관찰 대상 객체의 구현에는 영향이 없다.


정보 은닉은 객체지향 언어의 목표이다

기능을 간편하게 수정할 수 있으며, 기능을 추가하기 용이하고, 언제든 기능을 교체하는 것이 가능한 소프트웨어를 구현하는 것은 모든 소프트웨어 개발자들의 꿈이다. 그런 꿈이 담겨 있는 것이 객체지향 언어이고, 이 객체지향 언어가 기능의 수정 / 추가 / 교체를 가능하게 하기 위해 세운 기초 전략이 정보 은닉이다.

객체지향 언어를 통해 만들어진 좋은 설계, 즉 디자인 패턴과 같이 좋은 설계를 대표할만한 것들은 모두 정보 은닉 기법을 적어도 일부를 활용하고 있거나, 전적으로 정보 은닉을 통해 이득을 얻기 위해서 만들어진 것들이다. 그리고 객체지향의 설계 원칙(SOLID), 각종 객체지향 설계에 관련된 격언들은 거의 모조리 정보 은닉에 관련된 것들이다. 또한 객체지향이 만들어낸 여러 개념들과 언어적 특성들 중 대부분은 정보 은닉을 위해 만들어진 것들이다.

(예를 들어 상속을 보통 메소드와 변수를 재사용하는 것이라고 이야기하는데 이는 틀린 말이다. 상속을 통해 받을 수 있는 것 중에서 가장 중요한 것은 타입이다. 이 타입을 내려 받을 수 있기 때문에 하위 객체가 상위 클래스로 지칭 될 수 있다. 이것이 정보 은닉을 가져오고 객체지향의 모든 이점들을 가져 온다.)

Posted by 이세영2
,

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

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


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

// 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
,