1. Java 기본
참고:
(1) 객체지향
객체지향의 3요소
- Encapsulation (캡슐화)
- 의미: 객체의 속성(data fields)과 행위(메서드, methods)를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉한다.
- 캡슐화의 방법
1. 표준을 따르게 한다 (공통 상위클래스/인터페이스)
2. 필드자체가 아닌 메서드를 이용해 노출(getter 메서드)
3. 접근제어자 활용(public, private, protected, default)
- 캡슐화의 이점
1. 클래스의 필드 값에 대한 수정 권한을 설정 가능하다.
- 조회만 가능(read-only)하게 하거나 수정만 가능(write-only)하게 만들 수 있다.
2. 필드에 저장된 모든 값들을 컨트롤 할 수 있다.
-클래스의 변수 값이 손상될 확률이 줄어든다.
3.사용자는 데이터가 클래스에 어떻게 저장되는지 확인할 수 없다.
-클래스 내부의 데이터 형태가 변경되어도 사용하는 코드를 변경할 필요가 없다.
-데이터가 변경되어도 다른 객체에 영향을 주지 않아 독립성이 유지된다.
4.클래스의 결합도가 낮아져 재사용이 용이하다
-객체의 내부로직을 알 수 없게 되면 접근할 수 있는 부분이 제한되기 때문에 다른 코드와의 결합도가 낮아진다.
때문에 코드의 변경에 유연하게 대처할 수 있다.
- Inheritance (상속)
Java에서 상속은 부모 클래스의 변수/메소드를 자식 클래스가 물려받아 그대로 사용 가능하게 해준다.
여기서 부모클래스를 superclass, 자식클래스를 subclass라 부른다.
자식클래스에서 A라는 기능을 처리하는데 부모클래스에서 이미 똑같은 A라는 기능을 처리하고 있다면
자식클래스는 이를 상속받아 그대로 사용할 수 있으며, 코드의 중복을 막아준다.
상속은 extends라는 키워드를 사용하며 상속의 형태는 다음과 같다. 자식클래스 extends 부모클래스
-상속의 특징
-다중 상속이 불가능합니다. 즉, 2개 이상의 클래스를 한꺼번에 상속할 수 없습니다.
-부모의 생성자는 상속이 되지 않습니다.
-부모 클래스가 가진 멤버변수와 메소드를 모두 상속받습니다.
-부모 클래스 내에서 멤버 변수 또는 메소드가 private 접근 제한자를 사용하면 멤버 변수는 상속 받으나 바로 접근이 불가능하며, 메소드는 상속 되지 않는다.
- static 메서드 또는 변수도 상속이 된다.
- 동일한 이름의 변수가 부모 클래스와 자식 클래스에 둘 다 존재할 경우 부모 클래스의 변수는 가려진다.
-상속의 조건
Is-A 관계:
~는~이다'의 관계로 일반적인 상속의 개념
ex) 학생은 사람이다(o)
사람은 학생이다(x)
아래로 내려갈수록 구체화 또는 특수화 되어 지고 올라갈수록 보다 일반화 되어 진다.
상속 관계에 있을 때 파생 클래스는 기본클래스의 모든 특성들을 포함하기 때문에
아래로 갈 수록 특성이 많아지는데 이러한 특성들은 멤버 함수와 멤버 변수로서 반영이 되고,
그렇기 때문에 상속으로서 관계를 맺어 줄 수 있는 것이다.
has-A 관계: ~는~를 가지고 있다.
경찰은 몽둥이를 소유한다.
몽둥이 클래스는 Swing 함수를 가지고 있고 경찰 클래스는 몽둥이 클래스를 상속했기에 몽둥이 클래스의 기능을 사용할 수 있다.(즉 소유한것)
경찰은 몽둥이다의 is-a 관계는 성립이 안되지만 has-a 관계가 성립되어도 상속이 된다는 것을 알 수 있다.
- Polymorphism (다형성)
객체지향개념에서 다형성이란 '여러 가지 형태를 가질 수 있는 능력'을 의미하며 자바에서는 한 타입의 참조변수로 여러 타입의 객체를 참조할 수 있도록함으로써 다형성을 프로그램적으로 구현하였다.
인터페이스와 상속은 둘 다 다형성이라는 객체지향 프로그래밍의 특징을 구현하는 방식이다.
다형성은 하나의 객체를 여러 가지 타입으로 선언할 수 있다는 뜻이다.
Java에서 다형성은 상속과 인터페이스를 통해 이루어진다.인터페이스가 상속보다 다형성에 더욱 유연함을 제공한다.
인터페이스는 클래스의 선언 뒤에서 여러 개의 인터페이스를 구현할 수 있게 할 수 있다.
런 이유 때문에 하나의 객체를 여러 개의 타입으로 바라보는 다형성에는 상속보다 인터페이스가 더 큰 유연함을 제공한다고 할 수 있다.
객체지향의 5원칙 (SOLID 원칙) (참고자료)
- SRP (Single Responsibility Principle) - 단일책임 원칙
- 하나의 클래스는 하나의 책임만 가져야 한다
→ 어떤 변화 (요구사항의 변화 등)에 의해 클래스를 변경해야 하는 이유는 오직 하나이어야 한다 - 나머지 4원칙의 기초가 되는 원칙으로 SRP만 잘 지키면 다른 책임의 변경으로 인한 연쇄작용을 방지할 수 있다.
- ReviewController, ShopService(x)
- WriteReviewController, RemoveShopService(o)
- 하나의 클래스는 하나의 책임만 가져야 한다
- OCP (Open-Closed Principle) - 개방-폐쇠 원칙
- SW의 구성요소(모듈, 컴포넌트, 클래스, 메소드)는 확장에는 열려있고 변경에는 닫혀있어야 한다.
→ 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화 한다 - OCP를 가능하게 하는 중요 메커니즘은 추상화와 다형성
- 새로운 기능이 추가되는 경우 기존에 제공하던 클래스(또는 메소드)를 수정하는 것이 아니라 새로운 클래스(또는 메소드)를 추가해서 기능을 확장한다.
- SW의 구성요소(모듈, 컴포넌트, 클래스, 메소드)는 확장에는 열려있고 변경에는 닫혀있어야 한다.
- LSP (The Liskov Subsitution Principle) - 리스코프 치환 원칙
- 서브 클래스는 언제나 슈퍼 클래스를 대체할 수 있다.
→ 슈퍼 클래스가 들어갈 자리에 서브 클래스를 넣어도 원래대로 잘 작동해야 한다 - 상속의 오용을 방지하게 하는 원칙
- 슈퍼 클래스와 서브 클래스의 동작이 일관성 있게 동작해야 함
public interface Shape { Integer area(); } class Rectangle implements Shape { private Integer width; private Integer height; public Integer getWidth() { return width; } public void setWidth(Integer width) { this.width = width; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } @Override public Integer area() { return this.width * this.height; } } class Square extends Rectangle { @Override public Integer getWidth() { return super.getWidth(); } @Override public void setWidth(Integer width) { super.setWidth(width); super.setHeight(width); } @Override public Integer getHeight() { return super.getHeight(); } @Override public void setHeight(Integer height) { super.setWidth(height); super.setHeight(height); } @Override public Integer area() { return super.area(); } } //.... @Test public void 너비_계산_테스트(Shape s){ s.setWidth(5); s.setHeight(4); s.area(); //??? }
- 서브 클래스는 언제나 슈퍼 클래스를 대체할 수 있다.
- ISP (Interface Segregation Principle) - 인터페이스 분리 원칙
- 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 않아야 한다.
→ 하나의 일반적인 인터페이스보다는, 여러 개의 구체적인 인터페이스가 낫다. - SRP가 클래스의 단일책임을 강조한다면 ISP는 인터페이스의 단일책임을 강조함.
- 한 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 않아야 한다.
- DIP (Dependency Inversion Principle) - 의존성 역전의 원칙
- 고차원 모듈은 저차원 모듈에 의존하면 안된다 (자주 변경되는 또는 변경될 만한 구현체에 의존하지 말 것)
→ Layered Architecture와 같이 상하의 관계가 존재하는 구조에서 하위 레벨의 변경이 상위 레벨의 변경을 요구하는위계관계를 끊는 것
고차원 모듈이 저차원 모듈에 직접적으로 의존할 때(직접적으로 SDK 구현체 사용 및 Model 참조)@Service public class FileService { @Autowired private AmazonS3SDKImpl amazonS3SDKImpl; @Autowired private AlibabaS3SDKImpl alibabaS3SDKImpl; public void save(Path path, ServiceType serviceType) { if(ObjectUtils.nullSafeEquals(serviceType, ServiceType.AMAZON)){ this.amazonS3SDKImpl.save(new AmazonS3SDKImpl.AmazonS3Object(path)); } else if(ObjectUtils.nullSafeEquals(serviceType, ServiceType.ALIBABA)){ this.alibabaS3SDKImpl.save(new AlibabaS3SDKImpl.AlibabaS3Object(path)); } } public void find(){ //... } public void delete(){ //... } } enum ServiceType { AMAZON, ALIBABA; } //SDK 1.0 버전에 치명적인 버그가 있다면? class AmazonS3SDKImpl { public void save(AmazonS3Object s3Object) { //Amazon SDK 내부 구현 } public static class AmazonS3Object { private Path path; public AmazonS3Object(Path path) { this.path = path; } } } //SDK 1.1에서 AlibabaS3Object가 Deprecated 된다면? class AlibabaS3SDKImpl { public void save(AlibabaS3Object s3Object) { //Alibaba SDK 내부 구현 } public static class AlibabaS3Object { private Path path; public AlibabaS3Object(Path path) { this.path = path; } } }
고차원 모듈이 저차원 모듈에 의존하지 않고 저차원 모듈을 추상화 한 고차원 모듈에 의존할 때
(FileRepositoryFacade를 통해 저차원 모듈을 추상화 한 FileRepository 인터페이스를 의존)
@Service public class AdvancedFileSaveService { @Autowired private FileRepositoryFacade facade; public void save(Path path, FileRepositoryFactory.ServiceType serviceType) { this.facade.save(serviceType, new ApplicationFile(path), applicationFile -> { //callback }); } } @Component class FileRepositoryFacade { private FileRepository amazonS3Repository; private FileRepository alibabaS3Repository; public FileRepositoryFacade (FileRepository amazonS3Repository, FileRepository alibabaS3Repository) { this.amazonS3Repository = amazonS3Repository; this.alibabaS3Repository = alibabaS3Repository; } public enum ServiceType { AMAZON, ALIBABA; } public void save(ServiceType serviceType, ApplicationFile applicationFile, Consumer callback){ this.getInstance(serviceType).save(applicationFile); callback.accept(applicationFile); } private FileRepository getInstance(ServiceType serviceType) { if(ObjectUtils.nullSafeEquals(serviceType, ServiceType.AMAZON)){ return this.amazonS3Repository; } else if(ObjectUtils.nullSafeEquals(serviceType, ServiceType.ALIBABA)){ return this.alibabaS3Repository; } else { throw new IllegalArgumentException(); } } } //Interface로 각 구현체(Concrete) 추상화 interface FileRepository { void save(ApplicationFile applicationFile); } //Amazon S3 SDK를 의존하는 구현체 @Repository class AmazonS3Repository implements FileRepository { //Amazon SDK dependency ... private AmazonS3SDKImpl amazonS3SDK; @Override public void save(ApplicationFile applicationFile) { //applicationFile을 AmazonS3SDK에서 요구하는 형태로 바꾸어 처리 } } //Alibaba S3 SDK를 의존하는 구현체 @Repository class AlibabaS3Repository implements FileRepository { //Alibaba SDK dependency ... private AlibabaS3SDKImpl alibabaS3SDK; @Override public void save(ApplicationFile applicationFile) { //applicationFile을 AlibabaS3SDK에서 요구하는 형태로 바꾸어 처리 } } //각 SDK에서 사용하는 Model을 추상화 @Data @AllArgsConstructor class ApplicationFile { private Path path; public Boolean isAvaliableFile() { return this.path != null && Files.exists(this.path); } }
- 고차원 모듈은 저차원 모듈에 의존하면 안된다 (자주 변경되는 또는 변경될 만한 구현체에 의존하지 말 것)
※ DDD(Domain Driven Design) ?
- 도메인이란?
- 사전적 의미는 '영역','집합'
- DDD에서 말하는 Domain은 비즈니스 Domain-
- 비즈니스 Domain은 유사한 업무의 집합
- 어플레키이션은 비즈니스 도메인 별로 나누어 설계 및 개발 될 수 있다.
- DDD란?
비즈니스 domain별로 나누어 설계 하는 방식
기존의 어플리케이션 설계가 비즈니스 Domain에 대한 이해가 부족한 상태에서 설계 및 개발되었다는 반성에서 출발한 설계
기존의 현업에서 IT로의 일방향 소통구조를 탈피하여 현업과 IT의 쌍방향 커뮤니케이션을 매우 중요하게 생각합니다.
DDD의 핵심 목표는 "Loosly coupling","High cohesion" =의존성 최소화 응집도 c대화
- Ubiquitous Language(보편적 의사소통 언어)
현업, 개발자, 디자이너 등 참여자들이 동일한 의미로 이해하는 언어
정확한 커뮤니케이션을 위해 공통언어를 정의하고 사용해야 합니다.
- Domain Model(Entity, Value Object, Aggregate, Service, Repository, Factory, Domain Events)
도메인 모델정의 시 몇 가지 구현 패턴이 있는데 일반적으로 사용되는 패턴은 Entitiy 기반 모델을 정의하는 패턴을 사용하고 있다
Entity
속성이 아닌 식별성을 기준으로 정의되는 도메인 객체
ex)DB:ERD(Entity - RelationShip Model),J2EE:Entity Bean
Value Object
식별성이 아닌 속성을 이용해 정의되는불변 객체
모든 것에 식별성을 부여하고 Entity로 관리한다면 복잡성 증가
과거 Java의 DTO(Data Transfer Object) 패턴의 Value Object와 관계없음
Entity와 Value Object를 구별하는 첫 번째 조건은 식별성
식별성을 가지면 Entity 그렇지 않으면 Value Object
Service
Domain Object에서 위치시키기 어려운 operation을 가지는 객체
여러 Domain Object 다루는 연산 Service의 오퍼레이션은 일반적으로 stateless
Domain Object에 해당되는 역할을 service operation으로 만드는 경우 도메인 역할을 침범하여 강 결합이 일어남
Module
유사 작업 및 개념을 그룹화 하여 복잡도를 감소시키는 기법
응집도가 높은 모듈은 모듈간의 관계는 약 결합
java로 구현하는 경우 Package로 구성될 수 있다
Aggregate
연관된 Entity와 Value Object의 묶음. 일관성과 트랜잭션, 분선의 단위, 캡슐화를 통한 복잡성 관리
예를 들어 쇼핑몰 사이트에서 주문 Entity 내에 배송주소 정보를 우편번호,주소1,주소2,상세주소,이런식으로 각 칼럼으로 정의하는 것이 아니라, 주소라는 Value Object를 별도로 작성하고 주문 Entity는 주소 ValueObject를 포함하는 방식으로 관계 일관성 및 단순화를 유지한다
factory
복잡한 Entity의 생성 절차에 캡슐화 할 수 잇는 개념
생성하기 복잡한 Aggregate 내의 여러 객체를 동시에 생성
생성시 Aggregate의 일관성 유지
Repository
도메인 영역과 데이터 인프라스트럭쳐 계층을 분리하여 데이터 계층에 대한 결합도를 낮추기 위한 방안
생성된 Aggregate에 대한 영속성 관리, 조회,등록,수정,삭제 시 Aggregate의 일관성 유지
DB 및 데이터 저장소의 데이터를 조회하고 저장하는 경우 Repository를 활용한다.
- Layered Architecture(User interface, Application, Domain, Infrastructure)
목적별 계층으로 나누어 설계하는 것
UI 영역
사용자의 요청을 받아 응용 영역에 전달하고 응용 영역의 처리 결과를 다시 사용자에게 보여주는 역할을 한다.(Controller 영역,DispatcherServlet에게 요청과 응답을 전달하는 역할)
Application 영역
시스템이 사용자에게 제공해야 할 기능을 구현(service 영역)
Domain 영역
도메인 모델을 구현한다(이름,주소,상품 등)
인프라 영역
구현 기술에 대한 것을 다룬다.
- Bounded Context
사용자,프로세스,정책/규졍 등을 고유한 비즈니스 목적별로 그룹핑 한 것
'개발 > JAVA' 카테고리의 다른 글
[Java] [이펙티브 자바][02] 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2022.12.27 |
---|---|
[Java] [이펙티브 자바][01] 생성자 대신 정적 팩토리 메서드를 고려하라 (0) | 2022.12.27 |