나에게 주 언어가 무엇이냐고 묻는다면 나는 Java라고 대답을 한다. Java는 객체 지향 언어인데 객체 지향이 무엇이냐고 물어보면 어.. 객체 단위로 생각을 해서 만드는 거? 라고 대답을 하곤 한다. 과연 이게 맞는 말인지 객체 지향에 대해서 살펴보면서 조금 더 공부를 해보고자 한다.
순차적인 처리가 중요시 되며 프로그램 전체가 유기적으로 연결되도록 만드는 프로그래밍 기법
객체 지향 프로그래밍인 줄 알았는데 갑자기 절차 지향 프로그래밍이 나와서 놀란 사람들도 있을 것이다. 잘못 쓴 것이 아니라 객체 지향 프로그래밍을 알기 위해서 우선적으로 알아야하는 부분을 먼저 설명을 하려는 것이다.
이 절차 지향 프로그래밍이 뭐길래 객체 지향 프로그래밍과 연관이 되어있을까?
절차 지향 프로그래밍은 말 그대로 절차대로 순서대로 처리를 하는 방식인데 이는 컴퓨터의 작업 처리 방식과 유사하기 때문에 객체 지향 언어를 사용하는 것에 비해 더 빠르게 처리가 된다.
특히 옛날에는 하드웨어와 소프트웨어의 개발 속도차이가 크지 않았기 때문에 절차 지향 프로그래밍을 많이 사용했었다.
그러나 하드웨어의 발전으로 스프트웨어의 개발 시간이 따라가지 못하게 되어 소프트웨어의 개발 시간을 단축하기 위해 캡슐화해서 개념적으로 접근하는 형태인 객체 지향 프로그래밍이 탄생하게 된 것이다.
컴퓨터 프로그램을 명령어의 목록으로 보는 시간에서 벗어나 여러 개의 독립된 단위, 즉 “객체”들의 모임으로 파악하고자 하는 것이다
위키피디아에 나온 객체 지향 프로그래밍의 정의이다.
내가 얘기하는 객체 단위로 생각해서 만드는 거? 라는 말과 비슷한 것 같다ㅎㅎ
자랑은 이쯤하고 조금 더 쉽게 얘기를 해보자면
프로그램을 구성하기 전에 객체를 먼저 생각을 해보고 객체들의 관계를 중심으로 프로그램을 구성하는 방식이라고 생각하면 될 것 같다.
영어로는 OOP라고 쓰는데 Object-Oriented Programming의 약자이다.
자 그렇다면 이렇게 객체 지향 프로그래밍을 사용했을 때의 특징으로는 뭐가 있을까? 대표적으로 4가지가 존재한다.
추상화(abstraction)
공통된 속성이나 기능을 묶는 것으로 클래스를 생성하는 것을 추상화라고 할 수 있다.
호랑이, 토끼, 노새, 노루 등을 객체라고 한다면 이 객체들을 동물로 묶는 것을 추상화라고 할 수 있다.
캡슐화(encapsulation)
우리 흔히 알약을 생각해보면 캡슐 안에 어떠한 것이 들었는지 알 수 없다. 그것과 마찬가지로 우리가 추상화를 통해 공통된 속성이나 기능을 묶었을 때 이를 하나의 캡슐로 감싸서 은닉하는 것이라고 생각하면 된다.
상속(inheritance)
하위 클래스가 상위 클래스의 속성과 행위를 물려받는 것이다.
Member라는 클래스에 나이와 이름이 있다. 이때 학과와 학년을 추가하고 싶다면 Member라는 클래스에서 Student를 새로 생성할 수 있다.
다형성(polymorphism)
하나의 변수명 또는 메소드명이 상황에 따라 다른 의미로 해석될 수 있는 것을 뜻한다. 여기에는 오버로딩과 오버라이딩이 있다.
오버라이딩은 상위 클래스의 메소드를 하위 클래스가 재정의하는 것이고,
오버로딩은 메소드명은 같으나 매개변수의 개수가 혹은 매개변수의 자료형이 다른 메소드를 뜻한다.
유지보수의 측면에서 살펴보면 객체 지향 프로그래밍이 절차 지향 프로그래밍보다 유리하다.
그 이유는 객체 지향 프로그래밍은 객체를 구성하고 객체와의 관계들을 구성하기 때문에 수정 사항이 생겼을 경우 객체나 객체의 관계를 수정하면 되지만, 절차 지향 프로그래밍의 경우 흐름대로 코드를 짰기 때문에 수정했을시 어디에서 문제가 생길지 알 수 없다.
속도의 측면에서 살펴보면 절차 지향 프로그래밍이 객체 지향 프로그래밍보다 유리하다.
그 이유는 위에서도 말했지만 절차 지향 프로그래밍이 컴퓨터의 처리 방식과 동일하기 때문이다. 따라서 절차 지향 프로그래밍이 객체 지향 프로그래밍보다 더 빠르게 작업이 처리가 된다.
그렇다면 각각 어느 때 사용해야 적합할까?
비교적 대형 프로젝트일 경우 많은 기능이 존재할 때 객체 지향이 적합하고, 소형의 프로젝트일 경우 적은 기능만 존재하기 때문에 이때는 절차 지향이 적합하다.
여기서 알아야하는 가장 중요한 사실은 객체 지향 프로그래밍의 반대가 절차 지향 프로그래밍이 아니라는 것이다!
두 프로그래밍은 초점을 둔 부분이 다를 뿐이다. 절차 지향은 순차적인 실행에 객체 지향은 객체 간의 관계에 초점을 맞춘 것이다.
객체 지향 프로그래밍에 대해서는 어느 정도 알았는데, 갑자기 SOLID는 무엇인가?
객체 지향 설계의 원칙에는 SRP, OCP, LSP, ISP, DIP 이렇게 5가지가 있으며 이 각각의 앞글자를 따서 SOLID 법칙이라고 불린다.
Single Responsibility Principle의 약자로 하나의 클래스는 하나의 책임만을 가져야 한다는 원칙이다.
즉, 클래스는 한가지 기능만 가지며 한가지 책임을 수행하는데 집중되어야한다.
단일 책임 원칙을 적용하게 된다면 알맞은 책임을 가지기 때문에 책임의 영역이 확실해진다. 이렇게 되면 모듈이 변경되는 이유는 딱 한가지밖에 없게 된다!
Open Closed Principle의 약자로 기존의 코드는 변경하지 않으면서 기능을 추가할 수 있도록 설계되어야한다는 원칙이다. 즉, 확장에 대해서는 개방적이고 수정과 변경에 대해서는 폐쇄적이어야한다.
개방-폐쇄 원칙을 적용하게 된다면 기능의 추가 요청이 왔을 때 확장을 통해 구현을 하지만 확장에 따른 클래스의 수정은 최소화하도록 프로그램을 작성해야한다! 또한 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성을 얻기 때문에 객체 지향 프로그래밍에서 반드시 지켜야할 원칙이다!
Liskov Substitution Principle의 약자로 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 원칙이다. 즉, 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다는 의미이다.
리스코프 치환 원칙은 올바른 상속을 위해서 자식 객체의 확장이 부모 객체의 방향을 온전히 따르도록 권고하는 원칙이라고 생각하면 된다!
Interface Segregation Principle의 약자로 하나의 일반적인 인터페이스로 사용하는 것이 아니라 여러 개의 구체적인 인터페이스로 분리해서 사용해야한다는 원칙이다. 이때 주의할 점은 기존의 클라이언트에 변화를 주지 않으면서 인터페이스만 분리하여 구현해야한다는 것이다.
인터페이스 분리 원칙을 적용한다면 의존도를 낮춰서 리팩토링과 구조 변경을 용이하게 할 수 있다!
Dependency Inversion Principle의 약자로 추상화에 의존을 해야하며 구체화에 의존하면 안된다는 원칙이다. 자신보다 변하기 쉬운 것에 의존해서는 안되는 것이다.
추상화와 구체화만 봤을 때 변하기 쉬운 것은 추상화라고 생각할 수 있다. 하지만 내가 가지고 노는 장난감이 자동차에서 기타로 바뀐다면 이때 추상화는 장난감이고 구체화는 자동차와 기타가 된다. 이처럼 자주 변하지 않는 추상화에 의존을 해야 변화를 쉽게 수용할 수 있다.
의존 관계 역전의 원칙을 적용한다면 의존성 주입(DI)라는 기술로 변화를 쉽게 수용할 수 있는 코드를 작성할 수 있다!
의존성 주입이란 객체가 필요로 하는 의존 객체를 외부에서 제공(주입)하는 방식이다.
객체 지향 프로그래밍(OOP)는 세상을 객체로 보며, 객체들의 관계를 중심으로 프로그램을 구성하는 방식이다!
이 객체 지향 프로그래밍에는 추상화, 캡슐화, 상속, 다형성이라는 특징이 있다!
OOP의 5가지 원칙을 SOLID 원칙이라고 하며 SCP, OCP를 안다면 나머지를 유도할 수 있다!