Game Development, 게임개발

객체지향적으로 생각하기, Object-Oriented Thinking

게임이 더 좋아 2021. 4. 7. 18:57
반응형
728x170

 

우리가 편하다고 생각하는 프로그래밍언어의 특징이

바로 객체지향프로그래밍언어다.

 

더 잘 이해하기 위해선 어떤 요소가 특징이고 어떻게 생각해야하는지 배워보자.

개인적인 의견이 더 많이 들어갈 듯 싶다.

 

**2023.08.06 추가


 

우선 왜 우리는 객체지향언어가 편할까???

 

우리가 생각하는바를 실제 프로그래밍언어로 구현했기때문이다.

 

예를 들면

자동차를 길게 만들면 버스고, 빠르게 만들면 스포츠카, 크게 만들면 트럭인데..?

자동차에 대한 기본원리는 같지 않을까?

버스, 스포츠카, 트럭은 모두 자동차의 특징을 상속받는다.

반대로 버스, 스포츠카, 트럭을 추상화하면 자동차가 나온다.

-> 이러한 모든 생각을 프로그래밍 언어에도 적용했다.

 

우리가 개발하기 편해진 OOP의 특성을 알아보자.

크게 4가지로 나눌 수 있다.

Abstraction, 추상화

Encapsulation, 캡슐화

Inheritance, 상속

Polymorphism, 다형성

 

또한 객체 지향 언어라면 지켜야하는 원칙 5가지가 있다.

** SOLID라고도 한다.

 

SRP(Single Responsibility Principle) : 단일 책임 원칙
클래스는 단 하나의 책임을 가져야 하며 클래스를 변경하는 이유는 단 하나의 이유이어야 한다.

 

OCP(Open-Closed Principle) : 개방-폐쇄 원칙
확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

 

LSP(Liskov's Substitution Principle) : 리스코프 치환 원칙
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

 

ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.

 

DIP(Dependency Inversion Principle) : 의존 역전 원칙
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.

 


 

Abstraction, 추상화

추상화라고 하면 피카소..를 떠올리는 때의 내가 있다. 22살의 나였을까..? 그 때도 추상화 제대로 몰랐네 ㅎ

우는 여인을 떠올렸다.

하지만 여기서는 이 그림이 더 맞을듯 하다.

 

그림에서 소가 아니게 될 때까지, 소라고 생각할 수 없을 정도까지 요소들을 빼는 것이다.

그렇게 되면 남는건 소처럼 생긴 선이 남는다.

그래도 우리는 여전히 소란 것을 알 수 있다.

 

프로그래밍에서도 마찬가지다.

내가 인터페이스를 만들든, 최상위 클래스를 위해 만들든.. 추상화를 해서 만들어야 한다.

즉, 가진 것을 최소로 줄여야한다는 말이다.

이게 필요하지 않을까? 저게 필요하지 않을까?? 클래스에 집어넣다 보면..

그 클래스 자체는 쓸모가 있을지 모르나 확장하기가 어려워진다.

(상위 클래스에 너무 많이 넣으면 확장성이 오히려 줄어든다.)

 

예를 들어

영장류에 똑똑한 머리도 넣고, 직립 보행도 넣고, 별거 다 넣어서 인간이 되었다.

그렇게 만들어진 인간은 원숭이가 될 수 없다.

영장류가 진화의 분기점이란 말이다.

이미 갈라져 버리면 다른 길을 가야만 하는 것이다.

 

어떠한 클래스를 생성할 때 추상화를 꼭 하는 것이 좋다.

그렇게 함으로써 상속, 인터페이스를 제대로 쓸 수 있게 될 것이다.

 

** 하지만 말로 이렇게 하는 것이 쉽지... 실제로는 나도 제대로 못한다ㅎ 어렵다.

 


 

Encapsulation, 캡슐화

 

우리는 모든 것의 전문가가 아니다.

하지만 우리는 머리 모양의 전문가인 미용사가 쓰는 드라이어를 우리도 쓸 수 있다.

아니 그 미용사조차도 드라이어의 원리는 모른다. 

반대로 드라이어를 발명한 사람은 미용사보다 머리를 잘말릴까? 그것도 아니다.

 

즉, 우리는 무엇을 사용할 때

원리를 알고 사용하지 않는다.

사용법을 알고 사용한다.

++ 사용법을 몰라도 그냥 코드를 꽂아 보면서 배운다.

하지만 드라이어를 조금 더 사용자친화적으로 만들기 위해

슬라이더를 만들어놓았다.

 

강풍, 약풍, 냉풍을 사용자가 원하는 대로 바꿀 수 있게 되었다.

-> public

드라이어의 원리를 모르지만 기능을 사용할 수 있게되었다.

 

하지만 드라이어의 전압을 바꿔서 모터를 더 빠르게 돌리는 것과 같은 행위는 일반인이 못하게 막아놓았다.

-> private

뭐 그냥 비유해보았다.

 

즉, 내가 필요한 기능을 사용하기 위해서 내가 그 부분을 이해할 필요는 없었다.

다른 사람이 만든 기능을 언제든지 이용할 수 있다는 것이다.

이는 라이브러리라고 하는 집합체를 만들었고 사용자는 내부 구현방식을 모르고도 사용할 수 있게 되었다.

수학자는 증명을 할 수 있어야하고

공학자는 이용 할 수 있어야 한다고 그랬던가.

 

내가 한 말이다.

 


 

Inheritance, 상속

 

상속도 아까 추상화에 이어서 나온다.

우리가 영장류라는 것으로 추상화를 할 수 있다면

영장류의 특징을 가지는 원숭이, 침팬지, 고릴라, 인간들을 다 확장해서 만들 수 있을 것이다.

또한 그 사람에서도 상속이 되면서

오스트랄로피테쿠스 - 호모 에렉투스 - .... 호모 사피엔스 이렇게 상속되는 것이다.

 

** 상속에서는 부모의 행위를 자식이 거부하는 행위는 허용하지 않는다.

** 퇴화함수를 구현하는 것도 마찬가지다. 오버라이딩을하여 제대로 작동하지 않게 만드는 것이다.

-> 객체지향설계를 위반하는 행위

 

 


 

 

Polymorphism, 다형성

 

다형성이란 상속 이후에 나타날 수 있는 특징이다

 

오스트랄로피테쿠스는

Move()에 네 발로 기어다니기가 있다면

호모 에렉투스는

Move()에 두 발로 직립보행이 있다.

 

에렉투스가 오스트랄로피테쿠스에게 상속을 받아서 Move()를 가지고 있지만

Move()라는 의미만 같을 뿐 형태는 변화할 수 있다는 것을 말한다.

 

++ Overriding, Overloading이 다형성의 한 종류라고 말한다.

오버로딩 : 메서드의 이름이 같지만, 매개 변수가 다르다.

오버라이딩 : 부모 클래스의 메소드를 자식 클래스의 용도에 맞게 재정의하여 코드의 재사용성을 높인다.

 

 


 

SRP(Single Responsibility Principle) : 단일 책임 원칙

어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다.

 

라는 말이 있다.

즉, 그 이유는 그 클래스의 "기능"을 바꾸기 위함이어야 한다.

 

다시 말해서 하나의 클래스에는 하나의 기능만을 가지도록 설계하라는 것이다.

-> 이는 결합도를 낮춰준다.

 

즉, 변경할 때 결합도가 낮은 클래스를 변경하게 된다면 

그에 의존하는 클래스도 적기 때문에 비용이 적게든다는 것이다.

 

여기서도 추상화라는 개념이 나온다.

필요한 것 이상으로 들어가면 다른 클래스가 이 클래스와 의존할 가능성도 높아진다.

 

클래스를 설계할 때, 속성과 메서드를 만들 때, 정말 기능이 필요한가를 생각해보게 해주는 원칙이다.

 


 

OCP(Open-Closed Principle) : 개방-폐쇄 원칙
확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

 

 

확장은 자유롭게, 변경은 없게하지 못할 것이면 최소화라도 해야한다. 라는 말이다.

이와 역시 추상화와 연관이 깊다.

확장이 자유로워지려면? 최상위 클래스를 고려할 때 추상화해서 넣어야 한다는 말이다.

 

아까도 예로 들었지만

척추동물의 특징을 넣어야 하는데...

영장류의 특징을 넣었다?

척추동물은  사자, 말, 원숭이, 인간이 될 수 있는데

영장류는 원숭이, 침팬지, 고릴라, 인간 등 밖에 안된다.

 

즉, 불필요한 기능을 넣는다면 최상위 클래스에 적합하지 않게 된다.

확장성이 떨어진다는 말이다.

 


 

LSP(Liskov's Substitution Principle) : 리스코프 치환 원칙
상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.

 

 

여기서 업캐스팅, 다운캐스팅이란 개념이 나온다.

상속과 관련이 깊다.

상위 타입이란 상속을 해준 쪽에 해당한다.

하위 타입이란 상속을 받는 쪽에 해당한다.

 

예를 들어 동물 클래스와 사람 클래스가 있다고 해보자

동물은 사람인가? (x)
사람은 동물인가? (o) 동물의 한 종류

 

Animal instanceA = new Human();

상위 타입으로 하위 객체를 만들었다.

이 때 정상적으로 작동해야한다는 말이다.

정상적으로 작동하지 않으면 LSP 위반이다.

 

위반하게 되면 발생하는 문제점은

코드 복잡도를 높이고, 부모 클래스가 자식 클래스를 알아야 하는 경우도 생긴다.

또한 상위 타입으로 하위 타입을 다룰 수 없기 때문에 매번 다룰 때 마다 하위클래스를 명시적으로 선언해야 한다는 것이다.

 

-> 중간에 상속이 잘못 설계되었다?? -> 이미 99퍼센트 완료했다?  ->비용 어마어마

 

** LSP를 지키는 것은 어려울 수 있지만... 지키려고 노력을 하자..

trade-off 를 생각하는게 맘 편하다.

 


 

ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.

 

 

SRP가 클래스의 단일 책임이라면 ISP는 인터페이스의 단일 책임이라고 보면 된다.

즉, 인터페이스라고 해서 모든 기능을 때려 넣는 그런 행위는 지양해야 한다.

다중 상속은 안되어도 다중 인터페이스는 가능하기에

기능을 최대한 나누어서 역할을 명확히 정해서 인터페이스를 만들어야 한다.

 

 


 

DIP(Dependency Inversion Principle) : 의존 역전 원칙
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.

이 두 모듈 둘 다 다른 추상화된 것에 의존해야 한다.

 

 

즉, 의존성을 가지게 된다면 자신보다 변화가 많은 것에는 의존하지 말라는 이야기다.

다른 클래스에 의존하게 되면 그 클래스가 변경되었을 때, 나도 변하는 경우가 발생한다.

** 의존 관계는 단방향을 지향한다.

 

그것을 해결하기 위해서 모듈간의 의존성을 줄이기 위해 상위 레벨에서 정의한 추상을 하위 레벨 모듈에서 구현하게 하는 것이다.

여기서 상위 레벨은 추상적인 것. 하위 레벨은 구체적인 부분을 말한다.

즉, 인터페이스를 만들어서 구현하는 것이 적절하다.

 


 

객체 지향이란 것에 대해 알아보았다.설계하는 데에 시간이 엄청나게 들지만

그 이후 유지 보수가 쉬워지는 장점설계서가 있으면 이와 비슷한 것에도 이용할 수 있다는 장점

컴퓨터를 사람과 비슷하게 생각하게 하는 장점

 

소프트웨어 공학(Engineering)이란 것이 왜 필요한가를 알려준다.

최소 비용, 최대 성과를 내기 위해선 현재 꼭 알아야 하는 생각의 방향이다.

 

자꾸 읽으면서 체화시키도록 노력해야겠다.

반응형
그리드형