본문 바로가기

IT/개발상식

추상화

반갑습니다. 일단 새해 복 많이 받으세요 여러분.

서론이 길었네요. 길었던 김에 조금만 더 길게 해볼게요. 또 오랜만에 글을 올리게 되는군요. 놀랍게도 최근에도 꽤나 바빴습니다. 진짜에요. 어쩐일인지 최근들어 바쁜 일들이 정말 많아요.

네. 그럼 바로 이번 글의 주제 추상화에 대해서 이야기 하도록 하겠습니다. 소프트웨어를 공부하다 보면 이 단어를 정말 많이 듣게 되실 것이리라 예상을 해봅니다. 도대체 이게 뭔지, 사실 이론이나 기초 프로그래밍 언어를 배울 때는 감이 잘 안오실 수도 있겠는데요. 현업에서는 추상화라는 개념이 꽤나 중요하게 작용하곤 한답니다.

아니라구요? 저는 중요하다고 생각해요.

그럼 지금부터 어떻게 추상화 라는 개념이 중요한 역할을 하게 되는지 가벼운 이론과 함께 곁들여서 알아 보도록 할게요.

 

추상화

먼저 추상화 라는 것은요. 앞서 설명드린 인터페이스가 비슷한 역할을 한다고 보면 되겠는데요. 이와 비슷한 개념으로 abstract라는 키워드가 있어요. 단어부터 벌써 추상화 하고싶죠?.
물론, 인터페이스와는 살짝 차이점이 존재합니다. 또한 언어마다 약간의 차이는 존재할 수 있겠습니다만, 일반적으로 인터페이스 라는 녀석은 구현을 강제화 하는 역할을 해요. 그러니까 개발 도중에 인터페이스를 구현하고자 하는 곳에서 특정 함수를 구현하지 않으면 개발자가 바로 알아차릴 수 있기에, 미리 실수를 방지할 수도 있는 그런 역할을 하기도 하는게 인터페이스 입니다. 그리고 인터페이스를 작성하면서 미리 설계해둔 것을 머릿속으로도 정리하면서 어떻게 구현하면 좋을지 가이드 역할도 해주기도 하죠.

하지만 추상화라는 녀석은요 약간 독특합니다. 앞서 말씀드린 설계한 과정대로 쭉쭉 이어서 대충 이런이런 함수가 있으면 되겠지? 라는 역할을 하는 것이 abstract 라는 키워드의 역할이에요. 뭣하러 번거롭게 interface, abstract를 쓰나 싶곘지만 규모가 큰 시스템을 개발할 때는 꽤 큰 역할을 하곤 한답니다.

미리 설계한 것을 머릿속으로 정리하면서 구체적인 구현부 없이 재사용 가능한 함수들을 미리 적어두거든요. 그리고 이런 설계와 비슷한 역할을 하는 녀석들을 interface, abstract를 사용한 것들을 가져다가 구현해 버리곤 하거든요.

그리고 이러한 일련의 과정들을 추상화단계 라고 합니다. 실제로는 이런 추상화 단계에 꽤나 많은 시간을 투자해요. 이런 추상화 단게가 구체적이면 구체적일수록 개발 공수가 훨씬 줄어들거든요.

아니라구요? 저는 그렇다구요. 아님 말구요.

그럼 예시를 들어 살펴볼게요.

 

예시

사실 이런 설명을 드릴 때 Java언어면 더 좋겠는데, 제가 JavaScript를 더 잘하니까 TypeScript로 해볼게요? JS잘하는거랑 TS예시가 무슨 연관성이냐구요? 그냥 그렇게 할게요. 그렇게 하고 싶으니까.

아래의 예시는 그냥 단순히 Query를 실행시키려는 추상 클래스를 정의한 모습입니다. 실제로 뭔가 동작을 하는 클래스는 아니에요. 그냥 빈 껍데기일 뿐입니다. 그리고 아래와 같이 추상화 하는 클래스를 추상 클래스 라고 부릅니다.

abstract class SuperQueryExecutor {
    readonly 'instance' = Symbol.for('SuperQueryExecutor');

    manager: Manager;

    isTransaction: boolean = false;

    isReleased: boolean = false;

    databaseConnector: any;

    replicationMode: Replication;

    abstract query(query: string, params?: any[], useStructuredResult?: boolean): Promise<any>;

    abstract select(v: any[]): Promise<any>;
}

정말 어떤 특정 역할을 수행하는건 아니구요. 그냥 말 그대로 추상화만 한거에요. 이걸 사용할 때는요. extends라는 키워드를 사용해서 메소드를 오버라이딩 해줍니다.

오버라이딩이란, 말 그대로 덮어쓰는거에요. 저기에 abstract라는 키워드가 붙은 함수를 다시 재구현 하는걸 의미하죠. 그럼 이를 상속받은 class는 저 함수를 해당 클래스의 입맛에 따라 구현하면 되는거에요.

그럼 처음 설계한 모습 그대로 다양한 모습의 class를 만들어 낼 수 있겠죠? 꽤나 괜찮은 전략입니다. 그리고 이러한 개념은 객체지향 프로그래밍에서 아주아주 중요한 개념이라고들 하더군요.

class QueryExecutor extends SuperQueryExecutor {
  query(query: string, params?: any[], useStructuredResult?: boolean): Promise<any> {
    console.log("여기서 쿼리 실행할 내부 로직을 작성할거임.");

    return 대충_프로미스_반환값;
  }

  select(v: any[]): Promise<any> {
    console.log("여기서 select 쿼리 실행할 내부 로직을 작성할거임.");

    return 대충_프로미스_반환값;
  }
}

꽤 괜찮죠?

그럼 만약에 추상화를 안하면 어떻게 될까요?

사실 뭐 어떻게 되진 않아요. 그냥,,, 그냥 매번 새로운 기능 만들 때마다 class를 맨땅에 작성해야죠 뭐. 매번 새로운 느낌이 들겠죠..?
아마 처음 설계한 그 느낌이 안날꺼에요. 물론 문서가 존재한다면 이런 느낌이 안들겠지만, 처음 설계한 것과 별개로 뭔가 새로운게 계속 추가가 될겁니다. 점점 여러 개발자의 손을 타면서 처음의 그 맛은 안나겠죠.

 

마무리

오늘도 급하게 마무리 해보도록 할게요. 제가 판단하기에 꽤나 괜찮거나 유용하다는 정보들을 전달해 드리고 싶은데, 글을 쓸때마다 항상 아쉬움이 남네요.

그럼 다음 글에서 또 만나요 ~~! 

'IT > 개발상식' 카테고리의 다른 글

의존성 주입  (0) 2024.02.25
Class  (0) 2024.02.06
Clean Code  (0) 2023.12.02
Architecture  (0) 2023.10.25
Interface  (0) 2023.10.08