728x90

오브젝트 팩토리

팩토리

객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 일을 하는 오브젝트

public class UserDaoFactory {
	public UserDao userDao() {
		UserDao dao = new UserDao(connectionMaker());
		return dao;
	}

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

팩토리는 ConnectionMaker 메서드를 통해 어떤 커넥션 메이커를 사용할지 정하고

UserDao 메서드를 사용하여 최종적으로 UserDao 오브젝트를 리턴한다.

public class UserDaoTest {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		UserDao dao = new UserDaoFactory().userDao();
		//기존의 코드
	}
}

팩토리로부터 생성된 UserDao를 통해 기존과 같이 작업을 처리한다.

이렇게 코드를 작성하면 기존에 Test 클라이언트에서 오브젝트를 생성하고 전달하던 작업을

팩토리에 넘겨서 클라이언트는 온전히 자신의 역할만 수행하게 된다.

설계도와 팩토리

UserDao와 ConnectionMaker는 애플리케이션의 핵심 로직들을 담당하는 컴포넌트라고 볼 수 있고,

팩토리는 이런 오브젝트들을 구성하고 관계를 정의하는 설계도 같은 역할이다.

 

이렇게 팩토리를 통해 컴포넌트 오브젝트와 구조 오브젝트를 분리하여

컴포넌트 오브젝트들의 코드를 안전하게 보존할 수 있고 자유로운 확장이 가능해졌다.

제어의 역전

말그대로 애플리케이션의 제어 흐름 구조가 역전된다는 것인데

기본적인 애플리케이션의 실행 구조는 각 오브젝트가 자신이 사용할 클래스를 결정하고,

어떻게 오브젝트를 만들지 스스로 참여하는, 작업을 사용하는 쪽에서 제어하는 구조라면

제어의 역전은 이러한 기본 실행 구조를 뒤집는 것이라고 볼 수 있다.

 

즉, 오브젝트 스스로 자신이 사용할 오브젝트를 선택하지 않고 생성하지도 않고,

모든 제어 권한을 자신이 아닌 다른 대상에게 위임하는 것이다.

 

라이브러리는 개발자가 필요에 따라 능동적으로 추가하여 사용하지만

프레임워크는 개발자가 능동적으로 사용하는 것이 아닌 끌려가는 구조라고 볼 수 있고,

이를 제어의 역전이 적용되었다고 생각할 수 있다.

 

팩토리를 통해 UserDao를 생성하는 것도 제어의 역전이 적용된 것이다.

UserDao가 스스로 오브젝트를 생성하고 관리하는 것이 아닌 팩토리에 의해 관리되기 때문이다.

'Back-End > Spring' 카테고리의 다른 글

싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
초난감 DAO 리팩토링하기  (0) 2023.06.05
Spring Framework  (0) 2023.06.05
728x90

SOLID

객체 지향 설계의 다섯가지 원칙을 뜻하는 용어

 

SRP(Single Responsibility Principle): 단일 책임 원칙

OCP(Open-Closed Principle): 개방 폐쇄 원칙

LSP(Liskov Subsitution Principle): 리스코프 치환 원칙

ISP(Interface Segregation Principle): 인터페이스 분리 원칙

DIP(Dependency Inversion Principle): 의존관계 역전 원칙

개방 폐쇄 원칙 (OCP, Open-Closed Principle)

클래스나 모듈의 확장은 열려있고 변경은 닫혀 있어야 한다는 원칙으로

클래스나 모듈의 로직의 확장이 필요할 때 확장은 가능하지만,

이러한 확장의 결과가 다른 클래스에 영향을 주어서는 안된다는 것이다.

 

즉, 인터페이스를 통하여 클래스 간의 관계를 느슨하게 유지하여

핵심 기능의 코드에는 영향을 주지 않고 확장할 수 있게 하는 것을 말한다.

높은 응집도와 낮은 결합도

응집도가 높다는 것은 모듈과 클래스가 하나의 책임이나 하나의 관심사에만 집중되어 있다는 것

결합도가 낮다는 것은 모듈이나 클래스 간의 관계가 느슨하게 연결되어 있다는 것 (독립적)

 

결합도가 낮을 수록 구성이 깔끔하고, 변화와 확장이 빠르고 편리하다.

전략 패턴

자신의 기능 맥락(컨텍스트)에서 변경기 필요한 알고리즘(로직, 기능)을 인터페이스를 통해 외부로 분리시키고,

이를 구체적으로 구현한 알고리즘 클래스를 상황에 맞게 사용하게 하는 디자인 패턴

 

UserDao를 해당 전략 패턴의 컨텍스트라고 볼 수 있는데, 인터페이스를 통해 커넥션 기능을 분리하고

이를 구체적으로 구현한 클래스를 클라이언트를 통해 상황에 맞게 사용하기 때문이다.

'Back-End > Spring' 카테고리의 다른 글

싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO 리팩토링하기  (0) 2023.06.05
Spring Framework  (0) 2023.06.05
728x90

초난감 DAO

DAO (Data Access Object)

데이터베이스를 사용해 데이터를 조회 및 조작하는 기능을 전담하는 오브젝트

 

JavaBean (Bean)

비주얼 틀에서 조작 가능한 컴포넌트

파라미터가 없는 디폴트 생성자를 갖고 있어야 함

Getter와 Setter를 통해 빈을 수정/조회 할 수 있어야 함

 

그냥 캡슐화된 객체라고 생각하면 편함

기존 JDBC를 활용한 초난감 DAO의 문제점

public class UserDao {
	public void add(User user) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.cj.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", "DB계정명",
				"비밀번호");

		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/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", "DB계정명",
				"비밀번호");
		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;
	}

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

		User user = new User();
		user.setId("test");
		user.setName("한글");
		user.setPassword("abcd");

		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() + "조회성공");
	}

}

간단한 SQL CRUD 작업을 수행하는 코드이다.

 

초난감이라는 말에서 알 수 있듯이 DAO 코드가 많이 난감하다는 것인데

코드가 긴 것도 문제지만 코드의 유지보수성이 떨어진다.

 

객체지향설계는 유연하고 확장성 있는 코드로 미래를 생각하며 작성하는 것이 중요한데

초난감 DAO는 이러한 객체지향설계에 맞지 않는 방식이다.

객체지향적으로 해결하기 : 분리

기능이 분리가 되어있지 않다면

사용하는 데이터베이스가 바뀌거나 사용자 정보가 바뀌는 경우 등

기존에 코드들을 하나하나 모두 수정해야 하는 경우가 발생

 

관심이 같은 것들끼리만 모으고 같지 않는 것들은 떨어트려

서로 영향을 주지 않도록 분리하여 코드를 작성해야 함

 

DAO 코드에 어떤 기능들이 있는지를 살펴보고

관심사항을 추출해야 한다.

객체지향적으로 해결하기 : 관심사항 추출

DAO 코드의 관심사항은 아래와 같이 크게 3가지가 있다.

  1. 데이터베이스 연결에 사용할 커넥션 가져오기
  2. 데이터 추가 및 조회에 사용되는  SQL Statement 생성 및 실행
  3. 커넥션과 Statement 오브젝트 종료

또한 해당 관심사항들은 add와 get 메서드에서 반복적으로 실행 되는 것도 알 수 있는데

DAO 작업을 계속 하다보면 이러한 중복되는 코드들이 갈수록 많아질 것이기 때문에

이 각각의 관심사항들을 하나씩 나눠서 재사용성을 높이는 코드를 작성해야 한다.

객체지향적으로 해결하기 : 중복 코드 추출

인텔리제이에서 기본적으로 제공하는 리팩토링 기능처럼

중복되는 코드들을 하나의 메서드로 추출할 것인데

Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = 
	DriverManager.getConnection(
		"jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", 
        "db 사용자 id","pw");

우선 add와 get 메서드에서 중복적으로 사용되는

커넥션을 가져오는 코드를 분리하여 하나의 메서드로 만든다.

private Connection getConnection() throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.jdbc.Driver");
    Connection c =
    	DriverManager.getConnection(
        	"jdbc:mysql://localhost/toby?characterEncoding=UTF-8&serverTimezone=Asia/Seoul", 
        	"db 사용자 id","pw");
    return c;
}

위와 같이 중복되는 부분을 하나의 메서드로 추출하여 해당 기능이 필요할 때만

해당 메서드를 호출하여 사용하도록 하는 것을 통해

public User get(String id) throws ClassNotFoundException, SQLException {
    Connection c = getConnection();
}

코드의 수정이 필요한 경우 해당 메서드의 코드만 수정하면 되기 때문에

코드가 간결해지고 유연해졌다.

객체지향적으로 해결하기 : 추출한 메서드의 독립 (상속)

해당 메서드의 코드를 공개하지 않고 상대방에게 제공하고 싶은 경우에는

상속을 사용하여 추상 메서드와 구현으로 분리하는 방법이 있다.

abstract protected Connection getConnection() throws ClassNotFoundException, SQLException;

이렇게 메서드를 독립시키면 하나의 getConnection 메서드를 상황에 맞게 작성된 코드로 사용할 수 있다.

 

즉, 기존의 mysql이 아니라 오라클이나 다른 데이터베이스를 사용해야 하는 경우에는

해당 추상 클래스를 구현한 다른 클래스의  getConnection 메서드를 사용하면 되는 것이고

새로운 데이터베이스를 사용해야 하는 경우 손쉽게 확장해서 사용할 수 있다.

템플릿 메서드 패턴

위와 같이 슈퍼 클래스에서 기본적인 로직의 흐름(변하지 않는 기능)만 제어하고

기능의 일부를 추상 메서드나 오버라이딩이 가능한 protected 메서드로 만들어

서브 클래스에서 실질적인 기능(자주 변경되거나 확장되는 기능)을 구현하여 상황에 맞게 사용하는 디자인 패턴

 

보통 서브 클래스에서 반드시 구현해야 하는 부분들은 추상 메서드로 만들고

선택적으로 구현해야 하는 것은 protected등의 접근 제어자를 통해 메서드를 오버라이딩 할 수 있게 한다.

팩토리 메서드 패턴

서브 클래스에서 구체적인 객체 생성 방법을 결정하게 하는 디자인 패턴

객체지향적으로 해결하기 : 상속의 문제점

하지만 상속을 통한 메서드의 독립은 간단하지만 좋은 방법은 아닌데,

자바는 다중 상속을 지원하지 않기 때문에 추가적인 상속을 받을 수 없고

상속을 통해 연결된 관계는 결합이 강하기 때문이다.

 

현재까지 중복되는 코드를 하나의 메서드로 분리하고

이를 다시 상하위 클래스로 분리하는 작업을 했는데,

이제는 다시 이 상하위 클래스를 서로 독립적인 클래스로 만들어야 한다.

객체지향적으로 해결하기 : 클래스의 분리

서로 독립적인 클래스로 만드는 것은 간단한데

그저 새로운 클래스를 하나 만들고 커넥션 관련 코드를 옮기는 것이다.

public class SimpleConnectionMaker {
	public Connection getConnection() throws ClassNotFoundException,
			SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/toby?characterEncoding=UTF-8", "id",
				"pw");
		return c;
	}
}

이렇게 별도의 클래스를 가져다 사용하기만 하면 된다.

public abstract class UserDao {
	private SimpleConnectionMaker simpleConnectionMaker;
	
	public UserDao() {
		this.simpleConnectionMaker = new SimpleConnectionMaker();
	}
}

SimpleConnectionMaker 클래스에서 커넥터를 가져오는 메서드를 사용하기 위해

SimpleConnectionMaker 객체를 가져와서 필드에 멤버로 등록하고

UserDao 클래스의 생성자를 통해 해당 인스턴스를 생성해준다.

Connection c = this.simpleConnectionMaker.getConnection();

만들어진 SimpleConnectionMaker 인스턴스를 통해 getConnection 메서드를 사용하면

기존에 상하위 클래스로 상속 받던 관계가 완전히 분리되었다.

객체지향적으로 해결하기 : 여전히 존재하는 문제점

하지만 클래스를 분리하니 새로운 문제가 생기는데

상속을 통해 메서드를 분리했을 때는 상황에 맞게 코드를 사용할 수 있었지만

지금은 UserDao 클래스가 SimpleConnectionMaker에 종속되었기 때문에 

새로운 데이터베이스를 사용하는 경우에 대한 커넥션 관련 코드들을 변경할 수 없다

객체지향적으로 해결하기 : 인터페이스

이러한 문제를 해결하기 위해 자바에서는 인터페이스를 사용할 수 있다.

 

인터페이스는 추상 메서드의 집합으로 다중 상속을 지원하지 않는 자바에서

다중 상속과 비슷한 역할을 수행할 수 있게 해준다.

 

공통적, 필수적으로 구현되어야 하는 기능들을 인터페이스에 정의하고

이 인터페이스를 사용하여 클래스들간 연결을 해주면서도 느슨한 관계를 유지시킬 수 있다.

 

즉, 기존에는 클래스들 간 밧줄로 강하게 묶여 있었다면 인터페이스를 통해 느슨하게 묶여 있는 것이다.

객체지향적으로 해결하기 : 인터페이스 적용하기

데이터베이스 커넥션 기능을 정의할 인터페이스를 생성 후

해당 인터페이스의 구현 클래스들이 구현해야 할 추상 메서드를 선언해준다.

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

해당 인터페이스를 통해 상황에 맞게 실행되어야 할 코드들을 각각의 클래스에 구현한다.

public class DConnectionMaker implements ConnectionMaker {
	public Connection makeConnection() throws ClassNotFoundException, SQLException {
		// 커넥션 생성 구현 코드
	}
}

UserDao에서는 인터페이스를 사용하여

public class UserDao {
	private ConnectionMaker connectionMaker;
	
	public UserDao() {
		ConnectionMaker = new DConnectionMaker();
	}
    
    Connection c = connectionMaker.makeConnection();
}

이렇게되면 해당 인터페이스의 구현 클래스들이 서로 달라도 해당 인터페이스를 통해

makeConnection이라는 메서드를 상황에 맞게 사용할 수 있다.

 

하지만 아직도 UserDao의 생성자를 살펴보면 특정 객체의 생성자를 호출하여 사용하고 있다.

이러면 여전히 코드를 수정해야 하는 경우 여러 부분을 똑같이 수정해야 하는 것은 변하지 않았다.

객체지향적으로 해결하기 : 책임 분리

여전히 코드가 독립적이지 못한 이유는

어떤 커넥션 구현 클래스를 사용할지 직접 결정해줘야하기 때문인데,

이러한 부분을 제거해줘야 독립적이고 확장 가능한 클래스가 될 수 있다.

 

즉, 어떤 커넥션을 적용할지 결정하는 책임을 분리해야하는데

이러한 책임을 분리하기 위해서는 다른 클래스에 책임을 넘기는 것이 좋다.

 

UserDao라는 클래스(서비스)를 사용하는 클래스(클라이언트)에 이러한 책임을 넘겨서

클라이언트가 서비스에 어떠한 기능을 적용하라고 지정해주는 것이다.

 

고객(클라이언트)이 주문을 하면 해당 주문에 맞는 음식을 가게(서비스)가 제공하는 것과 같다.

객체지향적으로 해결하기 : 책임 분리의 원리

이제 클라이언트 오브젝트와 서비스 오브젝트의 관계를 설정해주면 되는데

서비스 오브젝트는 메서드나 생성자의 파라미터를 통해 외부에서 오브젝트를 전달 받고

클라이언트 오브젝트는 서비스 오브젝트의 파라미터에 맞게 오브젝트를 전달해준다.

 

현재 클래스들은 직접적으로 연결 되어 있지 않고 인터페이스를 통하여 간접적으로 연결되어 있기 때문에

파라미터로 전달되는 오브젝트의 클래스는 해당 인터페이스의 구현 클래스면 어떤 것이든 상관이 없다.

 

클래스 사이에 관계가 생기는 이유는 코드에 다른 클래스가 등장하기 때문이니

코드에 다른 클래스가 직접적으로 등장하지 않게 만들면 클래스 사이의 관계가 생기지 않는다.

 

하지만 오브젝트 사이의 관계는 특정 클래스가 등장하지 않아도 해당 클래스가 구현한

인터페이스를 사용했다면, 해당 인터페이스 타입으로 받아서 사용할 수 있다. (다형성)

 

즉, 모델링 시점(코드를 작성하는 시점)에는 존재하지 않던 관계

런타임 시에 클라이언트 오브젝트에서 관계를 주입 받아 생기게 되는 것이다.

객체지향적으로 해결하기 : 책임 분리 적용하기

클라이언트 클래스를 새로 생성하여 UserDao 클래스에 있던 main 메서드를 옮긴다.

이때 클라이언트의 메인 메서드의 UserDao의 생성자에 어떤 커넥션을 적용할지 전달한다.

public class Client {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		ConnectionMaker connectionMaker = new DConnectionMaker();
		UserDao dao = new UserDao(connectionMaker);

		//기존의 main 메서드 코드
	}
}

UserDao의 생성자는 아래와 같이 구현 클래스를 직접 상속 받지 않고

클라이언트로부터 구현 클래스를 전달 받게 한다.

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

드디어 인터페이스를 통해 완벽한 독립적인 코드 작성이 끝났다.

이제는 만약 데이터베이스를 다른 플랫폼으로 바꾸고자 한다면 메인 메서드의

생성자 부분의 코드만 수정하면 된다.

'Back-End > Spring' 카테고리의 다른 글

싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
Spring Framework  (0) 2023.06.05
728x90

Spring Framework

자바의 엔터프라이즈 애플리케이션 개발을 위한 프레임워크

 

스프링 컨테이너(애플리케이션 컨텍스트)

스프링 프레임워크가 제공하는 런타임 엔진

설정 정보를 바탕으로 애플리케이션의 오브젝트의 생성/관리 담당

독립적으로 실행 가능하지만 서블릿 같은 것으로 등록하여 사용

공통 프로그래밍 모델

프레임워크는 개발을 위한 틀을 제공해준다고 볼 수 있는데

스프링 프레임워크도 IoC/DI,  PSA, AOP라는 개념을 통해

개발자가 어떻게 코드를 작성해야할지 방향성을 제시해준다.

IoC(Inversion of Control, 제어의 역전) / DI(Dependency Injection, 의존성 주입)

객체의 생명주기와 의존관계에 대한 스프링 프레임워크가 제공하는 프로그래밍 모델

객체지향 설계와 디자인 패턴의 핵심 원리를 담고 있으며

스프링 프레임워크가 제공하는 모든 기능도 마찬가지로 해당 프로그래밍 모델로 작성되었다.

 

스프링을 제대로 사용하기 위해서는 해당 프로그래밍 모델에 맞게 코드를 작성하고

해당 프로그래밍 모델을 완벽하게 이해하는 것이 중요

PSA(Portable Service Abstractions, 서비스 추상화)

추상화라는 표현이 포함된 것에서 알 수 있듯이 객체지향 프로그래밍의 추상화처럼

구체적인 기술과 환경에 종속되지 않게 유연한 추상 계층을 두는 프로그래밍 모델

AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)

반복적인 코드를 하나의 메서드로 리팩토링하여 재사용하는 것처럼

공통적으로 사용되는 부가적인 기능을 독립적으로 모듈화하는 프로그래밍 모델

기술 API

UI 작성, 웹 프레젠테이션 계층, 비즈니스 서비스 계층, 기반 서비스 계층, 도메인 계층, 데이터 액세스 계층 등

여러 곳에서 필요한 주요 기술을 일관된 방식으로 사용할 수 있게 기능과 전략 클래스 등을 제공

 

스프링 프레임워크만의 가치

단순함

스프링은 목적을 이룰 수 있는 가장 단순하고 명쾌한 접근 방법을 지향하는 프레임워크로

이를 위해 단순한 객체지향적 개발 모델 POJO 프로그래밍을 사용

유연성

스프링은 유연성과 확장성이 매우 뛰어난 프레임워크로

다른 프레임워크들을 접목시켜 사용하는 것에 있어서 편리하고

개발자의 필요에 따라 스프링 프레임워크를 확장해서 사용할 수도 있다.

 

스프링 프레임워크를 공부하는 방법

핵심 가치와 원리 이해

스프링이 지향하는 핵심 가치를 이해하고

핵심 가치가 어떻게 적용되었는지 이해하기

 

POJO, IoC/DI, PSA, AOP

스프링의 기술 지식과 기술 선택 기준

스프링이 제공하는 기술의 종류와 접근 방법

상황에 맞는 기술과 접근 방법 선택에 대한 기준 마련에 대해 학습

적용과 확장

실제 애플리케이션에서 사용하기 위한 스프링의 응용과 확장 방법 공부

'Back-End > Spring' 카테고리의 다른 글

싱글톤 레지스트리 / 오브젝트 스코프  (0) 2023.06.07
스프링 IoC  (0) 2023.06.05
IoC(Inversion of Control, 제어의 역전)  (0) 2023.06.05
초난감 DAO와 객체지향  (0) 2023.06.05
초난감 DAO 리팩토링하기  (0) 2023.06.05

+ Recent posts