오브젝트와 의존관계

김수환·2023년 7월 30일
0

객체지향설계
객체는 어떻게 설계돼야 하나, 어떤 단위로 만들어지고 어떤 과정으로 객체의 존재를 들어내는가

DAO의 분리

객체지향 설계는 미래의 변화에 대비할 수 있어야 한다.

관심사의 분리

  • 미래의 변화에 대비
    -> 분리와 확장을 고려한 설계
  • 대체로 하나의 관심에 대해 변화가 일어남
    -> 관심이 같은 것 끼리는 하나의 객체, 근처 객체로 모아

중복 코드의 메소드 추출

메소드 여러개의 중복되는 코드가 있다면 이를 하나의 메소드로 만들어주자.

ex) userDAO의 db랑 연결하는 코드가 메소드마다 있음
db종류 바뀐다거나, url이 바뀐다거나 등의 이슈 -> 모든 메소드의 코드를 다 바꿔야 함
db연결 메소드 getConnection()이란 메소드를 만들어서 다른 메소드에서 이를 호출하도록 변경
public void add(User user) throws ClassNotFoundException, SQLException {
    Connection c = getConnection(); 
    ...
}
public User get(String id) throws ClassNotFoundException, SQLException { 
    Connection c = getConnection();
    ...
}

private Connection getConnectionO throws ClassNotFoundException, SQLException { 
    Class.forName("com.mysql.jdbc.Driver");
    Connection c = DriverManager.getConnection(
    "jdbc:mysql://localhost/springbook", "spring", "book");
}

리팩토링

: 변경사항에 대한 검증

  • 리팩토링 :
    기능은 유지 하면서 코드의 구조를 더 깔끔하게 기존의 동작방식에 변화 없이 내부 구조만 변경해서 재구성
    유지보수 용이, 가독성 up

테스트를 통해 기능이 유지되는지 검증할 수 있다.

상속을 통한 확장

  • 코드를 여러 프로젝트에서 재사용 하려고 한다.
  • 프로젝트 마다 사용하는 db가 다르다면?
  • 상속을 이용 -> 추상클래스 사용
    핵심 기능은 그대로 사용
    db연결 메소드만 프로젝트에서 사용하는 db에 맞춰 사용
public abstract class UserDao {
    public void add(User user) throws ClassNotFoundException, SQLException {
    Connection c = getConnection(); 
}
public User get(String id) throws ClassNotFoundException, SQLException { 
    Connection c = getConnection();
} 
public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
public class AUserDao extends UserDao {
    public Connection getConnection() throws ClassNotFoundException, SQLException {
        // A 프로젝트 DB connection 생성코드 
        }
}
public class BUserDao extends UserDao {
    public Connection getConnectionO throws ClassNotFoundException,
        SQLException {
            // B프로젝트 DB connection 생성코드
        } 

}

템플릿 메소드 패턴

상속을 통해 슈퍼클래스의 기능을 확장하기 위한 디자인 패턴

  • 변하지 않는 기능은 슈퍼클래스에 만들고 자주 변경되며 확장할 기능은 서브클래스에서 구현
  • 슈퍼클래스에 기본적인 로직의 흐름을 나타낸다.
  • 서브클래스에서 메소드를 필요에 맞게 구현

팩토리 메소드 패턴

서브클래스에서 구체적인 오브젝트 생성방법을 결정하게 하는것

  • 슈퍼클래스에서 미리 구현할 메소드를 호출해서 사용
  • 서브클래스에서 생성방법, 클래스를 재정

상속의 문제점

  • 자바는 다중상속을 허용하지 않음
  • 슈퍼클래스 변경 -> 서브 클래스도 변경
  • 확장된 기능을 다른 클래스에 적용할 수 없음 -> 또 코드 중복

dao의 확장


오브젝트는 변한다. 변화에는 특징이 있다. 변화 마다 각기 대응 방식이 다르다

클래스의 분리

  • 변화의 성격이 다른 코드를 다른 클래스로 분리하자
  • ex)
    userDao에 있던 db연결 코드를 다른 클래스로 분리
    db연결 객체를 생성해서 userDAO에서 사용
    코드를 여러 프로젝트에서 재사용 할 수 있음
public class UserDaoTest {
    public static void main(String[] args) 
    	throws ClassNotFoundException, SQLException {
    ConnectionMaker connectionMaker = new AConnectionMaker(); 
    // A 프로젝트에서 사용하는 db연동 
    UserDao dao = new UserDao(connectionMaker); 
    // 구현 클래스 결정 -> 객체 생성
    }
}

프로젝트 마다 사용하는 db가 다름 -> 같은 문제가 발생

인터페이스의 도입

  • 인터페이스를 이용해 추상화
  • 인터페이스를 이용해 객체를 생성하여 사용
  • 객체를 생성하는 코드에서 인터페이스 구현에 따라 각기 다른 객체를 생성해야함
  • ex)
    blogRepository에 기본적인 로직의 흐름을 작성하고
    memoryBlogRepository에 구체적인 구현을 함
    db사용 시 dbBlogRepository만 작성해주면 됨

관계설정 책임의 분리

클래스간의 관계를 설정하는 방법

  • 의존객체에서 해당 객체를 생성 : 구체적인 클래스를 알아야 사용
  • 의존 주입 : 인터페이스로 파라미터를 넘기고 실제 사용할때 구체적으로 클래스를 정해줌
    - 유연한 방법으로 클래스 분리
    - 필요따라 자유롭게 확장

원칙과 패턴

객체지향 설계를 위한 여러가지 방법

개방 폐쇄 원칙

기존코드를 수정하지 않고 기능을 추가할 수 있어야 한다.
  • 확장에는 개방적, 수정에는 폐쇄적
  • 인터페이스로 제공되는 확장에는 개방
  • 인터페이스를 이용하는 클래스의 변화에는 폐쇄

높은 응집도와 낮은 결합도

  • 높은 응집도 : 하나의 모듈, 클래스는 하나의 책임, 관심사에 집중되어있다.
    응집도가 낮으면 변경하려는 부분이 어딘지 찾아야됨
  • 낮은 결합도 : 객체간 연결이 최소한의 방법으로 간접적인 형태로 제공된다.
    높은 결합도 -> 클래스간 관계가 긴밀하여 작은 변경에도 다른 모듈과 객체에 영향이 큼

전략패턴

  • 자신의 기능 context 에서 필요에 따라 변경이 필요한 알고리즘은
    인터페이스를 이용해 외부로 분리한다.
  • 이를 구현한 알고리즘을 필요에 따라 바꿔서 사용할 수 있게 할 수 있는 디자인패턴
  • ex)
blogRepository를 이용해 db연결에 따라 변경하도록 함
실제 구현한 알고리즘은 memoryBlogRepository로 분리
dbBlogReopsitory구현하여 필요에 따라 바꿔서 사용 가능

제어의 역전 (IoC : Inversion of Control)

낮은 결합도를 활용하여 테스트, 유지관리를 더쉽게 하기 위한 설계 원칙

오브젝트 팩토리

팩토리 : 객체 생성 방법을 결정 -> 객체를 반환

  • 객체를 생성하는 쪽, 생성된 객체를 사용하는 쪽으로 역할을 나누려는 목적으로 사용
  • ex)
    DaoFactory라는 클래스 생성 -> DaoFactory에서 userDao, DB 연결 객체 생성
    db 연결 방식이 달라져도 DaoFactory로 자유로운 확장이 가능
public class DaoFactory { 
    public UserDao userDao() {
      
        ConnectionMaker connectionMaker = new AConnectionMakerO; UserDao userDao = new UserDao(connectionMaker);
        return userDao; 
    } 
}
  • 만약 다른 DAO의 생성기능을 넣어야 한다면?
public class DaoFactory { 
    public UserDao userDao() {
        return new UserDao(connectionMaker());
    }
    public AccountDao accountDaoO {
        return new AccountDao(connectionMaker());
    }
    public MessageDao messageDaoO {
        return new MessageDao(connectionMaker());
    }
    // ConnectionMaker를 생성하는 코드
    // 중복을 피하기 위해 따로 메소드를 구현
    public ConnectionMaker connectionMaker(){
    	return new AConnectionMaker();
    } 
   
}

제어의 역전이란?

프로그램의 제어 흐름 구조가 뒤바뀌는 것

  • 일반적으로 main(), application() 등과 같이 프로그램이 시작되는 지점에서 사용할 객체를 결정, 생성, 호출
  • 제어의 역전에서는 자신이 사용할 오브젝트를 스스로 선택 하지 않고, 제어 권한을 다른 대상에 위임한다.
  • 프레임워크도 제어의 역전 개념이 적용된 대표적인 기술
  • 프레임워크 위에 개발한 클래스를 등록 -> 프레임워크가 흐름을 주도하며 개발자가 만든 코드를 사용

스프링의 IoC

오브젝트 팩토리를 이용한 스프링 IoC

  • 빈(Bean) : 스프링이 제어권을 가지고 IoC 방식으로 직접 만들어 관계를 부여하는 객체

  • 빈 팩토리 : 스프링에서 빈의 생성과 관계설정과 같은 제어를 담당하는 IoC 객체

  • 애플리케이션 컨텍스트 : IoC 방식에 따라 만들어진 빈 팩토리,

  • 스프링 컨테이너 : 빈 팩토리이자 애플리케이션 컨텍스트
    의존관계 주입을 이용하여 애플리케이션을 구성하는 여러 빈들의 생명주기와 서비스 실행 등을 관리하며 생성된 인스턴스들에게 기능을 제공하는 것을 부르는 말이다.

  • 설정정보/ 설정 메타정보 : 스프링 컨테이너를 적용하기 위해 사용하는 메타정보.

  • 빈 팩토리라고 할 때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능 에 초점을 맞춘 것.

  • 애플리케이션 컨텍스트는 별도의 정보를 참고해서 빈의 생성, 관계 설정 등의 제어를 총괄하는 것 에 초점을 맞춘 것

스프링 컨테이너의 동작방식

  • 애플리케이션에서 IoC를 적용해서 관리할 모든 객체에 대한 생성과 관계설정을 담당한다.
  • 생성, 관계설정에 대한 코드가 없고 설정정보를 통해 생성정보와 관계설정에 대한 정보를 얻는다.
  • @Configuration 어노테이션을 통해 설정정보를 나타낼수 있다.
  • @Bean 어노테이션을 통해 객체 생성정보를 표현할 수 있다.

싱글톤 레지스트리와 오브젝트 스코프

싱글톤 패턴

객체의 인스턴스를 한개만 생성되게 하는 패턴

  • 프로그램 내에서 하나의 객체만 존재해야 한다.
  • 프로그램 내에서 여러 부분에서 해당 객체를 공유하여 사용해야한다.

서버 어플리케이션과 싱글톤

  • 클라이언트의 요청이 들어올 때 마다 객체를 새로생성
    -> 과부하, 서버가 감당할 수 없음
  • 서블릿 클래스당 하나의 객체만 만들어서 클라이언트의 요청에 따라 객체를 공유해서 사용

싱글톤의 한계

  • 상속 불가 : private 생성자
  • 테스트에 어려움
    생성 방식이 제한적이기 때문에 mock 객체로 대체하기 어려움
  • 서버환경
    클래스 로더 구성에 따라 (jvm의 분산 설치) 하나 이상의 객체가 생성 될 수 있음

싱글톤 레지스트리

스프링은 싱글톤 패턴으로 객체를 생성하고 관리한다.

  • static, private 의 사용과 상관없이 싱글톤 패턴을 사용할 수 있다.
  • 테스트 환경에서 자유롭게 객체를 생성할 수 있다.
  • 객체 지향적 설계와 디자인 패턴을 적용하는 데에 무리가 없다.

싱글톤과 오브젝트의 상태

멀티 스레드 환경에서 여러 스레드가 동시에 접근 할 수 있다.

  • 상태가 없어야한다.
  • 로컬 변수와 파라미터를 이용한다.
    독립적인 공간이 할당되기 때문에 여러 스레드가 값을 덮어쓰지 않는다.

스프링 빈의 스코프

객체가 생성되고 존재하고 적용되는 범위

싱글톤 스코프

  • 스프링 빈의 기본 스코프는 싱글톤이다.
  • 컨테이너에 하나의 객체만 만들어지고 강제로 제거하지 않는 한 스프링 컨테이너가 유지되는 동안 존재한다.

그 외의 스코프

  • 프로토타입 스코프
  • 요청 스코프
  • 세션 스코프

의존관계 주입

의존관계란?

A ----> B : A가 B에 의존하고 있다.

B의 변화가 A에 영향을 미친다.

ex)

blogService의 update() 파라미터가 바뀌면?
blogController에서 blogService.update()를 호출할 때 사용하는 파라미터가 바뀌어야 한다.

제어의 역전(IoC)과 의존관계 주입

IoC를 적용하여 객체지향적인 설계, 디자인 패턴, 컨테이너에서 동작하는 서버 기술을 사용한다.

의존관계 검색과 주입

  • 의존관계 검색
    의존관계를 맺을 객체를 결정, 생성 하는 작업은 외부 컨테이너에게 IoC에 위임
    객체를 가져올 때는 스스로 컨테이너에게 요청하는 방법을 사용
    빈 객체가 아닌경우 test 메소드등 객체를 주입받을 방법이 없는 경우에 사용
  • 의존관계 주입
    의존 관계의 객체를 가져올 때 메소드, 생성자를 통해 주입
    빈 객체 사이에서만 가능

메소드를 이요한 의존관계 주입

  • Setter를 이용한 주입
    수정자(Setter) 메소드는 외부에서 객체 내부의 속성값을 변경하려는 용도로 사용
  • 일반 메소드를 이용한 주입
    수정자 메소드처럼 set으로 시작. 여러 개의 파라미터를 받을 수 있음

정리

  • 스프링 : '어떻게 오브젝트가 설계되고, 만들어지고, 어떻게 관계를 맺고 사용되는지에 관심을 갖는 프레임워크'
    원칙을 잘 따르는 설계를 적용하려고 할 때 귀찮은 작업을 편하게 할 수 있도록 도와주는 유용한 도구

  • 개발자 : 객체를 어떻게 설계하고, 분리하고, 개선하고, 어떤 의존관계를 가질지 결정.

스프링을 사용한다고 좋은 객체지향 설계와 깔끔하고 유연한 코드가 저절로 만들어지는건 절대 아님

profile
hello human

1개의 댓글

comment-user-thumbnail
2023년 7월 30일

좋은 글 감사합니다. 자주 올게요 :)

답글 달기