🚩 INTRODUCTION
소프트웨어 설계 패러다임은 프로그램을 만들 때 어떻게 설계할지를 결정하는 방식입니다. 쉽게 말해, 프로그램을 어떻게 구조화하고 어떤 방식으로 문제를 해결할지에 대한 생각의 틀이라고 할 수 있습니다.
예를 들어, 집을 건축할 때 설계도를 그리는 것처럼, 프로그램을 만들 때도 일정한 설계 방식이 필요합니다.
소프트웨어 개발 시, 코드의 복잡성은 필수적으로 생각해봐야하는 문제인데, 이때 핵심적인 역할을 하는 패러다임이 OOP(객체지향 프로그래밍)와 AOP(관점지향 프로그래밍)입니다.
OOP는 객체 단위로 코드를 구조화하여 관리하기 쉽게 만들고, AOP는 로깅, 트랜잭션 관리 등 반복적인 기능을 분리해 효율성을 높입니다.
계속해서 건축의 예를 들어 설명해보겠습니다.
OOP는 각 방(객체)마다 필요한 침대, 창문, 가전 등(속성)을 개별적으로 설계하는 방식입니다. 그래서 각 방은 자신만의 고유한 속성과 기능을 가지고 있게 됩니다. 그러나, 전기 배선이나 수도 관리 같은 공통된 작업은 모든 방에서 필요한 부분이기 때문에 동일한 작업을 반복해서 해야 할 수 있습니다.
AOP는 모든 방에 공통적으로 필요한 작업을 한 번에 처리하는 방식입니다. 방마다 일일이 전기 배선을 다시 설계하지 않고, 전기 배선 시스템을 한 번에 집 전체에 적용하는 것처럼, 프로그램에서 공통적으로 적용해야 할 기능(로깅, 보안 등)을 하나로 관리해서 코드에 반복적으로 작성하지 않도록 해줍니다.
OOP와 AOP는 각각의 강점이 다르지만, 함께 사용하면 더 효율적으로 코드를 관리할 수 있게 되어 유지보수하기 쉽고, 쉽게 확장할 수 있게 됩니다.
이 글에서는 두 패러다임의 차이점과 장점을 비교하며, 실제 개발에서 어떻게 상호 보완적으로 사용할 수 있는지 설명하고자 합니다.
이 글을 작성하며 저또한 두 패러다임의 개념과 관계를 명확히 이해하고, 앞으로 소프트웨어를 설계할 때 어떻게 적용할 수 있을지 고민해보는 태도를 길러보고자 합니다!
⭐ OOP (객체 지향 프로그래밍)
객체 지향 프로그래밍(OOP, Object-Oriented Programming)은 소프트웨어 설계 패러다임 중 하나로, 데이터를 객체(Object)라는 단위로 구성하여 프로그래밍하는 방식입니다. 이 방식은 현실 세계의 개체들을 프로그램에서 객체로 모델링하여, 프로그램의 유연성과 재사용성을 높이는 데 중점을 둡니다. OOP는 추상화(Abstraction), 캡슐화(Encapsulation), 상속(Inheritance), 다형성(Polymorphism)의 4가지 주요 원칙을 기반으로 합니다.
✅ OOP의 4가지 주요 원칙
추상화(Abstraction)
추상화는 복잡한 시스템에서 필요한 중요한 정보만을 드러내고, 나머지는 숨기는 과정입니다.
예를 들어, 자동차를 운전할 때 운전자는 운전 방식만 알면 되지 엔진 내부의 복잡한 기계 구조나 연료 분사 시스템 등은 알지 않아도 됩니다. 자동차의 핵심 기능만 추상화 되어있어 복잡한 세부 사항을 신경 쓸 필요가 없는 것입니다.
위 사진은 교수님께서 운영하시던 DB 수업 질의응답용 깃허브 이슈 게시판인데요! 추상화와 관련된 질문에 제가 답변을 달았던 기억이 있어서 가져와봤습니당🤭
이때 이해가기 쉽도록 잘 설명한 거 같아서요ㅎㅎ (다른 학생분들이 따봉을 많이 해주셔서 기분이 좋았다.😆)
캡슐화(Encapsulation)
캡슐화는 데이터와 그 데이터를 처리하는 메서드를 하나의 객체로 묶어 외부에서 접근을 제어하는 방법입니다. 객체 내부의 데이터는 객체를 통해서만 접근 가능하며, 이를 통해 데이터 보호와 무결성을 유지할 수 있습니다.
더 쉬운 이해를 위해 전자레인지의 작동 방식을 생각하시면 될 거 같습니다. 저희는 조리 시간을 설정하는 버튼과 조리 시작 버튼만 사용하면 되지만, 전자레인지 내부 기능(온도, 판 돌아가는 속도 등)은 조작할 수 없습니다. 사용자는 필요한 기능만 접근할 수 있고, 내부 구현은 숨겨져 있는 것입니다. 만약 사용자가 전자레인지의 온도와, 판이 돌아가는 속도를 제어할 수 있었다면 전자레인지 폭발 사고가 몇배는 더 늘었을 겁니다. 캡슐화되어있기 때문에 안전한 것이죠!
상속(Inheritance)
기존 클래스의 특성을 새로운 클래스에서 물려받아 재사용하고 확장하는 방법입니다. 이를 통해 코드 중복을 줄이고, 계층적인 구조를 형성할 수 있습니다.
예를 들어, 동물이라는 큰 범주가 있을 때, 개와 고양이는 동물의 특성을 물려받는 하위 범주입니다. 개와 고양이는 "동물"이라는 공통 특성(예: 먹기, 움직이기, 귀엽기 등)을 가지고 있으며, 각각의 특성을 추가로 정의할 수 있습니다. 예를 들어 강아지는 멍멍, 고양이는 냥냥을 추가할 수 있겠죠!
다형성(Polymorphism)
동일한 이름의 메서드나 연산자가 다양한 형태로 동작할 수 있게 하는 것입니다. 이를 통해 같은 메서드 호출이 다른 방식으로 처리될 수 있습니다. 예를 들어, '날다'라는 메서드가 새와 비행기에서 각각 다른 방식으로 동작하는 것처럼, 다형성은 유연한 프로그램 설계를 가능하게 합니다.
다형성에선 오버라이딩과 오버로딩은 중요한 개념으로 사용됩니다.
오버라이딩 (Overriding)은 상속을 통해 부모 클래스의 메서드를 자식 클래스에서 재정의하는 방법입니다. 자식 클래스에서 부모 클래스의 메서드를 새로운 방식으로 구현할 수 있게 해줍니다
오버로딩 (Overloading)은 같은 이름의 메서드를 여러 개 정의하되, 매개변수의 타입이나 개수가 다르게 하는 방법입니다. 같은 메서드 이름을 사용하지만 서로 다른 인자를 받아들입니다.
이 두 개념은 코드의 재사용성과 유연성을 높이는 데 도움을 줍니다.
✅ 클래스와 객체
클래스는 객체를 만들기 위한 설계도입니다. 예를 들어, '자동차'라는 클래스를 생각해 볼 수 있습니다. 자동차 클래스는 자동차가 가질 속성(색상, 속도 등)과 기능(주행, 정지 등)을 정의합니다. 하지만 이 클래스 자체는 실제로 자동차를 만들지 않습니다. 그냥 자동차를 어떻게 만들어야 하는지에 대한 설계만 담고 있습니다.
이 설계도를 바탕으로 실제 자동차를 만들면, 그것이 바로 객체입니다. 즉, 객체는 클래스에서 정의한 설계에 따라 생성된 실제 인스턴스입니다. 여러 대의 자동차가 있을 때, 각각의 자동차는 클래스에서 정의한 속성과 기능을 가지지만, 색상이나 속도는 각각 다를 수 있습니다. 이처럼 실제로 존재하는 개별 자동차가 객체입니다.
✅ OOP의 장점
OOP를 사용하면 코드의 재사용성이 높아집니다. 예를 들어, 자동차 클래스에서 정의한 설계도를 다른 자동차 모델에도 재사용할 수 있습니다. 덕분에 새로운 자동차 모델을 추가할 때마다 설계도를 처음부터 다시 작성할 필요가 없습니다.
또한, OOP는 유지보수를 쉽게 합니다. 자동차 클래스에 문제가 생기면, 그 클래스를 수정하면 되므로, 전체 프로그램에 영향을 주지 않고도 문제를 해결할 수 있습니다.
OOP는 확장성을 높입니다. 기존에 작성한 클래스를 바탕으로 새로운 기능을 추가하거나, 기존 클래스를 확장하여 새로운 클래스를 만들 수 있습니다. 예를 들어, '전기 자동차'라는 새로운 클래스를 추가하여 기존 자동차 클래스를 확장할 수 있습니다. 이 방식은 프로그램이 커지더라도 유연하게 대응할 수 있게 해줍니다.
캡슐화라는 개념도 OOP의 장점 중 하나입니다. 캡슐화는 자동차의 내부 구조(엔진, 기어 등)와 같은 세부 사항을 숨기고, 자동차가 제공하는 기본적인 기능(주행, 정지 등)만 외부에서 사용할 수 있게 합니다. 이렇게 하면 자동차의 내부 구현에 신경 쓰지 않고도 기본적인 기능을 사용할 수 있습니다.
마지막으로, 다형성은 같은 이름의 메서드가 다양한 방식으로 동작할 수 있도록 해줍니다. 예를 들어, '자동차' 클래스에서 '소리내기'라는 메서드를 정의하고, 이 메서드를 '스포츠카', '전기차' 클래스에서 각각 다른 방식으로 구현할 수 있습니다. 이로 인해 코드가 더 유연하고, 다양한 상황에 맞춰 동작할 수 있습니다.
이러한 장점들 덕분에 OOP는 복잡한 소프트웨어를 더 잘 관리하고 유지보수할 수 있는 방법을 제공합니다.
⭐ AOP (관점 지향 프로그래밍)
AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 핵심 비즈니스 로직과 횡단 관심사(cross-cutting concerns)를 분리하는 프로그래밍 기법입니다. 이러한 개념들은 아래에서 더 자세히 설명해드리도록 하겠습니다!
쇼핑몰 개발을 예로 들면, 주문 처리 로직 외에도 로그 기록, 트랜잭션 관리, 보안 검증 같은 기능이 여러 곳에서 반복적으로 필요합니다. 이러한 공통 기능들을 비즈니스 로직에 직접 작성하면 코드가 복잡해집니다.
이처럼 보통의 비즈니스 웹 애플리케이션은 사업에 핵심적인 핵심 비즈니스 로직이 있고, 애플리케이션 전체를 관통하는 부가 기능 로직이 있습니다. 이를 횡단 관심사(cross-cutting concerns)라고 합니다.
횡단 관심사는 애플리케이션 전체에 걸쳐 나타나는 공통 기능으로, AOP는 이를 핵심 비즈니스 로직과 분리하여 코드의 복잡성을 줄이고, 유지보수를 쉽게 합니다. AOP의 목적은 코드의 가독성을 높이고, 공통 기능을 한곳에 모아 유연하고 확장 가능한 구조를 만드는 것입니다.
이처럼 AOP는 비즈니스 로직과 횡단 관심사를 따로 관리함으로써 코드가 더 깔끔해지고, 각 기능에 대한 변경이나 확장이 용이해집니다.
✅ AOP 핵심개념
횡단 관심사(Cross-cutting Concerns)
횡단 관심사는 여러 곳에서 반복적으로 필요한 공통 기능을 말합니다. 예를 들어, 보안 검사, 로깅, 트랜잭션 관리 등이 있습니다. 이러한 기능은 비즈니스 로직과는 직접적인 관련이 없지만, 애플리케이션의 여러 부분에서 필요합니다. 만약 이 기능을 비즈니스 로직마다 하나하나 추가하면 코드가 복잡해지고 유지보수가 어려워집니다.
핵심 관심사(Core Concerns)
핵심 관심사는 애플리케이션이 수행해야 할 본질적인 기능, 즉 비즈니스 로직입니다. 예를 들어, 쇼핑몰에서는 주문 처리, 결제 처리 등이 핵심 관심사입니다. AOP는 이러한 핵심 관심사와 횡단 관심사를 분리하여, 개발자가 비즈니스 로직에만 집중할 수 있게 돕습니다.
✅ AOP의 주요 구성 요소
Aspect(애스펙트)
Aspect는 여러 곳에서 공통적으로 필요한 기능을 한 군데 모아 관리하는 것을 의미합니다. AOP를 지원하는 프레임워크 (Spring AOP)에서는 Aspect와 Advice를 정의하여 이런 공통 작업을 자동으로 적용할 수 있습니다.
예를 들어, 카페에서 주문을 받을 때마다 포인트 적립과 같은 공통적인 작업이 반복됩니다. 포인트 적립이라는 공통된 작업을 Aspect로 만들어 둔 후, 주문 처리가 끝난 후 자동으로 포인트가 적립되도록 한 번만 정의해두면, 이 기능이 모든 주문 처리 과정에 자동으로 적용될 수 있습니다.
@Aspect
public class PointAccumulationAspect {
// After Advice: 주문이 완료된 후에 포인트 적립 작업을 실행
@After("execution(* com.cafe.OrderService.completeOrder(..))")
public void accumulatePoints(JoinPoint joinPoint) {
// 주문 정보 가져오기 (예: 주문 총액)
Object[] args = joinPoint.getArgs();
Order order = (Order) args[0];
// 포인트 적립 로직 (예: 주문 총액의 5% 적립)
Customer customer = order.getCustomer();
int pointsToAccumulate = (int) (order.getTotalAmount() * 0.05);
customer.addPoints(pointsToAccumulate);
// 로그 출력 등
System.out.println("포인트가 적립되었습니다: " + pointsToAccumulate + "점");
}
}
포인트 적립 작업을 Aspect로 만드는 과정의 코드입니다.
이를 통해 중복을 피하고, 코드 관리를 효율적으로 할 수 있게됩니다.
Advice(어드바이스)
Advice는 Aspect가 언제 실행될지를 결정하는 부분입니다.
포인트 적립과 영수증 발행 작업이 주문이 완료된 후 실행되면, 이것은 After Advice입니다. 만약 포인트 적립과 영수증 발행이 주문이 완료되기 전에 실행되면 Before Advice, 주문 처리 중에 실행되면 Around Advice가 됩니다. Advice는 공통 작업이 실행되는 시점을 정하는 것입니다.
위 Aspect 코드에서는 @After를 사용했는데, 이것은 completeOrder() 메서드가 실행된 후에 포인트 적립 로직이 실행됨을 의미합니다. 다른 시점에 실행되도록 하고 싶으면 @Before, @Around 등의 Advice를 사용할 수 있습니다.
Pointcut(포인트컷)
Pointcut은 Aspect가 적용될 구체적인 대상을 지정하는 부분입니다.
포인트 적립과 영수증 발행을 모든 주문에 적용할지, 아니면 특정 회원이나 특정 시간대의 주문에만 적용할지 결정하는 과정이 Pointcut입니다. 즉, 어느 주문에 이 공통 작업을 적용할지 정하는 역할입니다.
@After("execution(* com.cafe.OrderService.completeOrder(..))")
Aspect 코드에서도 확인할 수 있듯 Aspect가 적용될 대상을 지정할 수 있습니다. 위 코드에서는 OrderService.completeOrder()라는 메서드가 완료된 후에 포인트 적립이 실행되도록 설정했습니다. 즉, 주문이 완료되는 메서드(completeOrder)가 실행될 때마다, 포인트 적립 작업이 자동으로 실행된다는 의미입니다. 즉, 특정 메서드에만 Aspect가 적용되도록 하는 것이죠.
Join Point(조인 포인트)
Join Point는 Advice가 실제로 실행되는 순간입니다.
고객이 음료를 주문하고 결제가 완료된 그 순간에 포인트가 적립되고 영수증이 발행됩니다. 이 결제가 완료되는 순간이 Join Point입니다. Advice가 실행되는 실제 지점입니다.
Weaving(위빙)
Weaving은 Aspect와 기존 비즈니스 로직(주문 처리 로직)을 실제로 결합하는 과정입니다.
Spring AOP와 같은 프레임워크는 컴파일 타임 또는 런타임에 자동으로 Aspect와 비즈니스 로직을 결합합니다. 즉, 포인트 적립 작업을 별도로 작성하지 않고도, 지정된 메서드가 실행될 때 자동으로 이 공통 작업이 실행되게 됩니다.
✅ AOP의 장점
AOP의 가장 큰 장점은 반복되는 공통 기능을 쉽게 관리할 수 있다는 것입니다. 예를 들어 로깅, 보안, 트랜잭션 관리, 포인트 적립 등 여러 곳에서 공통으로 필요하지만, 각각의 클래스나 메서드마다 일일이 넣기 번거로운 작업을 한 번에 정의하고 필요할 때 자동으로 실행되도록 설정할 수 있습니다. AOP를 활용하면 이런 공통 기능을 중앙 집중식으로 관리할 수 있어, 코드의 중복을 줄이고, 필요할 때 수정도 한 곳에서 처리할 수 있습니다. 즉, 유지보수가 용이해지고, 시스템의 일관성도 유지할 수 있습니다.
⭐ OOP와 AOP의 차이점
OOP와 AOP는 이름이 비슷해 상반된 개념처럼 보일 수 있지만, 사실 AOP는 OOP를 보완하기 위해 사용됩니다. OOP는 프로그램의 목적에 따라 클래스를 만들고, 이 클래스로부터 객체를 생성하여 로직을 나눕니다. 이는 핵심 비즈니스 로직뿐만 아니라 부가적인 기능도 객체 단위로 분리합니다. 그러나 OOP만으로는 횡단 관심사(cross-cutting concerns)와 같은 반복적인 공통 기능을 적절하게 관리하는 데 한계가 있습니다. OOP는 객체 간의 관계를 정의하고 프로그램을 모듈화하는 데 집중하지만, 모든 객체에서 필요로 하는 로깅, 보안, 트랜잭션 같은 부가 기능들을 각 객체에 개별적으로 구현하면 코드 중복과 복잡성이 증가하게 됩니다.
반면, AOP는 객체 간에 공통적으로 필요한 기능들을 모듈화하는 데 중점을 둡니다. OOP로만 설계된 시스템에서는 공통 기능이 클래스마다 반복되지만, AOP를 사용하면 이러한 공통 기능을 한 곳에서 중앙 집중적으로 관리할 수 있습니다. AOP는 로직을 "언제", "어디서" 실행할지를 명확하게 정의해주고, 프로그램의 본질적인 로직과는 독립적으로 공통 기능을 처리할 수 있어 코드 중복을 줄이고 유지보수를 더 용이하게 합니다. 이렇게 AOP는 OOP가 처리하기 어려운 횡단 관심사를 깔끔하게 관리할 수 있게 해줍니다.
⭐ OOP와 AOP를 함께 고려해야하는 이유
OOP와 AOP를 함께 고려해야 하는 이유는 두 패러다임이 서로 보완적이기 때문입니다.
OOP는 객체지향적으로 시스템을 구조화하고 객체 간의 관계와 상호작용을 명확하게 정의하는 데 매우 탁월합니다. 그러나 객체별로 반복되는 공통 기능을 관리하는 데는 적합하지 않을 수 있습니다. 이런 공통 기능들은 시스템 전반에 걸쳐 적용되기 때문에, AOP를 도입하면 공통적인 로직을 별도의 모듈로 분리해 유지보수와 코드 관리가 더욱 수월해집니다.
OOP는 프로그램의 설계와 구조화에 유리하고, AOP는 이 구조 내에서 공통된 기능을 효율적으로 처리해줍니다. 이 둘을 함께 사용하면 프로그램이 더 유연하고 체계적으로 구성되며, 유지보수 역시 쉽게 할 수 있습니다.
🚩 결론
OOP와 AOP는 서로 상반된 개념처럼 보일 수 있지만, 사실 상호 보완적인 관계에 있습니다. OOP는 프로그램을 객체 중심으로 구조화하고, 객체 간의 상호작용을 통해 문제를 해결하는 데 중점을 둡니다. 그러나 프로그램 전반에 걸쳐 반복적으로 필요한 로깅, 보안, 트랜잭션 처리 같은 횡단 관심사를 개별 객체마다 구현하면 코드가 중복되고 복잡해집니다. AOP는 이러한 공통된 기능들을 모듈화하여 중앙에서 관리할 수 있게 해주고, 특정 시점에 자동으로 적용되도록 하여 코드 중복을 줄이고 유지보수를 더 용이하게 만듭니다.
따라서 OOP와 AOP는 단독으로 사용하기보다는 함께 사용했을 때 더 큰 효과를 발휘합니다. OOP는 시스템의 구조를 체계적으로 설계하는 데 유용하고, AOP는 그 구조 내에서 공통 기능을 효율적으로 처리하여 코드 관리와 유지보수를 더 쉽게 해줍니다. 이 두 가지 패러다임을 함께 사용하면, 프로그램의 유연성과 확장성이 높아지고, 더 나은 유지보수성과 코드 품질을 확보할 수 있습니다.
결론적으로, OOP는 프로그램의 기본 구조와 객체 간의 관계를 설계하는 데 강력한 도구이며, AOP는 그 구조 내에서 공통 기능을 효과적으로 관리함으로써 OOP의 단점을 보완하는 중요한 역할을 합니다. 두 패러다임을 함께 사용함으로써 더 유연하고 관리하기 쉬운 소프트웨어를 만들 수 있습니다.
📌 참고
'기술 지식 쌓아가기 📚 > Backend 🍔' 카테고리의 다른 글
[Spring] 서블릿(Servlet)에 대해 알려드리겠송! 😼🍃 (3) | 2024.09.18 |
---|---|
[Spring] SpEL(Spring Expression Language)이란? Spring에서 표현식을 다루는 쉬운 방법 알아가기 🍃 (1) | 2024.09.16 |
[Spring] 스프링 컨테이너란? 의존성 주입의 마법 🍃 (0) | 2024.09.15 |
[Spring] 스프링 빈(Bean)이란? 초보 개발자를 위한 쉬운 설명 🍃 (2) | 2024.09.13 |
[Spring] 제어의 역전(IoC)과 의존성 주입(DI) 완벽 이해하기 🍃 (4) | 2024.09.12 |