객체 지향 프로그래밍(OOP, Object-Oriented Programing)
객체 지향 프로그래밍은 컴퓨터 프로그램을 여러 개의 독립된 객체라는 단위의 모임으로 보는 프로그래밍을 말한다. 세계가 실재하는 객체들로 구성되어 있고, 보여지는 모든 현상과 사건은 객체들 간의 상호작용을 통해 발생한다고 보는 관점이다.
객체 지향 프로그래밍은 객체를 바꿔 갈아 끼우는 방식으로 프로그램을 유연하고 변경이 용이하게 만들기 때문에, 대규모 소프트웨어 개발에서 많이 사용된다.
객체 지향 프로그래밍에서는 각각의 객체를 속성(state)과 기능(behavior)로 분류하고, 이를 각각 변수(variable)와 메서드(method)로 정의하고 있다.
객체 지향의 특징
•
추상화(Abstration)
추상의 정의는 “사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하고 파악하는 것”이다. 여기서의 핵심은 공통성과 본질을 추출한다는 것으로, 객체 지향 프로그래밍에서의 추상화는 객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 의미한다.
예시를 들면 자동차와 오토바이는 모두 이동수단이라는 본질과 전진과 후진을 할 수 있다는 공통점이 있다. 이를 이동수단이라는 상위 클래스로 정의하고, 전진과 후진이라는 공통점을 기능으로 구현하는 것이 객체 지향의 추상화이다.
자바에서는 추상 클래스(abstract class)와 인터페이스(interface)로 추상화를 할 수 있고, 이를 구현하는 여러 구현체들을 만들어 적용하고 바꿔낄 수 있다(다형성).
•
상속(Inheritance)
상속은 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법 요소를 의미한다. 클래스 간 공유될 수 있는 속성과 기능들을 상위 클래스로 추상화 시켜, 상위 클래스로부터 확장된 여러 하위 클래스들이 상위 클래스의 속성과 기능들을 가져다가 사용할 수 있도록 한다.
상속을 통해 여러 클래스들 간에 공유하는 속성과 기능들을 간편하게 재사용하고, 반복적인 코드를 최소화할 수 있다. 추상화에서 언급했던 추상 클래스로 여러 클래스들의 공통점을 모아 위의 추상화하고, 해당 추상 클래스를 상속받아 사용하는 것이다.
상속 받아 사용하더라도 특정 메서드만 다르게 동작하고 싶다면, 메서드 재정의(method overriding)을 통해 해당 하위 클래스에서만 다르게 동작하도록 구현할 수도 있다.
•
다형성(Polymorphism)
다형성의 정의는 “어떤 객체의 속성이나 기능이 상황에 따라 여러가지 형태를 가질 수 있는 성질”이다. 객체 지향 프로그래밍에서의 다형성은 어떤 객체의 속성이나 기능이 상황에 따라 다른 동작을 수행할 수 있는 특성을 의미한다.
같은 이름의 메서드를 여러 개 정의하여 상황에 맞는 메서드를 호출해 사용하는 메서드 오버로딩(method overloading)이 다형성이다. 또한 인터페이스를 정의하고 해당 인터페이스를 사용할 때, 인터페이스를 구현하는 구현체들을 필요에 따라 바꾸어 사용하는 것도 다형성에 해당한다.
예시를 들면, 다형성은 우리가 운전면허가 있다면 어떤 자동차를 타더라도 운전 할 수 있는 것과 같다. 우리가 자동차의 내부 구조를 모르지만 어떤 자동차도 운전 가능한 것처럼, 다형성을 통해 인터페이스가 구현체의 내부 구조를 몰라도 되고 구현체가 바뀌어도 영향을 받지 않는 것이 다형성이다.
•
캡슐화(Encapsulation)
캡슐약을 살펴보면 캡슐 안의 내용물이 직접 볼 수 없고 외부로부터 오염되지 않고 안전하게 보관된다. 자바의 캡슐화도 이와 같이 외부로부터 클래스 내부를 보호하고 필요한 부분만 외부로 노출해 각 객체 고유의 독립성과 책임 영역을 안전하게 지키는 것을 말한다.
자바의 캡슐화는 public, protected, private와 같은 접근 제어자(access modifier)와 접근자(getter)/지정자(setter)로 구현된다.
SOLID 원칙
일반적으로 구조를 설계하고 코드를 작성할 때 지키길 권장되는 원칙들이 있다. 그중 대표적인 것이 SOLID 원칙으로, 객체지향 개발의 5대 기본 원칙이다.
•
단일 책임 원칙(SRP: Single Responsibility Principle)
작성된 클래스는 하나의 기능만을 가지며, 클래스의 모든 서비스는 그 하나의 책임을 수행하는데 집중해야한다. 이는 어떠한 변화가 발생했을 때, 클래스를 바꾸어야하는 이유는 하나뿐이여야 한다는 것이다. 이 원칙을 잘 지킨다면 책임 영역이 확실해지기 때문에 다른 역할에서의 책임 변경에 의한 연쇄작용에서 자유롭다. 뿐만 아니라 코드의 가독성이 좋아지고 유지 보수성이 올라간다는 장점이 있다.
•
개방-폐쇄 원칙(OCP, Open-Closed Principle)
소프트웨어의 구성요소는 확장에는 열려있고(Open) 변경에는 닫혀있어야한다(Close)는 원리다. 이는 변경사항이 발생해도 기존의 코드에는 변경이 발생하지 않아야하고, 기존 구성요소를 쉽게 확장하여 재사용 가능해야한다는 것이다. OCP는 관리 가능하고 재사용 가능한 코드의 기반이며, OCP 적용의 핵심은 추상화와 다형성이다.
•
리스코브 치환 원칙(LSP, Liskov Substitution Principle)
서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 원칙이다. 즉 다형성을 통한 확장 효과를 얻기 위해 서브 클래스가 기반 클래스와 인터페이스를 지켜야한다는 것이다. 비슷한 일을 하는 두 개체가 있다면 공통 되는 부분을 공통의 인터페이스를 만들고 이를 각각 상속 받아 구현하도록 한다.
•
인터페이스 분리 원칙(ISP, interface Segregation Principle)
하나의 클래스에서 자신이 사용하지 않는 인터페이스는 구현하지 않아야 한다는 원칙이다. 다시말해 하나의 범용적인 인터페이스보다, 여러 개의 구체적인 인터페이스 인터페이스가 더 낫다고 정의할 수 있다. 만약 어떤 클래스를 이용하는 클라이언트가 여러 개이고 각 클라이언트들이 해당 클래스의 특정 부분만을 사용한다면, 이를 각각 따로 빼내어 분리하는 것이다. SRP가 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다.
•
의존성 역전의 원칙(DIP, Dependency Inversion Principle)
추상화에 의존해야하고, 구체화에 의존하면 안된다는 의미의 원칙이다. 쉽게 말해 클라이언트는 인터페이스를 바라보고, 그 구현체를 몰라야한다는 의미이다. 이를 통해 클라이언트는 구현체의 의존성에서 벗어날 수 있고, OCP를 통해 재사용과 확장성을 보장 받을 수 있다.
설계 고려사항
SOLID 원칙 외에도 설계 시 고려하면 더 좋은 코드를 작성할 수 있는 특성들이 있다.
•
코드 재사용성
중복되는 코드를 줄이고, 코드 모듈화를 통해 공통되는 로직이나 서비스를 분리하여 재사용성을 높인다.
•
의존성의 복잡도
의존성을 적절하게 관리하여 복잡도가 지나치게 높아지지 않도록 주의한다. 복잡도가 지나치게 높다면 추상화와 인터페이스를 통해 낮추는게 바람직하다.
•
성능 및 확장성
새로운 기능을 추가하거나 기존 기능을 수정하는 경우, 작성해둔 코드나 구조가 너무 많이 변경되거나 성능 저하가 발생하지 않도록 확장성과 성능을 고려하여 작성한다.
•
코드 가독성
적절한 네이밍과 함수와 클래스를 통해 코드가 너무 복잡해지지 않도록하여, 가독성을 좋게 만들어 유지보수가 용이하도록 한다.