1_[spring]객체지향의 원리
객체지향
“현실에 존재하는 사물을 있는 그대로 모델링”
- C언어 등과 같은 절차지향 언어는 프로그램 복잡도가 증가하면서 유지보수 및 개발 기간 등 다양한 부분에서 비효율 발생
- 위와 같은 어려움을 해결하기 위한 효과적인 개발 방식을 채택하기 위해 등장
- 객체 지향의 특징 : 추상화, 상속, 은닉, 다형성, 캡슐화, 재사용, 인터페이스 등
🌷 객체 지향의 4대 특성 : 캡슐화, 상속, 다형성, 추상화
🌻 추상화 : 객체에 필요한 데이터 및 메서드에 대한 밑그림을 그리는 것
- 개념도를 설계한다고 생각
- 다형성, 상속과 비교하였을 때, 탱크나 세단 등이 하나의 Unit이 되고 그 기능으로 공통적인 부분들로써 move가 포함될 수 있겠다! 라고 대략적인 틀을 정의하는 것을 의미
🌻 상속 : 객체의 데이터 및 메서드를 자식클래스에 물려주는 것
- 자바는 다중상속 시 발생가능한 충돌(메서드명이 같은데 어떤 클래스로부터 물려받은지 구분이 안되는 경우 등)을 막기 위해서 다중상속이 지원되지 않음
- 하위로 내려갈수록, 자손클래스가 될 수록 보다 구체화됨(속성과 메서드가 더욱 풍부해짐)
- 장점
- 프로그램 구조에 대한 이해도 향상 : 최상위 클래스의 구조를 보고 하위 클래스의 동작 이해 가능(예: move메서드를 통해서 움직임을 예측)
- 재사용성 향상 : 필요한 속성이나 메서드를 물려받고, 재정의할 수 있음
- 확장성 향상 : 일관된 형태로 객체를 추가하고, 부가적인 부분만 별도 정의할 수 있음
- 유지보수성 향상 : 상속을 통해 물려받은 속성과 메서드는 같은 부모 클래스를 물려받은 클래스에서도 동일한 형태로 물려받아 작성 가능
🌻 은닉 : 객체의 데이터를 외부에서 무작위로 접근하지 못하도록 막아주는 것
🌻 다형성 :
- 한 객체가 다른 여러 객체로 재구성될 수 있는 것
- 오버라이딩: 메서드의 이름과 파라미터 갯수와 타입 모두 일치하는 메서드로, 내용을 재정의
- 오버로딩 : 메서드의 이름은 같으나, 파라미터의 갯수 혹은 타입이 다른 메서드
- 같은 부모 클래스 혹은 인터페이스를 상속받는 자식클래스들 간 공통적인 부분이 존재하는 경우, 이를 묶을 경우에도 사용할 수 있음
ex) List
===================================== 만약, Unit이라는 클래스의 자식클래스로 탱크, 세단 등이 있는 상황에서 private void unitMove(Unit unit){ unit.move();//캡슐화-내부메서드를 호출 } 위와 같이 부모클래스를 이용한 메서드를 기입해둔다면 자식클래스에서도 해당 메서드인 unitMove를 일괄적용받을 수 있다! 이 또한 위에서 리스트의 예와 마찬가지로 다형성이 적용되어
Unit unit = new 탱크(); 처럼 사용될 수 있기 때문이다!
🌻 캡슐화 : 객체에 필요한 데이터 및 메서드를 객체에 그룹화해주는 것
- 데이터를 메서드를 통해서 우회하여 접근할 수 있도록 하는 것
- method 설계
-속성이 선언되었지만 상태를 변경할 수 있는 메서드가 없다면 잘못된 것!!
-실물 객체가 가진 기능을 모두 제공해야 함
-각각의 메서드는 서로 관련성이 있어야 함
-객체 안의 메서드는 다른 객체를 전달받아서 처리하면 안된다!
단, 다른 객체가 필요한 경우에는 그 객체를 이용하지 않고, 그 객체의 getter 메서드를 이용해서 값 자체를 매개변수로 전달해주어야 한다!
- getter/setter
- CRUD method
- 비즈니스 로직 메서드
- 객체의 생명 주기 처리 메서드
-예: destroy(), disconnect() 등 소멸과 관련된 메서드
- 객체의 영구성 관리 메서드(유효성 속성에 대한 변경이 필요한 경우 private으로 막은 후 내부의 다른 메서드로 사용하도록)
-캡슐화의 장점
- 재사용성 향상 - 객체의 모듈성과 응집도가 높아지기 때문에 전역변수의 개념적 영향이 없음[전체 프로그램에 영향x]
- 유지보수의 효율성 향상
- 추상화가 재공됨 - 실제 메서드가 어떻게 동작하는지 외부에서 이해할 필요x
- 무결성 보장 - 필드값에 대한 접근을 getter setter를 제외한 다른 메서드들은 파라미터로 전달받아 실행하게 함으로써 객체의 값에 대한 유효성을 가질 수 있게 됨
🌷 C++과 자바의 차이점
- 시스템 레벨 접근
- 메모리 직접 할당 및 해제
- 포인터
등 복잡한 개발 방식을 자바에서는 추구하고 이지 않으며
어떤 운영체제에서도 자바 가상 머신 JVM만 있으면 독립적으로 실행가능하도록 설계되어 있기 때문에 여러 플랫폼과의 호환성이 우수!
🌷 객체의 3가지 요소
- 아래와 같은 요소를 지키지 않은 경우를 “불완전 객체”라 명명
- 상태 유지(객체의 상태)
- 객체는 상태 정보를 변수를 이용하여 속성으로써 저장하고 유지해야 함
- 이러한 속성은 그 값의 변경에 따라서 객체의 상태가 변경될 수 있어야 함
- 기능 제공(객체의 책임)
- 메서드로 인해 제공됨
- 캡슐화와 관련
- 외부로부터 직접 속성에 접근하지 않고 메서드로 속성을 우회접근할 수 있도록 제공되어야 함
- 고유 식별자 제공(객체의 유일성)
- 각각의 객체는 고유한 식별자를 지녀야 함( DB에서의 기본키 제약조건 혹은 UNIQUE 제약조건 같은 것)
🌷 물리 객체와 개념 객체
- 물리객체 : 실제로 존재하는 사물을 정의한 객체
- 개념객체 : 웹 시스템에서 Service에 해당됨. 비즈니스 로직을 처리하는 부분을 의미
- 실질적으로 대부분의 설계는 이러한 개념객체를 이용한 처리 부분이 많기 대문에 속성 및 오퍼레이션(메서드)를 잘 정의해야할 필요 존재!
- 예: 사용자의 action에 따라 계좌 잔고 속성을 변경하는 입/출금 처리, 계정만료
🌺 객체 지향 설계 5원칙 SOLID
개념 1. 응집도와 결합도
- 좋은 SW 설계 ⬅️ 결합도(coupling) ⬇️ & 응집도(cohesion) ⬆️
*결합도
- 모듈(클래스)간 상호 의존 정도를 나타내는 정도
- 결합도가 낮으면 모듈간 상호의존성 ⬇️ ➡️ 객체의 재사용 및 유지보수 유리
- 결합도가 높으면 , 즉 절차지향 언어입장에서는, A-B-C 로 연결되어 A를 수정하면 B,C도 수정해야 함!
*응집도
- 하나의 모듈 내부에 존재하는 구성요소들의 기능적 관련성
- 응집도가 높은 모듈은 하나의 책임에 집중하고 독립성이 높아져서 재사용 및 유지보수가 용이
- S SRP “Single Responsibility Principle” 단일 책임 원칙
- 어떤 클래스를 변경해야 하는 이유는 단 한가지뿐이어야 함!
- 각 모듈은 그 모듈의 기능에 집중할 수 있도록 분리해두도록 하는 원칙!(아래그림처럼) ▶️ 응집도가 높아짐
- 새로운 기능 추가 혹은 기능 수정시 유지보수 비용 부담이 줄어듦
- 동시에, 상속과 다형성을 적용한다면 기능의 변경 등의 상황의 영향도 줄어듦
public class Unit{
private int hp;
private int speed;
//...생략
public void move(){~~~}
public void attack(){~~~}
}
public class 탱크 extends Unit{
@Override
public void move(){
this.speed+=10;
}
@Override
public void attack(){
this.hp-=10;
}
}
→ 탱크는 탱크의 기능만 수행할 수 있게 됨으로써 전반적인 프로그램에
대한 영향이 줄어듦!
SOLID 원칙-SRP(Single Responsibility Principle)
- O OCP “Open Closed Principle” 개방 폐쇄 원칙
- 자신의 확장에는 열려있고, 주변의 변화에는 닫혀있어야 함
- 상위 클래스나 인터페이스를 중간에 두게 되면, 내부적으로는 단 하나의 통로를 두고, 외부적으로는(자식클래스 방향으로 뻗어나가는 모습) 여러 통로를 두는 모습(해당 클래스나 인터페이스를 상속받는 다른 클래스까지 포함하여 전체적인 그림으로 보았을 때)을 보이게 된다. 이를 통해서 자기자신의 내부통로를 통한 확장에는 열려있고 그 외에는 닫혀있기 때문에 , 자기자신의 확장을 수행하게 된다!
- 예) JDBC, MyBatis, Hibernate 등
- 예) 입출력 스트림-보조스트림과 메인스트림 간 관계
- L LSP “Liskov Substitution Principle” 리스코프 치환 원칙
- 서브타입은 언제나 자신의 상위(슈퍼)타입으로 교체할 수 있어야 한다!
객체지향 SOLID 설계원칙- LSP Liskov Substitution Principle 리스코프 치환 원칙
먼저 위의 그림으로 이해해보자
왼쪽은 리스코프 치환원칙이 적용되지 않은 상태이고, 오른쪽은 리스코프 치환 원칙이 적용된 상태이다
왼쪽의 경우 소나타는 아반떼가 될 수 없고 그랜저도 아반떼가 될 수 없다
하지만 오른쪽의 경우, 정찰기는 공중유닛이자 비행기가 될 수 있고
수송기도 마찬가지다! 이러한 특징은 상속과 다형성을 생각하게 한다!
덧붙이자면, 부모의 기능을 자식클래스에 적용했을 때 전혀 이물감이 없는 상태를 일컬을수 있는 개념이라고 생각해도 무방하다! 아래의 블로그에서 해당 예시를 잘 보여주고 있다!
- I ISP “Interface Segregation Principle” 인터페이스 분리 원칙
- 클라이언트는 자신이 사용하지 않는 메서드에 의존관계를 맺으면 안된다!
- 프로젝트 요구사항과 설계에 따라 SRP 혹은 ISP 원칙을 선택[SRP 개념과는 다르기 때문!- SRP는 기능에 집중할 수 있도록 지원]
- 사용하지 않는 인터페이스, 즉 기능의 집합체는 관계를 맺으면 안된다는 것!
- 자전거 내비게이션이 필요없는데 굳이 관계를 맺을 필요는 없음!
- D DIP “Dependency Inversion Principle” 의존 역전 원칙
- 자신보다 변하기 쉬운것에 의존하지 말 것!
- OCP에서 살펴본 바와 유사하다고 할 수 있는 형태로, 예를 들어서 사람은 계절에 따라 옷을 다르게 입을 수 있는데 이를 DIP를 통해 설계하면
객체지향 SOLID 설계-DIP Depedency Inversion Principle
옷은 계절에 따라 달라지는 데 만약 위와 같이 설계하지 않고
사람← 봄옷 사람← 여름옷과 같이 설계한다면 이는 DIP에 위배된다!
덧붙이자면, DIP에서는 변화하는 것 == 구체적인 것! 이라고 생각하면 된다!(https://dev-momo.tistory.com/entry/SOLID-원칙 참고!)
즉 위와 같이 설계한다면, 사람은 옷에 의존하게 되고, 봄 옷도 여름옷도 가을옷도, 겨울옷도 옷을 의존하게 되어 의존의 역전이 일어나게 된다. 즉, 옷이라는 인터페이스 혹은 상위 클래스를 둠으로써 사람은 덜 구체적인, 즉 변화하지 않는것과 관계를 맺게 된다!