java

[Java] OOP의 다형성 및 SOLID

코딩하는 공부방 2022. 7. 12. 02:41

안녕하세요~!!
파이썬이랑 Web 기초랑 CS 부분만 공부하다가
Spring 공부하면서 Java 기초부분을 정리하게 되었는데요
오늘은 OOP 장점과 SOLID 부분에 대해서 설명을 하겠습니다.


객체 지향 패러다임의 4가지 주요 특성 "캡슐화", "추상화", "다형성", "상속" 중에서 "다형성" 포커스를 두고
설명을 하겠습니다.

 

다형성(Polymorphis)

객체지향 언어는 동일한 이름을 가진 메소드를 허용하지 않는다. 예를 들어 "잠든다"는 동작이 구현된 메소드가 있다고 가정하자. 잠든다는 동일한 동작이 구태여 두 개나 구현될 필요가 없습니다. 이러한 관점에서 본다면 메소드의 고유 아이덴티디라고도 불릴 수 있는 메소드명의 유니크화는 어쩌면 당연하다.

하지만 조금 생각해보면 이상하다. JAVA는 타입에 죽고 타입에 사는 언어이다. JavaScript와 달리 파라미터에 아무 타입이나 넣을 수 없기 때문에, 정해진 타입 이외에 무언가를 넣으면 컴파일 단계에서 오류를 띄웁니다.

분명히 아까 동일한 이름을 가진 메소드는 동일한 객체에서 존재할 수 없다고 했었다. 그럼에도 불구하고 System.out.println()  의 경우, 메소드 명은 동일한데 여러 타입을 보란듯이 받아서 처리하고 있다. 어떻게 된걸까? 유명한 소드는 예외사항이라도 적용이 되는건가?

System.out.println() 이 여러 타입을 처리할 수 있는 이유는 메소드에 다형성이 적용되어있기 때문이다. 하나의 객체 혹은 메소드가 여러 타입을 참조할 수 있음을 의미한다. 다형성의 크게 객체의 다형성과, 메소드의 다형성으로 구분된다.

 

역할과 구현을 분리(스프링 설계 Tip)

역할과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리해집니다.

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

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

다형성의 본질

인터페이스를 구현한 객체 인스턴스를 실행 시점유연하게 변경할 수 있다
다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야합니다.
클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다

 

※인터페이스를 안정적으로 잘 설계하는 것이 매우 중요합니다.※

 

 


SOLID(객체 지향 설계)

위키백과에서 컴퓨터 프로그래밍에서 SOLID란 로버트 마틴이 2000년대 초반에 명명한 객체 지향 프로그래밍(OOP) 및 설계의 다섯 가지 기본 원칙을 마이클 페더스가 두문자어 기억술로 소개한것입니다. 프로그래머가 시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 할 때 이 원칙들을 사용할 수 있습니다.

 

ⓐ SRP 단일 책임 원칙

SRP(Single responsibility principle)로 한 클래스는 하나의 책임만 가져야 합니다.
하나의 책임이라는 것은 모호합니다. 클 수도 있고, 작을 수도 있습니다. 또 문맥과 상황에 따라 다릅니다.
중요한 기준은 변경입니다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 다른 것입니다.
예를 들어서 "UI 변경", "객체의 생성과 사용을 분리"에 적용이 됩니다.

ⓑ OCP 개방-폐쇄 원칙

OCP(Open/closed principle)란 "소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다"라는게 OCP 이다.

개방-폐쇄 원칙은 OOP의 핵심 원칙이라고 할 수 있다. 개방-폐쇄 원칙을 따르지 않는다고 해서 객체 지향 언어(Java, C++ 등)을 구현이 불가능한 것은 아니지만 이 원칙을 무시하고 프로그래밍을 한다면, 객체 지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 결코 얻을 수 없다. 따라서 객체 지향 프로그래밍 언어에서 개방-폐쇄 원칙은 반드시 지켜야할 기본적인 원칙이다.

근데 생각해보면 말이 안되는 것이다. 거짓말 같다..? 확장을 하려면 당연히 기존 코드가 변경이 되어야 하지 않는가
그 때 다형성을 활용해보는 것이다.
인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현을 하는 것이다. 위에 말한 것처럼 역할과 구현으로 분리해서 말이다.

  • MemberService 클라이언트가 구현 클래스를 직접 선택
    • MemberRepository m = new MemoryMemberRepository(); //기존 코드
    • MemberRepository m = new JdbcMemberRepository(); //변경 코드

구현 객체를 변경하려면 클라이언트 코드를 변경해야 한다. 분명 다형성을 사용했지만 OCP 원칙을 지킬 수 없다.이 문제를 어떻게 해결해야 하나? 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다.

ⓒ LSP 리스코프 치환 원칙

LSP(Liskov substitution principle)란 "프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀수 있어야 한다".

다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요하다. 예를 들어서 자동차 인터페이스의 엑셀은 앞으로 가라는 기능, 뒤로 가게 구현하면 LSP 위반, 느리더라도 앞으로 가야합니다.

ⓓ ISP 인터페이스 분리원칙

ISP(Interface segregation principle)란 특정 클라이언트를 위한 인터페이스는 여러 개가 범용 인터페이스 하나보다 낫다는 것입니다.
예를 들어 자동차 인터페이스는 운전 인터페이스와 정버 인터페이스로 분리를 하고 사용자 클라이언트는 운전자 클라이언트와 정비사 클라이언트로 분리를 시키는 것입니다.

분리를 하면 정비 인터페이스 자체가 변해도 운전자 클라언트에 영향을 주지 않습니다.
인터페이스가 명확해지고, 대체 가능성이 높아집니다.

ⓔ DIP 의존관계 역전 원칙

DIP(Dependency inversion principle)란 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙 을 따르는 방법 중 하나다.
쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻입니다.

앞에서 이야기한 역할(Role)에 의존하게 해야 한다는 것 같습니다. 객체 세상도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수가 있습니다. 구현체에 의존하게 되면 변경이 아주 어려워 집니다.

그런데 OCP에서 설명한 MemberService 는 인터페이스에 의존하지만, 구현 클래스도 동시에 의존합니다.

MemberService 클라이언트가 구현 클래스를 직접 선택

  • MemberRepository m = new MemoryMemberRepository();

DIP에 위반하는 형식입니다.

DIP를 정리 하자면

  • 첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 합니다.
  • 둘째, 추상화 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 합니다.

https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)
 

SOLID (객체 지향 설계) - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

 

'java' 카테고리의 다른 글

Java - Optional 문법  (0) 2022.08.12
[Java] Thread 란?  (0) 2022.07.30
ThreadLocal 이란?  (0) 2022.07.28
[Java] 컬렉션 - Collection 이란?  (0) 2022.07.18
[Java] Static import 란?  (0) 2022.07.14