• 현재 Java Bean이라고 부르는 것
    • 일부 자바빈 규약 정도를 지키는 것을 말함.
    • default 생성자 ( zero parameter consturctor) 를 갖고 있어야 한다 → 툴이나 프레임워크에서 reflection을 이용해 object를 생성하기 때문.
    • property 는 setter나 getter를 이용해 access할 수 있을 것. ( 하지만 Entity의 경우 setter를 오픈하는 것은 좋지 않다. getter도 무조건은 좋지 않다 )

 

객체지향 "기술 "은 어떤 것을 말하는 걸까? ( 객체지향적 개념 말고, 기술! ) 

  • ==> 변화에 효과적으로 대처할 수 있는 것.

 

변화에는 대비한다는게 무슨 말이지??

  • 변화의 폭을 최소한으로 줄여주는 것. ( 코드 여기저기서 변화가 일어난다면 변화의 폭이 넓은 것 을 의미 한다고 보면 된다 )
    • 어떤 변화가 일어났을 때, “변화하는 부분” 이 있고 “변하지 않는 부분” 이 있다. 는 것을 인식하는 것에서 시작한다.
    • 변경에 따라 “변화하는 부분” → 관심사가 같은 부분임을 의미한다 ⇒ 이들을 한 곳에 모아두고, “변화하지 않는 부분”은 따로 떨어트려주는 식으로 “관심사를 분리” 시킬 수 있을 것.

 

관심사의 분리

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

 

서로 다른 관심사가 섞여있다면 어떤 일이 일어날까?

  • 갖가지 변화가 일어날 때 마다, 코드 “여기 저기에서” 변경이 생겨버림 ( a관심사에 의한 변화, b 관심사에 의한 변화 .. .)
  • 코드의 중복이 “여기 저기에서” 나타난다. ( 모듈화를 시키지 않았기 때문임. 모듈화는 결국, 관심사를 분리시켜 놓는 것이니까 )
  • A 관심사의 변경이 → B 관심사 코드에도 영향을 주는 것이 된다. 서로 영향을 주게 된다.

 

코드를 수정한 후에는, 기존의 기능이 잘 동작하는가를 보장하기 위한 “테스트가 존재” 해야 한다.

관심사의 분리에서 가장 쉬운 것으로는

  • 중복 코드의 메소드를 추출
    • 어떤 장점이 있나 ?
      • → A 관심사 관련된 내용이 변경되면, 이 메소드 코드만을 수정하면 된다.
      • 다른 기능에는 영향을 주지 않으면서, 코드의 구조만 변경.

Refactoring에서 extract method

  • 공통의 기능을 담당하는 메소드로, 중복된 코드를 뽑아내느 것.

Refactoring

  • 외부의 동작 방식에는 변화 없이, 내부 구조를 변경해 재구성하는 작업.
  • 설계를 개선하여, 이해하기 더 편하고, 변화에 효율적으로 대응할 수 있는 코드를 만드는 것.
    • 즉, 생산성을 올리는 것.
  • 상속을 통한 확장이 가능하도록 추상화
    • 변화가 일어날 수 있는 부분을 “ 분리 + abstract method 로 ” 선언 ( class 역시 abstract class 가 되겠지 )
      • sublcass 마다 구현 ⇒ 손쉽게 “확장” 이 될 수 있다.
      • abstract method를 사용하는 측에서는 변화가 없을 수 있게 됨.
public abstract class UserDao{
	public void add(User user) throws ClassNotFoundException , SQLException{
		Connection c = getConnection();
		.... 구현
	}
	public User get(String id) throws ..{
		Connection c = getConnection();
		...구현
	}
	public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
}
public class NUserDao exctends UserDao{
	public Connection getConnection() throws ..{
		// "N사 DB connection" 생성코드
	}
}

디자인패턴?

  • s/w 설계시 “특정 상황에서, 자주 만나는 문제” 를 해결하기 위해 사용할 수 있는 “재사용 가능한 솔루션” 을 말한다.
  • 모든 패턴에는 이름이 있다. → 간단히 패턴이름을 언급하는 것 만으로도, “설계의 의도,해결책”을 “함께 설명할 수 “ 있다. ( 장점)
  • 주로 객체지향 설계에 관한 것. 대부분, 객체 지향적 설계원칙을 이용해 문제를 해결한다.
    • 객체 지향적인 설계로부터 문제를 해결하기 위해 적용할 수 있는 “확장성 추구 “ 를 하기 때문에, 패턴의 설계구조는 대부분 비슷한 느낌을 가짐.

 

  • 확장성 추구 방법은 대부분
    • 1️⃣클래스 상속
    • 2️⃣오브젝트 합성 (composition) ( 보통 composition을 사용하는 것을 지향함. 상속을 통한 확장도 가능은 한데, 확장성과 유연한 설계가 되는가는 개발자의 능력에 의존도가 높다. )
    으로 정리된다.

 

  • 패턴에서 가장 중요한 것은, 각 패턴의 핵심이 담긴 “목적 or 의도”다.
    • 패턴을 적용할 상황, 해결할 문제, 솔루션의 구조, 각 요소의 역할, 그리고 “핵심 의도” 를 기억해둬야 한다.

템플릿 메소드 패턴

  • 슈퍼 클래스에 “기본적인 로직의 흐름”) 을 만들고**, 기능의 “일부를 추상메소드 or 오버라이딩 가능한 protected method” 등으로 만든 뒤 “ 서브 클래스에서 이를 구현”** 하도록 하는 방법
    • 슈퍼클래스 add메소드 : 커넥션을 얻어오고, 사용자를 data로 추가하는 기본적인 로직을 작성해놓음 → 하위클래스에서는, getConnection 을 구현하기만 해 주면 된다.
  • “상속” 을통한 확장 방법.
    • 변하지 않는 기능 “ → 슈퍼클래스에
    • 자주 변경, 확장될 기능” → 서브클래스에
    • 슈퍼클래스 : 변경될 기능을 추상메소드 or 오버라이드 가능한 메소드로 정의해두고, 이를 이용해 코드의 기본 알고리즘을 담고 있는 템플릿 메소드를 만든다.
      • 이 때, 슈퍼클래스에, 추상메소드가 아닌, “디폴트 기능을 정의 or 비워두고는 서브클래스에서 선택적으로 오버라이드 하도록” 만들어놓은 메소드는 ⇒ hook 메소드라고한다.
      • 서브클래스에선 추상메소드 or hook 메소드를 오버라이드하여 ⇒ 일부 기능을 확장 가능.
    public abstract class Super{
    	public void templateMethod(){
    		// 기본 알고리즘 코드
    		hookMethod();
    		abstractMethod();
    	}
    	protected void hookMethod(){..}
    	public abstract abstractMethod();
    	}
    }
    

팩토리 메소드 패턴

  • 현재처럼, 하위클래스에서,어떤 Concrete 객체를 생성할 것인지 결정” 하는 경우.
    • 서브 클래스에서, 구체적인 객체 생성방법을 결정하게 하는 것.
  • 얘도 결국, “상속을 통한 확장” 방법 ( 인터페이스를 사용한다고 하더라도, 결국 확장을 위해선 “상속을 하게” 끔 하고 있음.. )
    • 슈퍼클래스 코드에선, 서브클래스에서 구현할 메소드를 호출해, 필요한 타입의 객체를 가져와 사용한다.
    • 이 때, 주로 “인터페이스 타입”으로 객체를 리턴, 슈퍼클래스에서 사용한다 ⇒ 슈퍼클래스에서는, 어떤 구현체의 인스턴스인지 알 필요가 없다. (관심x)
  • 팩토리 메소드 : 서브클래스에서 객체 생성 방법과, concrete class를 결정할 수 있도록 “미리 정의해둔 메소드”
  • 팩토리 메소드 패턴 : 팩토리 메소드를 통한 객체 생성 방법을, 나머지 로직 ( 슈퍼클래스의 기본코드)으로부터 독립시키는 방법

자바에서종종 객체 생성 기능을 가진 메소드를 “팩토리 메소드라고 부르” 기는 하는데, 이건 “팩토리 메소드 패턴의 팩토리 메소드” 와는 의미가 다르다!!! ( 그냥 단순히, 메소드 추출을 해 놓은 것 뿐, 하위클래스에서 구체 클래스를 결정하도록 위임해 놓은게 아니라는 말인 듯 )


템플릿메소드 패턴, 팩토리 메소드 패턴의 단점 → “상속” 을 사용하는 것.

  • Java는 class의 다중상속을 허용하지 않는다.
  • 만약, 커넥션 객체를 가져오는 방법을 분리하기 위해 상속구조로 만들어버리면, 다른 목적으로 UserDao에 상속을 적용하기 힘들어진다.
  • 그리고, “상속”을 통한, 상하위 클래스의 관계는 “긴밀한 결합” 을 하는 편임.
    • 서브클래스는 슈퍼 클래스의 기능을 직접 사용할 수 도 있다. ⇒ 슈퍼 클래스 내부의 변경이, 모든 서브클래스에 영향을 줄 수 도 있다.
    • 사실 이는, final 키워드, 접근제한자( private ) 등을 사용하여, 슈퍼클래스의 데이터, 행동을, 서브클래스에서 사용하지 않고, 오버라이딩 또한 불가하도록 제한을 가할 수는 있는 것으로 알고있음.  ( 하지만, 일단, 상속은 긴밀한 결합을 하는 코드로 작성되어있는 경우가 많겠지 ? ) 
  • 상속을 통한 확장을 하다보니, 그 확장기능을 , "다른 Dao 클래스"에는 적용할 수가 없다.
    • ex) ADao 에서는 Aconnection방법을 정의해놓음. BDao에서 Aconnection 방법 사용하고 싶으면, 다시 정의해야함.. ⇒ 코드의 중복 발생

DAO의 확장

앞서, 어떤 변화가 일어났을 때, 나머지 부분에서 “변화하는 부분” 과 “변화하지 않는 부분” 이 있다고 했었다. 변화의 “이유, 시기, 주기” 등이 다른 부분들이 존재하는 것이다.

이렇게 변화의 성격이 다른 것을 분리해, 서로 영향을 주지 않은 채로, 각각 “필요한 시점에 독립적으로 변경” 하는 것이 필요하다. 따라서 앞서 “상속을 통한 확장” 등을 한 것이다.

하지만 “상속”을 사용하는 것은 여러가지 단점이 있었다.

클래스의 분리

앞서, getConnection() 이라는 메소드 추출 && 상속을 통한 확장을 하던 방식이 아닌,

Connection을 생성하는 로직을, 아예 다른 class로 빼놓을 수가 있다. DB connection 생성 기능을 독립 시킨 클래스를 생성하는 것이다.

✋✋ 이런 리팩토링을 할 때 주의

  • 기능에 변화가 없는지 확인해야한다.
  • 이를 위해, 테스트가 필요하다.

단순히 SimpleConnectionMaker 라는 “클래스로만 분리” 한다면, 이를 사용하는 UserDao 측의 코드는, connection로직과 관련한 변경이 일어날때 영향을 받지 않는게 될까???

아니다. 현재처럼 코드를 작성한다면,

  • 사용자 측에서 “어떤 클래스를 사용할지 직접 결정” 하여 “생성” 하고, “특정 클래스에 종속” 된다.
    • UserDao 코드가 “특정클래스에 종속” 되어있다.
      • SimpleConnectionMaker를 “상속을 통해 확장” 하더라도, 그 하위클래스를 사용하려면, new 하위클래스(); 로 변경 하는 등 UserDao코드를 변경하는 게 필요하다. ⇒ 즉, 기존처럼, UserDao “클래스”만 공급하고, 업체마다, 상속을 통해 DB 커넥션 기능을 확장해 사용하게 하던 것이 불가능해짐. UserDAO 소스 코드를 함께 제공하지 않고는, DB 연결 방법을 바꿀 수 없어짐.
    • Connection을 사용하는 측(UserDAO)에서는 어떤 구체 클래스를 사용하는지 알고 있어야 하게 되었다.
public class UserDao{
	private SimpleConnectionMaker simpleConnectionMaker;
	public UserDao(){
		simpleConnectionMaker = new SimpleConnectionMaker(); //  new SimpleSubConnectionMaker();
	}
}

그래서 Interface가 필요하다.

인터페이스 도입

앞서 상속의 사용은 여전히 객체간에 “상당히 긴밀한 결합”을 하게끔하고 있었다.

긴밀한 연결을 느슨하게 하기 위해서는 “ 중간에 추상화” 가 필요하다. 추상적인 연결고리를 만들어 주는 것 이다.

Java에서 이 역할을 하는 것이 Interface다.

  • 인터페이스는, 자신을 구현한 구현체의 정보는 감춰버린다. ⇒ 인터페이스 타입을 사용하는 측에서는, 해당 타입에 들어오는 구체 클래스에 대해서는 알 필요도 없고, 인터페이스를 통해서 원하는 기능을 사용하기만 하면된다.
    • 인터페이스의 메소드를 통해 알 수 있는 기능에만 관심을 가지면 됨.
    • 기능이 어떻게 구현되었는지는 괌심 x.
  • 구체 클래스에서, 구현 방법을 결정한다.
public interface ConnectionMaker{
	public Connection makeConnection() throws ClassNotFoundException,SQLException;
}

 

ConnectionMaker를 구현하는 구현체에서 ,독자적인 방법으로 Connection을 생성한다.

사용자는 ConnectionMaker 타입의 객체를 사용하여 , 생성된 Connection을 받아올 수 있다.

public class UserDao{
	private ConnectionMaker connectionMaker;
	public userDao(){
		connectionMaker = new DConnectionMaker();
	}
	public void add(User user){
		Connection c = connectionMaker.makeConnection();}}
  • 하지만 여전히 문제가 남아있다 → new DConnectionMaker();

  • 터페이스를 사용했음에도 여전히 사용자측에서, Concrete class의 생성자를 호출하고 있다.
    • “어떤 클래스의 객체를 사용할지 결정하는(관심사항) 생성자 코드” 가 남아있다.
  • 사용자측에서는 “구체클래스를 알 필요가 없도록 만들어야 “ 한다.

→ 어떤 클래스의 객체를 사용할지 결정하는(관심사항) 을 분리해야한다

어떻게 분리할까???

관심사항( 인터페이스의 구체클래스 결정 및 생성) 의 분리

“***UserDao의 클라이언트(제3자)”***에서 ConnectionMaker 타입의 구체 클래스 객체를 생성하여 UserDao에 주입해주면 어떨까?

이렇게 한다면 UserDao 객체 와 ex)DConnectionMaker 객체 사이에 관계가 설정된다.

  • 이는 Class 사이의 관계가 ❌
    • Class 사이의 관계로는 : UserDao - ConnectionMaker(인터페이스) 사이의 관계.
  • 객체 사이의 관계 ⭕
    • 객체 사이의 관계는, 런타임시 , 한쪽이 다른 객체의 참조값을 갖는 방식으로 만들어진다.
    • 직접 생성자를 호출 하거나, 외부에서 만들어진 객체를 가져오는 방법도 있다.

외부에서 주입을 받는다면, UserDao에서는 인터페이스타입의 참조변수를 사용하면서, 실제로 어떤 구현체가 들어오는지에 대해 신경쓰지 않아도 되게 된다.

  • 이러한 구현을 할 때는, 실수로라도, UserDao 측에서, 구체 클래스를 직접 생성하여, 직접적인 관계가 맺어지지 않도록 하는 것에 주의하자.
  • 이러한 관계는 “코드상으로는 보이지 않는 관계” 가 “객체 생성 후에 만들어지는 것 “ ( 런타임시 주입 받으니 )

즉, 제3자인 클라이언트의 책임 : 이런 관계를 갖는 구조로 만들어주는 것

클라이언트의 책임

  • ConnectionMaker의 구현 클래스를 선택
  • 선택한 클래스의 객체를 생성
  • UserDao와 해당 객체를 연결

만약 이러한 것을 UserDao의 관심사에 포함시켰다면 → 확장성이 떨어졌을 것.

분리된 모습

UserDao에서는 생성자를 통해, 외부에서 주입받도록 하였다.

  • interface 타입의 참조변수를 사용 ( 실제 어떤 구현체가 들어오는지는 관심사가 ❌)
public UserDao(ConnectionMaker connectionMaker){
	this.connectionMaker - connectionMaker;
}

Client에서는 다음과 같이 할 수 있다

public class UserDaoTest{
	public static void main(String[] args) {
		ConnectionMaker connectionMaker = new DConnectionMaker(); //구체 클래스 결정 및 생성
		UserDao dao = new UserDao(connectionMaker);
	}
}

만약 UserDao에서 NConnectionMaker()를 사용하도록 하고 싶은 경우라면 Client측의 코드만 변경하면 된다.

  • 더이상 DB 커넥션을 가져오는 방법을 변경하는 것에 의해 UserDao 코드에 변경이 생기지 ❌

 

 

 

 

 

참조

 

(책)토비의 스프링

 

https://tecoble.techcourse.co.kr/post/2020-05-18-inheritance-vs-composition/

 

상속보다는 조합(Composition)을 사용하자.

tecoble.techcourse.co.kr

https://bperhaps.tistory.com/entry/%EC%BA%A1%EC%8A%90%ED%99%94%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%96%B4%EB%96%A4-%EC%9D%B4%EC%A0%90%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80

 

캡슐화란 무엇인가? 어떤 이점이 있는가?

객체지향을 공부하면 처음부터 귀에 못이 박히도록 듣는 단어가 있다. 바로 캡슐화다. 캡슐화가 도대체 뭐고, 이게 왜 중요한 요소일까? 일단 위키를 보도록 하자 캡슐화(영어: encapsulation)는 객

bperhaps.tistory.com

https://livenow14.tistory.com/33

 

[Java] 상속은 캡슐화를 깨뜨린다?

public class Car{ private final String name; Car(String name) { this.name = name; } public final String getName() { return name; } } public class Avante extends Car{ Avante(String name) { super(name..

livenow14.tistory.com

 

' > 토비의스프링' 카테고리의 다른 글

ch02 : 테스트의 필요성  (0) 2022.02.20
DI , 왜 생성자 주입을 하라는 걸까?  (0) 2022.02.16
02. 원칙과 패턴  (0) 2022.02.13
복사했습니다!