본문 바로가기
JavaScript & TypeScript/TypeScript

TS와 JAVA에서 OOP를 구현하는 방식의 차이와 아키텍처 공부에 대한 필요성

by 감중에홍시 2024. 4. 9.

저는 요즘 객체 지향을 공부할 겸, 겸사겸사 객체 지향의 정수인 자바도 학습하고 있습니다. 그리고 최근 타입스크립트와 OOP(Object-Oriented-Programming) 구조로 이루어진 회사의 코드에 추가 기능을 작업하던 중, 들었던 생각이 있어 이를 공유하고자 합니다.

일단 회사 코드 클래스의 설계는 대략 아래와 같이 하나의 부모 클래스를 여러 자식 클래스가 상속을 받는 구조입니다.

Basis 클래스는 해당 클래스에 대한 정보들이 들어있고, DrawBasis 클래스는 Basis 클래스를 참조하며, Basis 클래스 안에 있는 속성을 어떻게 그려야 할 지를 정해주는 클래스입니다.

그리고 DrawBasis 클래스를 여러 자식 클래스들이 상속 받아서 다양한 자식 클래스들을 각각의 속성에 맞게 그려주는 역할을 합니다.

이 중에서 저는 ChildC와 ChildD에만 필요한 기능을 개발하고 있었습니다. 그렇다면 ChildC와 ChildD 클래스에 해당 기능을 추가하면 되지만, 처음에 제가 생각을 잘못하여 부모 클래스인 DrawBasis(ex: 메서드 B)에 해당 로직을 추가하고, 해당 로직에서 "상속받은 클래스가 ChildC와 ChildD인지 확인하는" 분기문을 추가로 작성했었습니다.(물론 최종적으로는 부모 클래스가 아닌 ChildC와 ChildD 클래스에 해당 기능을 구현하였습니다..)

OOP에서는 자식 클래스의 객체가 생성될 때, 부모 클래스의 속성과 메서드도 함께 인스턴스에 포함되어 메모리에 할당 되기 때문에 자식 인스턴스에서는 부모 클래스의 속성 및 메서드에 접근할 수 있습니다.

하지만 부모 클래스의 객체가 생성될 때는, 어떤 자식 인스턴스가 생성될 지 모르기 때문에 부모 객체에서 자식 클래스의 속성 및 메서드에 접근하는 것이 불가능합니다.

그렇다면 부모 클래스에서 자식 클래스로의 참조가 아예 안되는 것일까요? 그건 아닙니다. 개발자 입장에서 어떤 자식 클래스가 본인(부모 클래스)을 참조하는 지 확신할 수 있다면, 자식 클래스의 속성 및 메서드에 접근할 수 있습니다. 하지만 프로그래밍 언어별로 이를 구현하는 방식에 차이가 있습니다.

 

 자바에서는 다운캐스팅을 사용하여 상위 타입의 참조가 실제로 가리키는 객체를 하위 타입으로 변환할 수 있습니다.

이는 런타임에 이루어지며, 런타임에 객체의 실제 타입이 다운캐스트하려는 타입과 호환되지 않으면 ClassCastException이 발생합니다. 아래는 자바의 다운 캐스팅에 대한 예시 코드입니다.

Object obj = new String("Hello");
String str = (String) obj; // 다운캐스팅

객체 obj는 Object 클래스 타입이지만 Object 클래스의 하위 클래스인 String 클래스를 통해 생성됐습니다. 이 코드를 작성한 개발자는 obj가 String 클래스를 통해서 생성된 객체임을 알기에 다운 캐스팅을 통해 obj 객체를 강제로 String 클래스로 형 변환을 할 수 있습니다.

 

 타입스크립트에서는 'as' 키워드와 타입 가드를 통해 다운 캐스팅과 비슷한 기능을 구현할 수 있습니다.

1. as 키워드를 이용하는 방법

let someValue: any = "This is a string";
let strLength: number = (someValue as string).length; // 타입 단언

someValue는 any로 선언되어 string형의 메서드인 length를 사용하면 TypeScript 타입 에러가 나지만, 위와 같이 as를 이용하여 타입을 단언(우회)해준다면 타입 에러를 방지할 수 있습니다.

2. 타입 가드를 이용하는 방법

class Animal {}
class Bird extends Animal {
    fly() {
        console.log("Flying");
    }
}

function isBird(animal: Animal): animal is Bird {
    return animal instanceof Bird;
}

let myAnimal = new Bird();

if (isBird(myAnimal)) {
    myAnimal.fly(); // 여기서 myAnimal은 Bird로 간주됩니다.
}

타입 가드는 특정 스코프 내에서 변수의 타입을 보장하는 방식으로 위 예제에서 isBird 함수는 사용자 정의 타입 가드입니다. 이를 통해 Animal 타입의 myAnimal이 실제로 Bird 클래스의 인스턴스인 경우에만 fly 메서드를 안전하게 호출할 수 있습니다.

 

자바의 다운캐스팅과 타입스크립트의 as, 타입 가드가 완벽하게 동일하지는 않지만, 세 방법 다 개발자가 컴파일러보다 해당 타입에 대해 더 많이 알고 있다고 "단언"하는 것이며, 이를 통해 더 구체적인 타입으로 변환을 수행한다는 공통점이 있습니다.

 

 

느낀점

저는 이러한 과정을 겪으면서, "프로그래밍 언어(자바, 타입스크립트 등)는 아키텍쳐(OOP)를 구현하는 도구일 뿐이며, 실제로는 소프트웨어의 구조와 설계가 더 본질적이고 중요한 요소구나."라는 생각이 들었습니다. 추가로

'객체 지향의 상속 구조에서 타입 체크 후 안전하게 캐스팅하기 위한 행위'라는 아키텍쳐적인 내용을 구현하기 위한 방법은 프로그래밍 언어 별로 조금씩 다르기는 하나 이미 그 방법이 다 구현이 되어있구나. 사실 더 중요한 것은 다운 캐스팅 등의 방법을 사용하지 않아도 구현이 될 수 있도록 좋은 설계를 하는 것이 더 중요한 것이 아닐까?

라는 생각이 들며 좋은 설계 및 아키텍쳐 학습에 대한 필요성을 느끼게 되었습니다.

여담으로, 객체 지향적 관점에서 볼 때, 위와 같은 요구사항(추상화)을 여러 프로그래밍 언어로 구현할 수 있다는 점에서 이는 객체지향의 "다형성"과 결이 비슷하다는 생각이 들었습니다. 새삼 객체 지향의 위대함을 느끼며, 온세상이 객체 지향임을 느끼게 되는 경험이었습니다.