본문 바로가기

Dev.World/개발상식&언어

다시 보는 객체 지향

발 공부를 시작할 때, 객체 지향이란 무엇인가, 객체 지향적인 설계는 어떻게 해야 하는 것인가에 대한 포스팅을 한 적이 있다. 이쯤에서 이 개념들을 다시 한 번 짚고 넘어가봐야 할 것 같아서 다시 한 번 정리해봤다.

#다시 보는 객체 지향
객체 지향의 시작
우리가 주변에서 사물을 인지하는 방식대로 프로그래밍할 수 있지 않겠는가 하는 것이 객체지향의 출발이다. 객체 지향에서 객체를 다음과 같이 정의한다. 세상에 존재하는 모든 것은 객체이며 각각의 객체는 고유하다. 또한 객체는 어떠한 속성을 갖고 어떠한 행위를 한다. 이에 따라 객체의 속성을 property로 정의하고 행위를 method로 정의한다.

객체 지향은 어떤 특성을 갖고 있는가?
캡슐화 : 정보은닉
상속 : 재사용
추상화 : 모델링
다형성 : 사용 편의

클래스란 무엇인가?
클래스는 분류에 대한 개념을 정의해둔 것이다. 객체는 말 그대로 실체다.
프로그래밍 상에서 클래스를 기반으로 객체화된 것을 인스턴스라 한다.

클래스를 기반으로 new라는 키워드를 통해 인스턴스를 생성하면 메모리에는 어떤 일이 일어날까
인스턴스는 new 키워드를 통해 생성되어야만 메모리에 영역이 할당된다. 인스턴스가 가지고 있는 속성들은 생성되기 전까지는 메모리에 할당되지 않는 것이다. 그리고 인스턴스의 속성, 즉 인스턴스 멤버 변수들은 스태틱 영역이 아니라 힙 영역에 할당된다.

흔히들 이야기하는 붕어빵과 붕어빵 틀은 '잘못된 메타포'이다. 필자도 이렇게 배운 것으로 기억한다. 이것은 잘못되었다. 붕어빵은 먹는 것이고 붕어빵 틀은 무엇인가를 만드는 것이다. 붕어빵 틀을 기반으로 객체화 된 것은 붕어빵 틀이 되어야 하며 붕어빵은 붕어빵을 기반으로 객체화되어야 한다. 우리가 실생활에서 존재하는 어떤 것을 프로그래밍으로 옮겨올 때, 추상화 과정을 거쳐 클래스를 만드는 것이다.



객체지향의 특성들에 대해서 알아보자.
추상화란 무엇인가?
구체적인 것을 분해해서 관찰자가 관심있는 특성만 가지고 재조합하는 것이라고 할 수 있다. 현실 세계를 프로그래밍으로 옮겨올 때 객체의 특성을 정의하고 객체가 하는 행위를 정의할 텐데, 객체의 모든 특성, 모든 행위를 정의할 수는 없다. 프로그래밍 과정에서 만들고자 하는 것에 필요한 부분을 선별하여 특성과 행위를 정의하는 것이다. 이 과정을 추상화라고 하며, 모델링이라고도 한다. 클래스를 설계하는 과정에서 추상화가 사용되며 추상화의 결과가 클래스이다.


상속이란 무엇인가
상속은 조직도나 계층도의 개념이 아니다. 클래스는 분류이므로 상속도 분류이어야 한다. 객체 지향에서 상속은 상위 클래스의 특성을 하위 클래스에서 상속하고 거기에 더해 필요한 특성을 추가, 즉 확장해서 사용할 수 있다는 의미이다. 상위 클래스는 상위 클래스에서 포함할 수 있는 특성이 많을수록 좋고 인터페이스는 구현을 강제할 메서드의 개수가 적을수록 설계가 잘 된 것이라고 할 수 있다.

그렇다면 상속을 받아 확장한 클래스를 생성하면 메모리에서는 어떤 일이 벌어질까?
만약 Animal 클래스를 상속받은 Tiger 클래스가 있다고 가정해보고 Tiger 클래스를 기반으로 인스턴스를 생성해보자.
1
2
//public class Tiger extends Animal { … }
Tiger tiger = new Tiger();
cs

Tiger 클래스를 기반으로 인스턴스를 힙 메모리에 생성하는데 이 주소값을 가리키는 참조 변수를 tiger라 한다. 이 때, 인스턴스가 생성될 때, Animal 클래스도 함께 힙 메모리에 생성된다. 즉, 하위 클래스의 인스턴스가 생성될 때 상위 클래스의 인스턴스도 함께 생성된다. 사실, 모든 인스턴스가 생성될 때 모든 클래스의 최상위 클래스는 Object 클래스의 인스턴스도 함께 생성되는 것이었다!


다형성이란 무엇인가
자바에서의 다형성은 주로 오버라이딩과 오버로딩으로 설명할 수 있다.
오버라이딩은 같은 메서드 이름, 같은 인자 목록으로 상위 클래스의 메서드를 재정의 하는 것이고
오버로딩은 같은 메서드 이름, 다른 인자 목록으로 다수의 메소드를 중복 정의하는 것이다.
사실 다형성은 상속의 개념도 끌어올 수 있다.
상위 클래스를 기반으로 하위 클래스를 다룰 수 있는 것을 다형성이라고 한다.

extends와 implements의 차이는 무엇인가
extends는 추상 클래스 또는 상위 클래스를 상속받을 때 사용하는 키워드이고
implements는 인터페이스 구현을 강제하기 위해 사용하는 키워드라는 것은 다들 알 것이다.

implement 는 be able to 의 성격을 갖고
extends 는 is a kink of 의 성격을 갖는다.

또한 extends 키워드로는 하나의 클래스만을 상속받을 수 있지만
implements 키워드는 여러 개의 인터페이스를 구현 강제할 수 있다.



어떻게 객체 지향적으로 설계할 것인가
좋은 소프트웨어 설계를 위해서는 결합도는 낮추고 응집도는 높이는 것이 바람직하다.  결합도는 모듈 간의 상호 의존 정도로서 결합도가 낮으면 모듈 간의 상호 의존성이 줄어들어 객체의 재사용이나 수정, 유지보수가 용이하다. 응집도는 하나의 모듈 내부에 존재하는 구성 요소들의 기능적 관련성으로 응집도가 높은 모듈은 하나의 책임에 집중하고 독립성이 높아져 재사용이나 기능의 수정, 유지보수가 용이하다. 다음 5원칙도 이러한 관점에서 재정립 한 것이다.

객체 지향 설계 5원칙
1. SRP(Single Responsibility Principle) : 단일 책임 원칙
2. OCP(Open Closed Principle) : 개방 폐쇄 원칙
3. LSP(Liskov Substitution Principle) : 리스코프 치환 원칙
4. ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
5. DIP(Dependency Inversion Principle) : 의존 역전 원칙
단일 책임 원칙
작성된 클래스는 하나의 기능만 가지며 클래스가 제공하는 모든 서비스는 그 하나의 책임을 수행하는  데 집중되어 있어야 한다. 어떤 변화에 의해 클래스를 변경해야 하는 이유는 오직 하나 뿐이어야 한다. 각 개체 간의 응집력이 있다면 병합이, 결합력이 있다면 분리가 적합하다. 단일 책임 원칙은 속성, 메서드, 패키지 등에도 적용할 수 있는 개념이다. 메서드가 단일 책임 원칙을 지키지 않을 경우 나타나는 대표적인 냄새가 바로 분기 처리를 위한 if 문이다

개방폐쇄의 원칙
소프트웨어의 구성요소인 컴포넌트, 클래스, 모듈, 함수 등은 확장에는 열려있고 변경에는 닫혀있어야 한다는 원리이다. 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 해야 한다는 의미로 요구사항의 변경이나 추가사항이 발생하더라도 기존 구성요소는 수정이 일어나지 말아야 하며 기존 구성요소를 쉽게 확장해서 재사용할 수 있어야 한다는 뜻이다.

OCP를 가능하게 하는 중요 매커니즘은 추상화와 다형성에 있다. 변경될 것과 변경되지 않을 것을 엄격히 구분하여 이 두 모듈이 만나는 지점에 인터페이스를 정의한다. 구현에 의존하기 보다 정의한 인터페이스에 의존하도록 코드를 작성한다. JDBC가 그 예이다. 자바 애플리케이션은 데이터베이스라는 주변의 변화에 닫혀있고 데이터베이스가 자신의 확장에는 열려있는 것이다.

리스코프 치환의 원칙
서브 타입은 언제나 기반 타입과 호환될 수 있어야 한다. 일반적으로 선언은 기반 클래스(상위 클래스)로 생성은 구체 클래스(하위 클래스)로 대입하는 방법을 사용한다. 생성 시점에서 구체 클래스를 노출시키기 꺼려질 경우 생성 부분을 Abstract Factory 패턴을 사용하여 유연성을 높일 수 있다. 상속한 경우, 상위 클래스와 하위 클래스가 is a kind of 의 관계 또는 be able to 의 관계를 가져야 한다.

인터페이스 분리의 원칙
가능한 최소한의 인터페이스를 사용해야 하며, 하나의 일반적인 인터페이스보다는 여러 개의 구체적인 인터페이스가 낫다는 원리이다. 클라이언트는 자신이 사용하지 않는 메서드에 의존 관계를 맺으면 안되기 때문이다.

의존성 역전의 원칙
자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 것이 의존 역전 원칙이다. 하위 클래스나 구체 클래스가 아닌 상위 클래스 일수록 또는 인터페이스, 추상 클래스를 통해 의존하라는 것이다. 상위 클래스 일수록 또는 인터페이스, 추상 클래스 일수록 변하지 않을 가능성이 높기 때문이다. 



end