4 minute read

0. 객체지향 프로그래밍

  • 실세계를 참고하여 객체들을 창조하고,  그 객체들 간의 협력을 통해 프로그램을 개발하는것이다.
  • 객체는 상태를 가지고 있다. 현재 상태 기반으로 책임에 맞는 행동(메소드)를 수행하여 성공/실패여부를 판단하고, 새로운 상태로 갱신한다.

1. SOLID (객체지향의 원칙)

1. SRP: 단일 책임 원칙

객체는 되도록 하나의 책임을 가져야 한다. (클래스를 고치는 이유는 단 하나여야 한다.)

CRUD 기능을 제공하는 클래스를 개발했다고 가정해보자, 이 클래스의 메소드는 create, read, update, delete 4개로 구성되어있고, 고치는 사유는 4가지가 될수 있으므로 개발자의 시각에 따라 단일 책임원칙을 위반할수 도 있다. 하지만 동일한 자원에 대한 crud를 한다는 관점에서는 단일책임원칙을 지켰다고 볼수 있다. 따라서 SRP에 대한 준수 여부는 개발자가 비즈니스 환경에 맞게 그때그때 판단해야 한다.

2. OCP: 개방 패쇄 원칙

새로운 기능 추가, 수정을 하면서 코드 변경을 최소화 해야 한다. 캡슐화를 하지 않으면 결합도가 높아져 수정시 관련 클라이언트코드를 모두 수정해야하는 취약점이 생길수 있다. 캡슐화를 하면, 결합도가 낮아져 해당 객체의 메소드만 수정하면 되서 확장에 열려 있다.

public interface QuestionSearchRepository<T, ID> {
    Page<Question> findAll(Pageable pageable, UserIdentityField user, QuestionParams params);
}

위 코드는 문제들을 DB로부터 읽어오는 레파지토리 계층의 메소드이다. 메소드를 JPQL로 구현한 구현체를 사용하고 있다고 가정해보자. Querydsl이라는 새로운 라이브러리를 알게되었다. 하지만 걱정이 없다. 리파지토리를 사용하는 서비스는 인터페이스에만 의존하고 있기 때문에 Querydsl을 사용하여 새로운 구현체를 구현하더라도(확장에 열림) 인터페이스에만 맞춰서 구현하연 서비스 코드의 변경을 막을수 있다. (변경에는 닫힘)

3. LSP: 리스코프 치환원칙

서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 것을 뜻한다.

public interface QuestionSearchRepository<T, ID> {
    Page<Question> findAll(Pageable pageable, UserIdentityField user, QuestionParams params);
}

이코드를 다시 가져왔다. 위 메소드는 검색메소드이기 때문에 구현체에서 검색기능을 구현해야 한다. 이메소드가 삭제나 생성, 업데이트를 하도록 구현하면 LSP를 위반하게 된다.

4. ISP: 인터페이스 분리 원칙

되로록 인터페이스를 분리하여(역활을 쪼개어) 한다. 어떤 클래스의 메시지를 테스트한다고 생각해보자, 클래스의 메시지는 다른 구현체에 요청을 할 가능성이 높고, 인터페이스를 통해 공개된 메시지를 통해 통신한다. 테스트를 위해선 메시지들을 위해 mocking이 필요한데, 인터페이스가 분리되어있지 않고 합쳐져 있다면 클래스(클라이언트)가 어떤 메시지를 필요로 하고 mocking을 해야할지 판단하기 위해서는 메소드가 어떻게 구현되어있는지를 조사해야 하는 불편함이 있다. 또한 개발자가 퇴직하고 새로운 개발자가 퇴직했을때 인수인계가 안되면 위의 과정을 다시 반복해야 하기때문에 유지보수성이 떨어진다.

5. 의존관계 역전원칙

잘변하는것보다 잘변하지 않는것에 의존해야 한다. 구현이 아닌 인터페이스에 의존해야 한다. 자바의 리스트는 인터페이스이고 이의 구현체를 LinkedList, ArrayList등이 있다. ArrayList를 사용하고 있을때, 굳이 ArrayList가 제공하는 메소드가 필요없으면 객체 래퍼런스를 List로 받아쓰는것이 좋다. 향후 LinkedList로 교체하게되면, 구현체만 바꿔주면 클라이언트 코드의 동작이 보장된다.

2. 객체지향 프로그래밍의 특징

1. 추상화

프로그램에 필요한 객체들의 역활(개념)분류 하는것이다. 추상화가 잘되면 객체가 가지는 책임이 명확해져 재사용성이 증가하고, 같은 역활을 가지는 객체의 교체(다형성)도 용이하여 재사용성을 높혀 단일책임원칙과 인터페이스분리 원칙에 기여한다.

2. 캡슐화

협력에 필요한 메시지(요청)는 공개하고 나머지 내부 메시지나 메시지를 처리하는 메소드에 필요한 상태들을 감춘다. 상태에 대한 변경은 오르지 상태의 소유자 객체만 할수 있고 이를 통해 자율성을 얻는다. 자율성이 높아질수록 객체는 똑똑해지고 객체의 교체가 용이해지면서 유지보수가 쉬워진다. 캡슐화를 지켜서 개발하면 같은 메시지를 제공하는 객체로 교체를 하는것이 용이하여 개방패쇄원칙에 기여 한다.

2. 다형성

추상화를 통하여 얻은 역활을 맡는 객체는 여러개일수 있다. 다형성의 특징으로 클라이언트 코드의 변경없이 객체의 교체가 가능하여 개방패쇄원칙에 기여한다.

3. 포트 & 어댑터 아키텍처

어댑터란 어플리케이션의 종단에서 요청을 받거나 처리하는 객체로 DAO나, 스프링 웹컨트롤러 등이 있다. 어댑터와 도메인로직을 포함한 어플리케이션이 통신할때는 인터페이스인 포트를 사용하는것이 포트&어댑터 아키텍처이다.

포트를 통해 통신하면서 의존관계 역전원칙, 개방패쇄 원칙을 지켜 어댑터의 교체가 용이해지므로 좋은 객체지향 아키텍처가 된다. 어플리케이션은 순수한 자바코드로 구성, 어댑터는 특정 프레임워크에 라이브러리에 의존적이다. DI을 통해 어댑터를 포트를 주입해주면서 프레임워크에의해 흐름이 주도되는 IOC가 일어나게 된다.

4. IOC

제어 역전(Inversion of Control, IoC)은 객체 지향 프로그래밍에서 사용되는 프로그래밍 패러다임으로, 컴퓨터 프로그램의 제어 흐름이 프레임워크나 컨테이너에 의해 결정되는 것을 의미한다. 일반적인 프로그래밍에서는 개발자가 코드의 흐름을 직접 제어하고, 필요한 객체를 직접 생성하여 사용한다. 하지만 제어 역전에서는 프로그램의 실행 흐름과 객체의 생성, 관리 등이 외부에서 결정되고 개발자는 이러한 결정에 따라 코드를 작성한다.

5. DI

의존성 생성에 대한 책임을 어플리케이션에서 분리하는것이 DI이다. DI를 통해 개방패쇄원칙과 의존관계역전원칙을 지키는데 도움을 얻을수 있다. 또한 객체를 클라이언트 주입하므로 잘변하는계층이 잘안변하는 계층에 의존하게 되고 IOC 를 구현한다. 전략패턴, 탬플릿메소드패턴, 템플릿 콜백 패턴과 궁합이 맞는다.

6. 전략패턴

전략 패턴(Strategy Pattern)은 객체 지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로, 알고리즘을 정의하고 각각을 캡슐화하여 동적으로 교환 가능하도록 만드는 패턴입니다. 이 패턴을 사용하면 알고리즘의 변화에 영향을 받지 않고 알고리즘을 사용하는 클래스를 변경하거나 확장할 수 있습니다.

Context (컨텍스트): 알고리즘을 사용하는 클래스를 의미합니다. 컨텍스트는 전략 인터페이스를 멤버로 갖고 있으며, 해당 인터페이스를 통해 다양한 전략 객체를 주입받아 사용합니다.

Strategy (전략): 알고리즘을 추상화한 인터페이스를 나타냅니다. 이 인터페이스를 구현하는 여러 전략 클래스들은 각각 다른 알고리즘을 구현하고 있습니다.

ConcreteStrategy (구체적인 전략): Strategy 인터페이스를 구현한 클래스들로, 실제로 다양한 알고리즘들을 제공합니다.

7. 템플릿메소드패턴

템플릿 메서드 패턴(Template Method Pattern)은 객체 지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로, 알고리즘의 구조를 정의하고 하위 클래스에서 알고리즘의 특정 단계를 구현하도록 하는 패턴입니다. 이를 통해 알고리즘의 구조는 변경하지 않으면서 알고리즘의 일부를 서브클래스에서 변경할 수 있습니다.

Abstract Class (추상 클래스): 알고리즘의 구조를 정의하는 추상 클래스입니다. 이 클래스는 템플릿 메서드를 가지고 있으며, 이 메서드는 알고리즘의 구조를 담고 있습니다. 추상 클래스는 템플릿 메서드와 함께 구체적인 단계들을 나타내는 추상 메서드 또는 기본 구현이 있는 메서드들을 포함하고 있습니다.

Concrete Class (구체적인 클래스): 추상 클래스를 상속받아 알고리즘의 구체적인 단계들을 구현하는 클래스입니다. 구체적인 클래스는 추상 메서드들을 오버라이딩하여 각각의 단계를 구현합니다.

8. 템플릿콜백 패턴

알고리즘의 특정 부분만을 클라이언트가 수정할 수 있도록 하는 패턴

템플릿 메서드 (Template Method): 알고리즘의 구조를 정의하는 메서드를 의미합니다. 이 메서드는 일련의 단계들을 정의하고, 그 중간에 변경이 필요한 부분을 추상 메서드 또는 콜백 메서드로 선언하여 서브클래스에서 구현하도록 합니다.

콜백 메서드 (Callback Method): 템플릿 메서드에서 정의한 알고리즘의 특정 단계를 클라이언트에서 구현하는 메서드를 의미합니다. 이 메서드들은 추상 메서드 또는 인터페이스를 통해 선언됩니다.

클라이언트 (Client): 템플릿 메서드를 호출하여 알고리즘을 실행하는 클래스를 의미합니다. 클라이언트는 특정 알고리즘 단계를 구현한 콜백 메서드를 템플릿 메서드에 전달합니다.

9. AOP (관점지향 프로그래밍)

코드에 공통적으로 등장하는 ASPECT를 분리하는 패러다임. AOP를 구현하는 방법은 여러가지가 있다. ASPECT는 advice(처리해야하는 공통된 작업), pointcut(처리할 클래스와 메소드), join point(처리할 포지션) 으로 구성된다.

  1. 직접 프록시(데코레이터) 클래스를 만들어서 필요한 처리한다.
  2. jdk 다이나믹 프록시로 프록시 클래스를 쉽게 만들수 있다. 이때 invocationHandler, methodintercepter를 선택할수 있고 methodintercepter의 경우 타켓을 파라미터로 받을수 있어 advice의 재사용성을 높혀준다.

10. 싱글톤 패턴의 문제점

구현에 따라 멀티쓰레드의 경합으로 인해, 리플랙션이나 직렬화 역직렬화 하는 과정에서 인스턴스가 2개 이상 만들어질 문제점이 있다. 싱글톤패턴은 클래스 자체가 인스턴스를 생성하고 제공하는 구체적인 방법을 제공하므로 개방패쇄원칙과 의존관계역전원칙을 위배한다. 추상 클래스와 인터페이스는 다형성(Polymorphism)과 상속(Inheritance)을 활용하여 유연하고 확장 가능한 코드를 작성하는데 중요한 역할을 한다. 하지만 정적 클래스는 인스턴스를 생성할 필요가 없으며, 인스턴스를 생성하지 않는 클래스는 다형성과 상속을 고려할 필요가 없기 때문이다.

Leave a comment