[OOP] 객체 지향의 정보 은닉

세동네·2021년 8월 10일
0
post-thumbnail

· 정보 은닉

정보 은닉은 객체 지향 프로그래밍에서 매우 중요한 이론 중 하나이지만, 객체 지향을 공부하면서 이에 큰 중요성을 못 느끼는 사람들이 더 많다. 하지만 우리가 객체 지향 프로그래밍에 이용하는 다양한 기법이 정보 은닉을 위한, 정보 은닉을 가능하게 한다는 것을 알게 된다면 생각이 조금은 달라질 것이다.

객체 지향에 대해 공부하면서 정보 은닉을 못 들어봤을 리는 없을 것이다. 이와 함께 언급되는 캡슐화, 추상화와 같은 이론들을 더 많이 공부하고 중요하게 생각하는 경우도 있을 것인데, 사실 이러한 것들이 정보 은닉을 위해 사용되는 개념들이라는 것에 집중해야 한다.

정보 은닉은 소프트웨어에서 사용하는 객체에 대한 구체적인 정보를 노출시키지 않도록 하는 기법으로, 객체 간의 의존성을 낮춰 기능의 교체나 변경이 쉽도록 하는 동시에 쉽게 변경될 수 있는 내용 또는 비밀을 내부로 감추고 쉽게 변경되지 않을 내용만을 외부로 노출시키는 것이다. 이와 동시에 인터페이스로 모듈을 추상화하여 복잡하거나 변경될 수 있는 내용을 외부에서 안정적으로 사용할 수 있게 해주는 것이 중요하다.

이것을 쉽게 소프트웨어 개발에서의 유연성을 확보한다고 표현할 수 있다.

- 객체 지향과의 관계

잠시 객체 지향에 대한 이야기를 해보자. 대표적으로 Java, C++, C# 등 다양한 객체 지향 프로그래밍 언어가 있고, 이들의 가장 대표적인 특징은 클래스를 이용한다는 것이다. 클래스는 여러 필드(변수)와 메서드(함수)를 포함하고, 클래스의 멤버들을 외부에서 어느 정도 수준까지 사용할 수 있게 할 것인지 public, private, protected 등의 접근 지시자로 필요한 경우 내용을 감출 수 있다. 이것이 "캡슐화"이다.

class Test{
    private void Init(){}
    private void Execute(){}
    private void Release(){}
    
    public void Process()
    {
        Init();
        Execute();
        Release();
    }
}

위와 같은 예시에서 캡슐화를 통해 타 클래스는 Test 클래스의 Process() 메서드만을 호출할 수 있을 것이고, Process()를 호출함으로써 필요한 과정을 실행해준다는 것을 알 수 있지만, 그 내부에서 구체적으로 어떠한 작업이 어떠한 순서로 일어나고 있는 것인지 알 수 없다.

그와 동시에 설계자가 의도한 순서에 맞지 않게 함수들이 동작하거나, 필요한 과정을 일부 생략하고 특정 부분만 동작하여 시스템에 예기치 못한 오류가 발생하는 것을 방지할 수 있다.


또한 객체 지향에서는 필요한 경우 비슷하지만 조금씩 다른 형태의 여러 클래스를 만들기 위해 해당 클래스들이 공통적으로 가져야 할 기능을 하나의 부모 클래스에 구현하고 그 기능이 필요한 자식 클래스들에 부모 클래스를 끌어다 사용하는 상속이라는 것을 이용한다.

상속을 C#, JAVA에서는 더 진화시켜 다양한 자식 클래스가 이름은 같지만 실제 내용은 조금씩 다른 작업을 수행할 때 인터페이스라는 것을 활용한다.

예를 들어 교실을 청소할 때 1분단은 쓸기, 2분단은 닦기, 3분단은 분리수거를 분담한다고 하자. 이들은 모두 똑같이 "청소"라는 행위를 하지만, 실제로 취해야 하는 구체적인 행동이 다른 것이다. 이러한 작업을 인터페이스로 쉽게 처리해 줄 수 있다.

상속은 클래스와 클래스 간의 관계를 정의해주는 것이지만, 인터페이스는 클래스와 비슷하지만 메서드만 포함할 수 있는 타입이다. 이러한 인터페이스도 클래스와 같이 상속 가능한 타입이지만, 인터페이스에서 메서드의 선언 자체는 추상 메서드 형태로 선언하고 인터페이스를 상속하는 클래스에서 인터페이스에 선언된 메서드들을 반드시 정의해주어야 한다.

public interface IDropItem
{
    void Gain();
}

public class DropGoldItem : MonoBehaviour, IDropItem
{
    public void Gain()
    {
        PlayerController.playerStat.gold += Random.Range(10, 20);
        
        gameObject.SetActive(false);
    }
}

public class DropHealItem : MonoBehaviour, IDropItem
{
    public void Gain()
    {
        PlayerController.playerStat.hp -= 10;

        gameObject.SetActive(false);
    }

}

이러한 코드에선 IDropItem이라는 인터페이스를 상속하는 클래스를 이용할 땐 해당 클래스가 가지는 메서드를 직접 이용하기보단 인터페이스에 선언된 공개 추상 메서드를 이용한다고 하는 것이 더 정확한 표현이다. 이러한 인터페이스를 이용하면 클래스 간의 연관성이 사라지고 인터페이스에만 의존하여 수정이나 확장 등에 더 용이한 형태가 된다는 장점이 있다.


이러한 것들이 정보 은닉이랑 무슨 관계가 있나 싶겠지만, 캡슐화는 그 자체로 정보 은닉의 의미를 가지고 있고, 상속이 적용된 객체에 상위 타입을 캐스팅해 사용하는 객체의 구체적인 타입을 은닉하거나 인터페이스나 추상 클래스로 메서드 구현을 은닉할 수 있다.

즉, 위에서 언급한 객체 지향의 다양한 개념들은 정보 은닉을 가능하게 하는 수단이자 방법이 되는 것이다. 캡슐화와 정보 은닉을 동일한 개념으로 설명하는 문서들이 많은데, 실제로는 그렇지 않다는 것을 의미한다.

· 항상 정보 은닉을 신경써야 한다?

물론 서론에서 말한 것처럼 정보 은닉이 객체 지향에서 매우 중요한 개념이고 신경 쓰며 소프트웨어를 개발할 필요가 있지만, 매 순간 정보 은닉을 고려하여 설계한다면 지금 당장 필요한 기능들을 구현할 정성과 신경을 낭비하는 것일 수도 있다. 기본적으로 소프트웨어를 설계할 때 "일단 돌아가게 만들고, 나중에 고쳐라"라는 말을 많이 듣는데, 클린 코드가 아니라도 일단 동작하는 스크립트를 작성하고 최적화는 이후에 하나씩 차근차근 잡아가는 것이 개발적인 측면에서 더 효율적일 수 있다는 것이다.

같은 이치로 설계의 시작 단계에서 모듈 내부의 수정 가능성을 고려하고 완벽한 인터페이스를 작성하는 것을 고민하여도 실제 구현을 하면서 어차피 수정할 내용이 생길 가능성이 크고, 하루 빨리 개발에 진척이 보이도록 구현하는 것이 우선이 될 수도 있다는 것이다. 확실하게 변경 가능성이 높은 사항에 대해선 정보 은닉의 원리를 지켜 설계하더라도, 그 가능성이 명확하지 않은 경우 후에 확실히 변경 가능성이 높은 내용이라고 판단되었을 때 정보 은닉을 적용해주어도 크게 문제가 되진 않을 것이다.


· 참고

[1] 객체지향의 올바른 이해 : 5. 정보 은닉(information hiding)
[2] Information Hiding
[3] 객체지향 프로그래밍의 캡슐화, 상속, 다형성

0개의 댓글