Spring boot - 오브젝트와 의존성 관계

원종서·2021년 12월 26일
0

spring

목록 보기
3/12
  • 스프링이란
  • 스프링이 관심을 갖는 대상인 오브젝트의 설계와 구현, 동작원리

1.1 초난감 DAO

DAO (Data Access Object)
DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트

package sptringbook.user.domain;

public class User {
    String id;
    String name;
    String password;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
create table users(
    id varchar(10) primary key ,
    name varchar(20) not null ,
    password varchar(10) not null
);

자자빈
두 가지 관례를 따라 만들어진 오브젝트 , 간단히는 이라고도 부른다
1. 디폴트 생성자 : 자바빈은 파라미터가 없는 디폴트 생성자를 갖고 있어야한다. (리플렉션을 이용해 오브젝트를 생성하기 때문이다)
2. 프로퍼티 : 자자빈이 노출하는 이름을 가진 속성을 프로퍼티라 한다. 프로퍼티는 set으로 시작하는 세터와 게터를 이용해 수정 또는 조회할 수 있어야한다.

1.1.2 UserDao 클래스

사용자 정보를 DB에 넣고 관리하는 클래스

JDBC를 이용하는 작업의 일반적인 순서.
1. DB 연결을 위한 Connection을 가져온다.
2. SQL을 담은 Statement를 만든다.
3. 만들어진 Statement를 실행한다.
4. 조회의 경우 쿼리의 실행 결과를 ResultSet으로 받아 정보를 저장할 오브젝트에 옮겨준다.
5. 작업중에 생성된 리소스는 작업을 맞춘 후 닫는다.

1.1.3 main()을 이용한 DAO 테스트 코드

package sptringbook.user.dao;

import sptringbook.user.domain.User;

import java.sql.*;

public class UserDao {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        UserDao dao =  new UserDao();
        User user = new User();
        user.setId("2");
        user.setName("원종서");
        user.setPassword("hello");

        dao.add(user);

        System.out.println(user.getId() + " 등록 성공");

        User user2= dao.get(user.getId());
        System.out.println(user2.getName());
        System.out.println(user2.getPassword());

        System.out.println(user2.getId() + " 조회 성공" );
    }
    public void add(User user) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/toby_stringboot", "root", "dnjswhdtj1!");

        PreparedStatement ps = c.prepareStatement(
                "insert into users(id,name,password) values (?,?,?)"
        );

        ps.setString(1,user.getId());
        ps.setString(2, user.getName());
        ps.setString(3,user.getPassword());

        ps.executeUpdate();

        ps.close();
        c.close();

    }

    public User get(String id) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/toby_stringboot", "root", "dnjswhdtj1!");

        PreparedStatement ps = c.prepareStatement(
                "select * from users where id=?"
        );
        ps.setString(1,id);

        ResultSet rs = ps.executeQuery();
        rs.next();

        User user = new User();
        user.setId(rs.getString("id"));
        user.setName(rs.getString("name"));
        user.setPassword(rs.getString("password"));

        rs.close();
        ps.close();
        c.close();

        return user;

    }
}

위의 dao코드는 형편없다. 이를 객체지향에 맞춰 밑에서 수정해보겠다

1.2 DAO의 분리

1.2.1 관심사의 분리

객체지향의 세계에서는 모든 것이 변한다.
변한다는 것은 오브젝트에 대한 설계와 이를 구현한 코드가 변한다는 소리
그래서 개발자가 객체를 설계할 때 가장 염두에 둬야 할 사항은 미래의 변화를 어떻게 대비할 것인가.

분리 1.

관심사의 분리 (Separation of Concerns)

  • 관심이 같은 것끼리는 하나의 객체 안으로 또는 친한 객체로 모이게 하고, 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것

1.2.2 커넥션 만들기의 추출

위의 코드의 add() 메서드 하나에서 세 가지의 관심를 볼 수 있다.

  • 중복 코드의 메서드 추출.
// DB Connection 메서드 추출,

  public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection c = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/toby_stringboot", "root", "dnjswhdtj1!");
        return  c;

    }

코르를 수정한 후에는 검증을 해야한다.

1.2.3 DB커네션 만들기의 독립

UserDao 소스코드를 변경하지 않고 DB커넥션 생성 방식을 바꿀 수 있는 방법

  1. 상속을 통한 확장

템플릿 메서드 패턴
: 슈퍼클래스에 기본적인 로직의 흐름을 만들고, 그 기능의 입루를 추상메서드나, 오버라이딩 가능한 protected 메서드 등으로 만든 뒤 서브클래스에서 이런 메서드를 필요에 맞게 구현해서 사용하는 방법

슈퍼클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 서브클래스에서 선택적으로 오버라이드 할 수 있도록 만들어둔 메서드를 훅 메서드 라고 한다.

팩토리 메서드 패턴
템플릿 메서드 패턴과 마찬가지로 상속을 통해 기능을 확장하는 패턴.
슈퍼클레스 코드에서는 서브클래스에서 구현할 메소드를 호출해서 필요한 타입의 오브젝트를 가져와 사용한다,.

이 메서드는 주로 인터페이스 타입으로 오브젝트를 리턴하므로 서브클래스에서는 정확히 어떤 클래스의 오브젝틀ㄹ 만들어 리턴할지는 슈퍼클래스에서는 알지 못한다.
이렇게 서브클래스에서 오브젝트 생성방법과 클래스를 결정할 수 있도록 미르 정의해줃 메서드를 팩토리 메서드라고 한다.

위의 두 메서드 패턴을 적용해 상속을 사용했다는 단점이 있다.
단지 Connection 을 분류하기 위해 상속을 사용하는 것은 다중 상속이 안되는 자바에서는 너무 아까운 일이다.
두번째로는 상속을 통한 상하위 클래스의 관계는 생각보다 밀접하다. (이는 우리가 상속으로 분류한 것이 모순이된다) , 다른 DAO 클래스에 적용할 수 없는 것도 단점이다.

1.3 DAO의 확장 -----

데이터 엑세스 로직을 어떻게 만들가 와 디비 연결을 어떤 방버으로 할 것인가라는 두개의 관심을 상하위 클래스로 분류시켰다.

변화의 성격이 다르다는 것은 변화의 이유와 시기가 다르다는 뜻이다.

1.3.1 클래스의 분리

두개의 관심사를 본격적으로 독립시키면서 동시에 손쉽게 확장할 수 있는 방법
두개를 각각의 클래스로 만드는 것이다.
Has-a 관계로 바꿔주는 것이다.

public class SimpleConnectionMaker {
    public Connection makeNewConnection() throws ClassNotFoundException, SQLException{

        Class.forName("com.mysql.cj.jdbc.Driver");

        Connection c= DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/toby_stringboot", "root","dnjswhdtj1!"
        );
        return  c;
    }
}

성격이 다른 코드의 분리는 되었지만, UserDao 클래스만으로 DB커넥션 기능을 바꾸는 것이 불가능해졌따.
왜냐하면 UserDao 코드가 SimpleConnectionMaker 라는 특정 클래스에 종속되었기 때문이다.
(메서드 이름이 makeNewConnection 이 아닌 openConnection 같은 이름을 사용했따면 UserDao 코드를 일일이 변경해야한다.)
DB 커넥션을 제공하는 클래스가어떤 것인지 UserDao가 구체적으로 알고 있어야 한다.

이에 대한 근본적인 문제의 원인은 UserDao가 바뀔 수 있는 정보 즉 DB커넥션을 가져오는 클래스에 대해 너무 많이 알고 있기 때문이다. -> 확장성이 떨어진다.

1.3.2 인터페이스 도입

클래스를 분리하면서도 이런 문제를 해결할 수 있는 방법 !! -> 추상화 ( 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업)

인터페이스는 어떤 일을 하겠따는 기능만 정의해놓았기 때문에 인터페이스에는 어떻게 하겠다는 구현 방법이 나타아 있지 않다. (구현 클래스에게 달려있다) 기능!=구현

public interface ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}



public  class UserDao {
    private  ConnectionMaker connectionMaker;

    public UserDao() {
        this.connectionMaker =new DConnectionMaker();
    }
    ...
}

하지만 UserDao 클래스 생성자에서 인터페이스의 구현 클래스가 아직도 UserDao 코드 내에 존재한다.
초기에 한 번 어떤 클래스의 오브젝트를 사용할 것인지 결정하는 생서자의 코드는 제거되지 않았다.

1.3.3 관계설정 책임의 분리

위의 문제점을 해결하기 위해 UserDao를 사용하는 클라이언트에게 UserDao와 ConnectionMaker 의 구현클래스의 관계설정을 위임하는 방법을 사용한다.!

UserDao의 클라이언트 오브젝트가 바로 제 3의 관심사항인 UserDao와 ConnectionMaker 관계를 결정해주는 기능을 분리해서 두기에 적절한 곳이다.

오브젝트와 오브젝트 사이의 관계를 설정해주어야한다.
오트젝트 사이의 관계는 런타임 시에 한쪽이 다른 오브젝트의 주소를 갖고 있는 방식으로 만들어진다.

UserDao 오브젝트가 ConnectionMaker를 구현한 클래스의 오브젝트를 사용하게 하려면 두 클래스의 오브젝트 사이에 런타임 사용관계 또는 링크, 또는 의존관계 라고 불리는 관계를 맺어주면 된다.

사용자 (UserDaoTest)는 UserDao 와 ConnectionMaker 구현 클래스의 런타임 오브젝트 의존 관계를 설정하는 책임을 담당한다.


public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        UserDao dao =  new UserDao(new DConnectionMaker());
        
        ...
      	
    }
}

   private  ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker =connectionMaker;
    }

1.3.4 원치과 패턴

  1. 개방 폐쇄 원칙 (Open - Closed Principle)
    : 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.

UserDao는 DB 연결 방법이라는 기능을 확장하는데 열려있다.
UserDao 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지 않고 유지할 수 있도록 변경에는 닫혀있다.

객체지향 설계 원칙(SOLID)

  • SRP(The Single Responsibility Principle) : 단일 책임 원칙
  • OCP(The Open Closed Principle) : 개방 폐쇄 원칙
  • LSP(The Liskov Substitution Principle): 리스코프 치환 원칙
  • ISP(The interface Segregation Principle) :인터페이스 분리 원칙
  • DIP(The Dependency inversion Principle): 의존관계 역전 원칙
높은 응집도와 낮은 결합도 p 87
  1. 전략 패턴
    개선한 UserDao 구조를 디자인 패턴의 시각으로 보면 전략패턴에 해당한다
    자신의 기능에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 분리시키고 이를 구현한 궤적인 알고리즘ㅇ 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴

UserDao는 전략 패턴의 컨텍스트에 해당한다. 컨텍스트는 자신의기능을 수행하는데 필요한 기능 중 변경 하능한 DB연결 방식 알고리즘을 인터페이스로 정의하고, 이를 구현 클래스 즉 전략을 바꿔가면서 사용할 수 있게 분리했다.

컨텍스트(UserDao) 를 사용하는 클라이언트 (UserDaoTest) 는 컨텍스탁 사용할 전략(ConnectionMaker를 구현한 클래스)을 컨텍스트의 생성자 등을 통해 제공해주는게 일반적이다.

응집력이 높고, 결합도가 낮다

1.4 제어의 역전 (Inversion Of Control)

오브젝트 팩토리

UserDaoTest는 기존의 USerDao가 직접 담당하던 기능을 떠밭았다.
하지만 원래는 USerDao의 기능이 잘 동작하는지를 테스트 하려고 만든 것이다.
지금은 다른 책임까지 맡고 있으니 문제가된다.
USerDao와 ConnectionMaker 구현 클래스의 오브젝트를 만들어주는 것과,
그렇게 만들어진 두 개의 오브젝트가 연결되어 사용할 수 있도록 관계를 맺어주는 것으로 분리하자.

팩토리

객체의 생성방법을 결정하고 , 그렇게 만들어진 오브젝트를 돌려주는 클래스를 새로 정의, 이런 일을 하는 오브젝트를 팩토리라고 부른다.

public class DaoFactory {
    public UserDao userDao(){
        ConnectionMaker connectionMaker = new DConnectionMaker();
        return  new UserDao(connectionMaker);
    }
}

public class UserDaoTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        UserDao dao =  new DaoFactory().userDao();

USerDao와 ConnectionMaker는 각각 앱의 핵심적인 데이터 로직과 기술 로직을 담당하고 있고,
DaoFactory는 이런 앱의 오브젝트를 구성하고 그 관계를 정의하는 책임을 맡고 있다.

전자가 실질직언 로직을 담당하는 컴포넌트, 후자는 앱을 구성하는 컴포넌트의 구조와 관게를 정의한 설계도 역활을 한다고 한다.

DaoFactory를 분리했을 때 얻을 수 있는 장점 중 하나는, 애플리케이션의 컴포넌트 역활을 하는 오브젝트와 애플리케이션 구조를 결정하는 오브젝트를 분리했다는데 가장 의미 있다.

1.4.2 오브젝트 팩토리의 활동

DaoFactory UserDao 말고 다른 Dao의 생성 기능을 넣었다고 해보자. 단순하게 다른 Dao 생성 메서드에다가 ConnectionMaker의 구현 클래스의 인스턴스 만드는 부분을 적어노면 코드의 중복성으로 좋지 않다.

그래서 ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 코드를 별도의 메소드로 뽑아낸다.

public ConnectionMaker connectionMaker(){
	return new DConnectionMaker();
}

1.4.3 제어권의 이전을 통한 제어관계 역전

간단히 프로그램의 제어 흐름 구조가 뒤바끼는 것
일반적으로 흐름이라하면 모든 오브젝트가 능동적으로 자신이 사용할 클래스를 결정하고, 언제 어떻게 그 오브젝트를 만들지 스스로 관장한다. 모든 종류의 작업을 사용하는 쪽에서 제어하는 구조이다.

제어의 역전은 이런 제어의 흐름의 개념을 거꾸로 뒤집는 것

제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다.
모든 제어 권한을 자신이 아닌 달ㄴ 대상에게 위임한다.

제어의 역전 개념이 적용된 예시는 템플릿메서드
추상 UserDao를 상속한 서브 클래스는 getConnection()을 구현한다. 하지만 이 메서드가 언제 어떻게 사용될지는 서브클래스 자신은 모른다., 단지 기능을 구현해놓으면 , 슈퍼클래스에서 템플릿메서드인 add, get등에서 필요할 때 호출해서 사용하는 것이다.

즉 제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할때 호출되어 사용되도록 하는 것,, 이것이 제어의 역전 개념이다

(프레임워크도 제어의 역전 개념이 도입 되어있음)

UserDao ,DaoFactory 에도 제어의 역전이 적용됨.

ConnectionMaker의 구현 클래스를 결정하고 오브젝틀를 만드는 제어권을 UserDao에서 DaoFactory로 넘어감.

0개의 댓글