단순히 프라이빗한 데이터를 직접 데이터를 직접 접근하지 못하게 하기 위해 사용하는 것이 아닌 내부적인 기능에 초점을 맞춰야 한다. 단순히 데이터를 접근하기 위한 용도로 캡슐화를 사용한다면 객체에 대한 책임이 분산될 수 있다.
캡슐화를 하기 위한 두가지 규칙
묻지 말고 시켜라
참조한 객체의 데이터에 대한 로직이 필요할 경우에 직접 구현하지 말고 구현된 데이터를 요청하여 데이터에 대한 책임을 전가 시킬 수 있다.
데미테르의 법칙
1 뎁스만 사용할 뿐 2뎁스로 사용하지 말아야 한다. 이것은 묻지 말고 시켜라의 연장선으로 참조하는 객체가 가지고 있는 데이터를 참조하는 사용자 입장에서는 알 필요가 없고 데이터를 사용하여 데이터의 내부 기능을 사용하게 되면 내부 기능의 변화에 범위가 외부에도 노출되어 의존성이 낮아진다.
자식 클래스가 부모클래스를 상속받아 설계된 구조를 일컫는다. 상속으로 이루어진 관계에서는 부모 클래스의 멤버 변수나 메서드를 사용할 수 있다.
자식 클래스는 부모 클래스의 모두 접근이 가능한가?
모두 접근이 가능한 것은 아니다. 부모 클래스의 멤버 변수나 메서드의 접근자가 private가 아닌 public 이나 protected일 경우메나 사용할 수 있다.
protected 접근자는 왜 사용할까?
우선, public 과 protected의 차이를 살펴보자
public 접근자는 외부에 노출이 가능하다.
public 접근자는 자식 클래스가 접근할 수 있다.
protected 접근자는 외부에 노출되지 않는다.
protected 접근자는자식 클래스가 접근할 수 있다.
두 접근자 모두 자식 클래스에게 노출이 되지만 외부에 노출 여부에 차이가 있다.\
변경 가능성이 열려있다는 것을 암시
상속받은 자식 클래스에서 구현을 완성시켜야 한다는 것
상속은 언제 사용해야 할까?
SOLID 원칙의 리스코프 치환 원칙을 통해서 해답을 얻을 수 있다.
리스코프 치환 원칙은 상속받은 자식 클래스는 부모 클래스를 대체할 수 있는 경우에만 상속을 해야한다고 명시하고 있다.
자식 클래스가 부모 클래스를 대체할 수 있는 경우는 부모 클래스의 외부로 노출되는 메서드를 자식 클래스에서도 같은 의미로 제공되어야 한다는 것을 의미함. → IS-A 관계가 성립
상속으로 얻을 수 있는 장점은 무엇이 있을까?
상속 관계에서는 외부로부터 다형성을 보장하면서 클래스 내부 구현 코드를 모두 구현하지 않고 공통된 로직을 그대로 사용할 수 있다.
클래스 타입에 따라 변경되는 로직만 일부분 구현하면 된다는 것이 장점
하지만 이러한 장점도 java8 부터 인터페이스의 디폴트 메서드 기능이 나오면서 인터페이스내에서 로직 구현이 가능하여 상속의 장점이 약화되었다고 할 수 있다.
추상 클래스는 왜 사용할까?
추상 클래스는 protected 접근자를 사용하는 이유를 더 명확하게 나타내기 위해서 사용한다고 볼 수 있다.
부모 클래스에서는 변경되는 로직을 abstract로 정의하여 내부 로직은 구현하지 않고 상속받은 자식 클래스에서는 무조건 구현할 수 있게 만들어 변경되어야 할 부분과 변경되지 않을 부분을 더 명확히 구분할 수 있게 되었다.
컴파일 단계에서 자식 클래스가 abstract 메서드를 오버라이딩 하지 않을 경우에는 에러가 발생하므로 런타임 단계에서 발생할 수 있는 예외를 명확하게 확인할 수 있는 장점이 있다.
이러한 추상 클래스를 사용한 대표적인 사례가 템플릿 메서드 패턴이라고 할 수 있다. 템플릿 메서드 패턴은 외부로 노출되는 메서드는 그대로이나 내부에서 변경되는 로직만 별도로 추상화하여 외부에서 필요한 전략을 선택하여 내부 구현만 변경되도록 노출하는 디자인 패턴이다.
잘못된 상속 방법
어떤 이는 상속은 코드를 재사용 하기 위해 사용한다고 주장하기도 한다. 이는 전형적인 상속의 잘못된 사용 사례이다.
코드 재사용을 위해 상속을 하면 어떠한 단점이 있나?
캡슐화를 위반할 수 있다.
부모의 public 메서드는 외부에 노출하기 위한 용도로 사용된다. 그러나 자식 클래스에서도 부모 클래스의 public 메서드는 외부로 노출되기 때문에 자식 클래스에서 의도하지 않는 동작을 수반할 수 있게 되며 캡슐화를 위반하게 된다.
설계가 유연하지 않게된다.
상속으로 인해 결합도가 높아지면 다음과 같은 두 가지 문제점이 발생
하나의 기능을 추가하거나 수정하기 위해 불필요하게 많은 수의 클래스를 추가하거나 수정해야 한다.
단일 상속만 지원하는 언어에서는 상속으로 인해 오히려 중복 코드의 양이 늘어날 수 있다.
합성이란?
합성은 객체가 다른 객체의 참조자를 얻는 방식으로 런타임시에 동적으로 이뤄진다. 이는 보통 has-a 관계라고 일컫는다. 따라서 다른 객체의 참조자를 얻은 후 그 참조자를 이용해서 객체의 기능을 이용하기 때문에 해당 객체의 인터페이스만을 바라보게 됨으로써 캡슐화가 잘 이뤄질 수 있다.
합성은 언제 사용해야 할까?
구현 코드를 재사용하고 싶을 때 사용하면 유리하다. 또한 합성으로 사용된 코드는 사용하는 클래스에 따라 외부로 노출시킬 수 있고 내부로 캡슐화 할 수도 있어 클래스 특성에 맞게 캡슐화를 할 수 있다.
합성을 사용하고 인터페이스 타입을 사용한다면 런타임 시에 외부에서 필요한 전략에 따라 교체하며 사용할 수 있으므로 좀 더 유연한 설계를 할 수 있다.
대표적인 사례가 전략 패턴이다.
합성의 단점은?
합성은 객체 간의 관계가 수직관계가 아닌 수평관계가 된다.
따라서 큰 시스템에서 많은 부분에 걸쳐 합성이 사용될 때 객체나 메서드 명이 명확하지 않으면 코드가 가독성이 떨어지고 이해하기 어려워지게 된다.
합성을 사용할 시 용도에 따라 클래스들을 패키지로 분리하고 각각의 사용 용도가 명확하게 드러나도록 인터페이스를 잘 설계해야 한다.
결론
상속을 사용하고 싶다면 단순히 코드를 재사용 하는 용도가 아닌 부모 클래스를 대체할 수 있는 IS-A 관계인지 고려해야 한다. IS-A 관계에서도 변경되는 부분이 있다고 하면 protected 접근자나 abstract 를 사용하여 자식 클래스에게 명확하게 전달해야 한다.
단순히 코드를 재사용하고 싶다면 합성을 고려해보자 합성을 사용하면 코드 재사용도 가능할 뿐더러 캡슐화도 지킬 수 있다. 또한 다양한 전략에 따라 런타임시에 교체도 가능하여 유연한 설계가 가능하다. 단 합성을 하려는 클래스에 너무 많은 기능들이 정의되어 있거나 합성하는 인터페이스의 기능이 단일 책임보다 많은 책임을 가진 설계라면 분리해야할 필요가 있다.
AOP는 공통의 관심사를 추상화해 잘 보관하고 있다가 필요한 곳에 동적으로 삽입하며 적용해주는 기술이다. 이로 인해 핵심 로직과 부가 기능을 분리할 수 있고 각각의 기능은 단일 책임을 가진 코드를 유지할 수 있다.
핵심 로직과 부가 기능이 같이 있다면
두 가지 책임을 가지므로 하나의 책임에 인한 코드 수정은 다른 코드에 side effect가 발생할 수 있다.
두 가지 기능이 접목되어 있어 테스트 코드를 작성하려 할 때도 두가지 기능을 다 고려 해야한다.
비즈니스 로직이 복잡하거나 부가 기능이 추가될수록 유지보수 하기가 어려워진다.
유지보수와 비슷한 맥락으로 기능을 확장하거나 다른 전략으로 기능을 변경하기도 어려워 진다.
AOP를 적용한다면?
코드에는 핵심 로직만 남기고 부가 기능은 별도로 추상화하여 주입 받는 방식으로 리팩토링 할 수 있을 것이다.
//대표적인 AOP 방식이 @Transactional 적용 @Transactional public void upgradeLevels() throws Exception { List<User> users = userDao.getAll(); for (User user : users) { if (canUpgradeLevel(user)) { upgradeLevel(user); } } this.transactionManager.commit(status); }
코드를 분리하면 어떤 점이 좋을까?
메서드 또는 클래스는 한 책임을 가질 수 있는 코드를 작성할 수 있고 응집도 높은 코드를 작성할 수 있다.
테스트 코드 또한 비즈니스 로직에 대한 테스트 케이스만 작성하면 된다.
유지보수 측면에서도 비즈니스 로직 수정시에 부가 기능에 대한 side effect는 찾아 볼 수 없게 된다.
프록시란?
자신의 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것(대리인 ,대리자)
사용 목적에 따라
클라이언트가 타깃에 접근하는 방법을 제어하기 위해
프록시 패턴
타깃에 부가적인 기능을 부여 해주기 위
데코레이터 패턴
AOP 용어 정리
Target Object
부가 기능을 부여할 대상
Aspect
AOP의 기본 모듈. 그 자체로 애플리케이션의 핵심 기능을 담고있진 않지만 애플리케이션을 구성하는 중요한 한가지 요소, 부가될 기능을 정의한 Advice와 어드바이스를 어디에 적용할지 결정하는 Pointcut을 함께 가짐
Advice
타깃에게 제공할 부가 기능을 담은 모듈. 타깃이 필요없는 순수한 부가 기능. Aspect가 무엇을 언제 할지를 정의하고 있음