객체 지향 프로그래밍

컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위.

"객체" 들의 모임으로 파악하고자 하는 것

각각의 객체는 메세지를 주고받고, 데이터를 처리할 수 있다. = 협력

 

또 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 유용하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다. 

유연 변경? 다시 말해 레고 블럭 조립하듯 컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법

이것이 다형성(Polymorphism)이라고 한다. 

 

객체 지향의 가장 중요한 개념 중 하나는 다형성이다. 

다형성은 역할과 구현으로 구분을 한다.  역할은 인터페이스고 구현은 그 인터페이스를 구현한 객체라고 보면된다!

 

 

 

다형성을 운전자-자동차로 예를 들어보자

 

운전자(역할)는 자동차(역할)를 운전한다 

'자동차 역할'을 '자동차 구현'으로 분리해보면 K3, 아반떼, 테슬라..(인스턴스)

'자동차 구현' 이 바뀌어도 운전자는 역시나 운전할 수 있다.

이 말은 즉슨, 운전자는 자동차 인터페이스 즉 '자동차 역할' 에 대해서만 의존을 하고있다. 

'자동차 구현' 처럼 다른 대상으로 변환이 가능하고(새로운 기능들 제공) 무한확장이 가능하나 '자동차 역할'에 영향을 주지 않으며 운전자(클라이언트)는 바꿀 필요가 없다. 

이것이 바로 '유연하고 변경용이하다' 라고 할 수 있다. 

 

 

정리
역할과 구현을 분리
  • 역할과 구현으로 구분하면 유연해지며 변경이 편리해진다 
  • 클라이언트는 대상의 역할(인터페이스)만 알면 된다. 
  • 클라이언트는 구현 대상의 내부 구조를 몰라도 된다 (자동차 디테일 구조를 몰라도 됨 )
  • 클라이언트는 내부 구조가 변경되어도 영향을 받지 않는다. (엑셀, 브레이크만 알아도 된다)
  • 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다 (K2 에서 테슬라로 바꿔도 운전자는 영향X) 

핵심은 역할이 중요하다! 

  • 자바 언어
    • 역할 = 인터페이스
    • 구현 = 인터페이스를 구현한 클래스, 구현 객체

객체를 설계할 때 역할과 구현을 명확히 분리 

객체 설계시 역할(인터페이스)를 먼저 부여하고 , 그 역할을 수행하는 구현 객체 만들기

 

 

이론으로는 알겠는데 이제 코드로 예를 들어보자 

 

  • 역할 : MemberRepository 인터페이스
  • 구현 : MemoryMemberRepository 클래스, JdbcMemberRepository 클래스
    • 인터페이스를 구현한 객체 인스턴스

 

우선 MemberService 에서 save()를 호출하면 

MemberRepository 인터페이스를 구현 한  MemoryMemberRepository 클래스는 save() 메서드를 호출된다.

 

MemoryMemberRepository 클래스는 save()를 오버라이딩을 하여 자체적인 동작(구현)을 제공할 수 있다. 

다시 말해 MemberService 는 MemberRepository 인터페이스를 통해 MemoryMemberRepository 클래스의 메서드 save() 를 호출할 수 있다.  

  • MemberService > MemberRepository > MemoryMemberRepository 의 save()호출

 

 

[MemberService]

public interface MemberService {

	//회원 가입
	void join(Member member);
	
	//회원 조회
	Member findMember(Long memberId);
}

 

 

 

[MemberRepository]

public interface MemberRepository {

	//회원 저장
	void save(Member member);
	
	//회원 id를 찾는기능
	Member findById(Long memberId);
}

 

[MemoryMemberRepository]

@Component
public class MemoryMemberRepository implements MemberRepository{ //MemberRepository를 구현

	
	private static Map<Long, Member> store = new HashMap<>();
	
	//MemberRepository의 save()를 재정의(오버라이딩)
	@Override
	public void save(Member member) {
		store.put(member.getId(), member);
	}

	@Override
	public Member findById(Long memberId) {
		return store.get(memberId);
	}

}

 

 

여기서 다형성의 본질을 배울 수 있다. 

  • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다.
    • MemberService 는 MemberRepository 인터페이스의 메서드를 호출하기 때문에 인터페이스에만 의존한다.
    • 의존한다는 뜻은 "내가 쟤를 알고 있다" 라는 말
    • MemberService 는 MemoryMemberRepository 의 구체적인 코드를 몰라도 된다. 
  • 인터페이스를 구현한 객체 인스턴스( MemoryMemberRepository )를 실행시점에서 유연하게 변경할 수 있다.   
    • 구현체( MemoryMemberRepository ) 변경가능 = JdbcMemberRepository 클래스를 사용해도 된다. 

 

대신 인터페이스가 깨지면 모두 깨짐

그래서 인터페이스를 안정적으로 잘 설계하는게 가장 중요하다!

 

 

 

스프링과 객체 지향

  • 스프링은 다형성이 가장 중요하다.
  • 스프링을 사용하면 구현을 편리하게 변경할 수 있다.
  • 다형성을 극대화해서 이용할 수 있게 도와주고 다형성을 편리하게 사용할 수 있도록 지원하는 개념이라고도 할 수 있다.
  • 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용하여 역할과 구현을 편리하게 다룰 수 있도록 지원한다

 

 

 

SOLID 

: 좋은 객체 지향 설계의 5가지 원칙

  • SRP (Single responsibility principle) 단일 책임 원칙
    • 한 클래스는 하나의 책임만 가진다 
    • 변경이 있을 때 파급효과가 작으면 단일 책임 원칙을 잘 따른것 
    • ex) UI 변경, 객체 생성과 사용을 분리
  • OCP (Open / cloased principle) 개방-폐쇄 원칙
    • 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야한다 (코드에 변경할 필요없이 기능추가 가능)
    • 역할과 구현의 분리같은 거라고 생각하면된다
    • 다형성을 활용해보면 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현 추가
      • 새로운 JdbcMemberRepository 클래스 만들어서 MemoryMemberRepository 클래스를 변경
      • 기존 코드를 변경을 하지않은 것! 
    • 클라이언트 코드인 MemberService 에서 구현 클래스를 직접 선택시
      • MemberRepository m = new MemoryMemberRepository(); //기존코드
      • MemberRepository m = new JdbcMemberRepository(); //변경코드
      • OCP 위반 (코드가 변경됐기 때문)
    • OCP 원칙을 지키려면 다형성 만으로 지킬 수 없고 DI, IOC 컨테이너가 필요함 
  • LSP (Liskov substitution principle) 리스코프 치환 원칙
    • 자동차가 엑셀 구현(앞으로 간다의 기능) 하도록 구현한다. 
      • 뒤로가는 기능을 만들면 리스코프 치환 원칙을 위반
  • ISP (Interface segregation principle) 인터페이스 분리 원칙
    • 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다
      • 자동차 인터페이스 => 운전 인터페이스 , 정비 인터페이스로 분리하면
      • 사용자 클라이언트 => 운전자 클라이언트 , 정비사 클라이언트로 분리가 가능하다
      • 이렇게 분리하면 정비 인터페이스를 변경해도 운전자 클라이언트에 영향을 주지 않아서 인터페이스가 명확하고 대체 가능성이 높아진다. 
  • DIP (Dependency inversion principle) 의존관계 역전 원칙
    • 추상화에 의존하고 구체화에 의존하면 안된다 
    • 다시 말해 클라이언트 코드가 구현 클래스에 의존하지 않고 인터페이스에 의존해야한다 (객체도 인터페이스에 의존하면 유연하게 구현체 변경가능)
    • 예를들어 운전자는 자동차의 역할만 알면되지 k3에 대해 알 필요가 없다.
    • DIP원칙을 지키려면 다형성 만으로 지킬 수 없고 DI, IOC 컨테이너가 필요함 
    • OCP 처럼 클라이언트 코드인 MemberService 에서 구현 클래스를 직접 선택시
      • MemberRepository m = new MemoryMemberRepository(); //기존코드
      • MemberRepository m = new JdbcMemberRepository(); //변경코드
      • MemberRepository 인터페이스에 의존하나 구현클래스까지 동시에 의존한다.
      • DIP위반 (구체화에 의존했기 때문)

 

 

 

정리 

: 다형성만으로는 OCP, DIP 를 지킬 수 없다.

 

 

어떻게 해결? 스프링으로! 

  • 스프링은 DI , DI 컨테이너 제공으로 다형성 + OCP,DIP 를 가능하게 지원한다.
  • 클라이언트 코드 변경없이 기능확장

 

 

 

반응형
LIST

+ Recent posts