Cold_Pitch

 

프로젝트 개요

주제 : 스타트업의 초기 아이디어를 유저에게 공유하여 피드백을 받을 수 있는 플랫폼

인원 : 6

기간  : 2023.05.01 ~ 2023.07.21

개발 환경 : Java, SpringBoot, React, MySQL, AWS, Docker, Github

Github : Cold_Pitch (https://github.com/MightyLions/Cold_Pitch)

 

프로젝트 배경

스타트업 생존율이 떨어지는 현 상황에서, 제품 혹은 서비스 시장 안착이 어려운 스타트업들을 대상으로 고객의 소리를 쉽게 수집할 수 있는 '콜드피치' 플랫폼을 기획하게 되었습니다. 이 플랫폼은 스타트업들이 고객의 피드백을 바탕으로 제품을 신속하게 개선하고 시장 반응을 빠르게 파악하는 것을 돕습니다.

 

 

기대효과

'콜드피치'는 스타트업이 제품과 서비스를 개선하고, 타깃층의 소비 목적에 부합하는 방향성을 제공할 수 있도록 돕습니다. 또한, 스타트업과 사용자 간의 원활한 소통을 통해 상호 신뢰를 형성하고, 스타트업이 문제를 예측하고 미리 솔루션을 준비할 수 있는 능동적인 기업으로의 전환을 지원합니다.

 

 

서비스 다이어그램
시퀀스 다이어그램
ER 다이어그램

데이터 베이스 정규화

- 데이터베이스 정규화는 데이터베이스 설계 과정에서 중복성을 최소화하고 데이터 일관성과 효율성을 향상시키기 위한 방법론이다. 정규화는 관계형 데이터베이스에서 주로 사용되며, 여러 개의 테이블로 데이터를 분할하고 연결하여 데이터 중복을 제거하는 과정을 거친다. 이를 통해 데이터의 저장 공간을 절약하고 데이터 조작 작업을 효율적으로 수행할 수 있다.

 

정규화 목적

  • 데이터 중복 제거: 정규화를 통해 테이블을 분할하고, 중복된 데이터를 제거하여 데이터의 일관성과 정확성을 유지한다. 중복된 데이터가 없으면 데이터베이스의 크기를 줄이고 데이터의 일관성을 향상시킬 수 있다.
  • 데이터 일관성 유지: 정규화를 통해 각 테이블의 구조를 단순화하고 데이터의 종속성을 명확하게 정의함으로써 데이터의 일관성을 유지할 수 있다. 갱신 이상을 방지하고, 데이터 일관성을 유지함으로써 데이터의 정확성과 신뢰성을 향상시킨다.
  • 쿼리 성능 향상: 정규화를 통해 테이블이 작고 명확하게 구성되면 쿼리의 실행 속도를 향상시킬 수 있다. 작은 테이블에 대한 조인 연산이 간단해질 수 있다.

 

 

이상 현상

- 정규화를 거치지 않은 데이터베이스가 가지는 불필요하게 중복된 데이터로 인한 릴레이션 조작 시 겪는 에러 현상

  • 삽입 이상(Insertion Anomaly): 데이터를 삽입할 때, 필요한 정보의 부분적인 내용만 제공되어 데이터가 완전하지 않는 경우 발생한다. 예를 들어, 주문 정보와 함께 고객 정보를 입력하지 않으면 주문 정보를 삽입할 수 없는 경우가 있다.
  • 갱신 이상(Update Anomaly): 데이터를 갱신할 때, 일부 튜플만 갱신되어 데이터의 일관성이 깨지는 경우 발생한다. 예를 들어, 동일한 고객이 여러 번 등장하는 경우 고객 정보를 갱신할 때 모든 튜플을 일괄적으로 갱신하지 않으면 데이터 불일치가 발생할 수 있다.
  • 삭제 이상(Deletion Anomaly): 데이터를 삭제할 때, 필요한 정보가 함께 삭제되어 데이터 손실이 발생하는 경우 발생한다. 예를 들어, 고객과 관련된 주문 정보를 모두 삭제하면 해당 고객 정보도 함께 삭제되는 경우가 있다.

 

정규화 과정

  1. 1차 정규화 (1NF): 중복된 데이터를 제거하고 각 테이블이 유일한 기본 키(PK)로 식별될 수 있도록 분할
    • 학생(학번, 이름, 전화번호) ▼
      • 학생(학번, 이름), 연락처(학번, 전화번호)
  2. 2차 정규화 (2NF): 부분적 종속성을 제거하기 위해 테이블 세분화
    • 학생(학번, 이름, 학과, 교수) ▼
      • 학생(학번, 이름, 학과), 교수(학번, 교수)
  3. 3차 정규화 (3NF): 이행적 종속성을 제거하기 위해 테이블 추가 세분화
    • 주문(주문번호, 고객번호, 고객이름, 상품번호, 상품이름, 상품가격) ▼
      • 주문(주문번호, 고객번호, 상품번호)
      • 고객(고객번호, 고객이름)
      • 상품(상품번호, 상품이름, 상품가격)
  4. 보이스-코드 정규화 (BCNF): 결정자 종속성을 해소하기 위해 테이블 분할
    • 학생(학번, 과목, 교수이름) ▼
      • 학생(학번, 지도교수)
      • 교수(교수이름, 과목)
  5. 4차 정규화 (4NF): 다중 값 종속성을 제거하기 위해 테이블 분할
    • 학생(학번, 과목, 취미) ▼
      • 학생(학번, 과목)
      • 취미(학번, 취미)
  6. 5차 정규화 (5NF): 조인 종속성을 해소하기 위해 테이블 분할
    • 주문(주문번호, 고객번호, 고객이름, 상품번호, 상품이름, 수량) ▼
      • 주문(주문번호, 고객번호, 수량)
      • 고객(고객번호, 고객이름)
      • 상품(상품번호, 상품이름)
      • 주문수량(주문번호, 수량)

 

이렇게 데이터베이스 정규화는 위의 과정을 순차적으로 진행하여 데이터 중복을 최소화하고 데이터의 일관성과 효율성을 높이는 작업을 수행한다. 각 단계에서 필요한 테이블 분할은 데이터의 특성과 관계형 데이터베이스의 설계 요구사항에 따라 달라질 수 있다.

 

 

 

 

 

 

'ETC' 카테고리의 다른 글

[Swagger] 스웨거 사용법 (Spring Boot)  (0) 2023.05.30
[DB] 데이터베이스 덤프 (with MySql Workbench)  (0) 2023.05.18
[Java] BufferedReader, BufferedWriter  (1) 2023.05.15
[OOP] SOLID 원칙  (1) 2023.05.12
[Java] 메모리 구조  (1) 2023.05.08

Swagger

- Swagger는 API 문서화와 API 개발을 지원하기 위한 오픈 소스 프레임워크이다. Swagger를 사용하면 API를 쉽게 문서화하고 시각적으로 표현할 수 있으며, 클라이언트 개발자들이 API를 쉽게 이해하고 테스트할 수 있다.

 

주요 기능

  • API 문서화 :  API 엔드포인트, 매개변수, 요청 및 응답 형식, 인증 등 API에 대한 자세한 문서를 작성할 수 있다.
  • 자동화된 문서 생성 :  소스 코드에 주석을 추가하여 API에 대한 문서를 자동으로 생성할 수 있다. 이를 통해 개발자는 API 변경 시 문서를 일일이 업데이트할 필요 없이 최신 정보를 유지할 수 있다.
  • 시각화된 API UI :  Swagger UI를 통해 API를 시각적으로 표현한다. Swagger UI는 사용자가 API를 쉽게 테스트하고 결과를 확인할 수 있는 인터페이스를 제공한다. 개발자들은 Swagger UI를 통해 API 엔드포인트를 호출하고 요청 및 응답을 확인할 수 있다.
  • API 테스트 :  Swagger UI를 통해 사용자는 API를 직접 테스트할 수 있다. API에 대한 입력 필드를 제공하고, 사용자가 필요한 매개변수를 설정하고 요청을 보낼 수 있다. 그리고 응답 결과를 확인할 수 있다.
  • 클라이언트 코드 생성 :  API 정의를 기반으로 클라이언트 코드를 자동으로 생성할 수 있다. 이를 통해 클라이언트 개발자는 API를 더 쉽게 호출하고 사용할 수 있다.

 

평소 Postman을 주로 사용해왔지만, 이번 팀 프로젝트에서 팀원들이 swagger를 사용하는 것을 보고 배워보고 싶었다.

 

사용법

1. 의존성 추가 ( build.gradle )

//swager
    implementation 'io.springfox:springfox-boot-starter:3.0.0'
    implementation 'io.springfox:springfox-swagger-ui:3.0.0'

 

2. Docket의 Bean 등록을 위한 클래스 생성 ( SwaggerConfig.java)

@EnableSwagger2
@Configuration
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.OAS_30)
                .securityContexts(Collections.singletonList(securityContext()))
                .securitySchemes(List.of(apiKey()))
                .select()
                .paths(PathSelectors.any())
                .build().apiInfo(apiInfo());
    }

    //이후 권한인증 확인을 위해서
    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .build();
    }

    //이후 권한인증 확인을 위해서
    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return List.of(new SecurityReference("Authorization", authorizationScopes));
    }

    private ApiKey apiKey() {
        return new ApiKey("Authorization", "Authorization", "header");
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("ColdPitch API")
                .description("콜드피치 API 명세")
                .version("1.0.0")
                .build();
    }
}

 

3. 컨트롤러 작성

- 현재 프로젝트의 기능에 포함되는 사업자 등록번호 진위검증 컨트롤러 

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/company")
public class CompanyRegistrationController {
    private final CompanyRegistrationService companyRegistrationService;

    @PostMapping("/validate")
    @Operation(summary = "사업자 등록번호 진위검증")
    public ResponseEntity<CompanyRegistrationDto> validate(@RequestBody CompanyRegistrationDto companyRegistrationDto, BindingResult result) {
        if (result.hasErrors()) {
            throw new CustomException(ErrorCode.POST_BAD_REQUEST);
        }

        boolean isValid = companyRegistrationService.validateAndSaveCompanyRegistration(companyRegistrationDto);

        if (!isValid) {
            throw new CustomException(ErrorCode.COMPANY_NOT_FOUND);
        }

        return ResponseEntity.ok(companyRegistrationDto);
    }
}

 

4. 테스트

- Try it out 이후, 서버에서 요청하는 데이터를 형식에 맞게 request에 넣은 후 execute

 

5. 결과 확인 

- 간단하게 워크벤치에서 확인해보니 해당 요청을 잘  받아서 무사히 db에 저장된 것을 볼 수 있다.

 

'ETC' 카테고리의 다른 글

[DB] 데이터베이스 정규화  (0) 2023.06.02
[DB] 데이터베이스 덤프 (with MySql Workbench)  (0) 2023.05.18
[Java] BufferedReader, BufferedWriter  (1) 2023.05.15
[OOP] SOLID 원칙  (1) 2023.05.12
[Java] 메모리 구조  (1) 2023.05.08

Database Dump

- '데이터베이스 덤프'란 데이터베이스의 내용과 구조를 외부 파일로 저장하는 것을 말한다. 저장된 덤프 파일은 데이터베이스의 백업, 복구 또는 데이터 이전 등에 사용될 수 있다. 이러한 덤프 파일은 일반적으로 두 가지 형식 중 하나로 생성된다.

  • SQL 텍스트 파일 : 이 형식의 덤프는 일반 텍스트 파일로, 데이터베이스의 구조를 생성하는 SQL 명령문(테이블 생성 등)과 데이터를 삽입하는 SQL 명령문(INSERT 등)을 포함한다. 해당 파일은 사람이 읽고 수정할 수 있으며, 어떠한 DBMS에서도 사용할 수 있는 장점이 있다.
  • 이진 파일 : 이 형식의 덤프는 데이터베이스의 내용을 이진 형식으로 저장한다. 일반적으로 특정 데이터베이스 관리 시스템에 종속적이다. SQL 텍스트 형식에 비해 데이터를 효율적으로 저장하고 복구할 수 있으며, 크기가 작다는 장점을 가지고 있다.

 

 

MySql Workbench에서 DB 덤프

 

<- 해당 테이블을  백업 및 복구
( 'dubmp-test-db'.'dump_user' )

 

 

 

 

1. 데이터 백업 (Data Export)

I ) Server - Data Export 를 통해 덤프 파일 생성

 

II ) 스키마 선택 - 테이블 선택 - 덤프 파일 생성 위치 선택 - Start Export

기본 경로) C:\Users\owner\OneDrive\문서\dumps

 

III ) 덤프 파일 생성 

 

VI ) SQL 텍스트로 작성된 덤프 파일

 

 

2. 데이터 복구 (Data Import)

I ) Server - Data Import 를 통해 덤프 파일 백업

 

II ) 저장된 덤프 파일 경로 선택 - Start Import

 

III) 복구된 테이블 구조와 데이터 확인

 

'ETC' 카테고리의 다른 글

[DB] 데이터베이스 정규화  (0) 2023.06.02
[Swagger] 스웨거 사용법 (Spring Boot)  (0) 2023.05.30
[Java] BufferedReader, BufferedWriter  (1) 2023.05.15
[OOP] SOLID 원칙  (1) 2023.05.12
[Java] 메모리 구조  (1) 2023.05.08

BufferedReader, BufferedWriter

자바에서 BufferedReader와 BufferedWriter는 입출력 스트림에서 높은 효율성을 가진다. 이러한 클래스들은 버퍼를 사용하여 데이터를 한 번에 일정량씩 처리함으로써 입출력 시점에서 많은 이점을 제공한다.

 

 

1. BufferedReader

'BufferedReader'는 Reader 클래스를 확장하는 클래스로, 텍스트 입력을 처리하도록 설계되어 있다. 내부적으로 입력 스트림을 버퍼에 저장함으로써 파일이나 네트워크에서 데이터를 읽는 효율성을 크게 향상시킨다. 버퍼링은 데이터를 작은 조각들로 한 번에 처리하는 대신, 더 큰 블록으로 처리하도록 해서 입출력 작업의 효율성을 향상시킨다.

  • read() : 입력 스트림에서 단일 문자를 읽음
  • readLine() : 입력 스트림에서 한 줄을 읽음
  • close() : BufferedReader를 닫고, 관련된 모든 시스템 리소스를 해제

 

2. BufferedWriter

'BufferedWriter'는 Writer 클래스를 확장하는 클래스로, 텍스트 출력을 처리하도록 설계되어 있다. 내부적으로 출력 스트림을 버퍼에 저장함으로써 파일이나 네트워크에 데이터를 쓰는 효율성을 크게 향상시킨다.

  • write(int c) : 단일 문자를 출력 스트림에 씀
  • write(String s) : 문자열을 출력 스트림에 씀
  • newLine() : 줄바꿈 문자를 출력 스트림에 씀
  • flush() : 버퍼에 있는 모든 출력을 강제로 씀
  • close() : BufferedWriter를 닫고, 관련된 모든 시스템 리소스를 해제

 

장점

  • 효율성 : ‘println()’ 등의 메서드들이 문자 단위로 데이터를 처리하는 방식이다. 그것에 비해 버퍼를 통한 여러 문자를 한 번에 읽고 쓰는 해당 방식은 작업의 효율성이 높인다.
  • 성능 : 버퍼링을 사용하여 시스템 자원을 적게 사용하고 실행 속도를 빠르게 만들 수 있다.
  • 사용자 정의 : 해당 클래스들은 더 많은 사용자 정의 옵션을 제공한다. 예를 들어, 버퍼의 크기를 조절하여 입출력 성능을 최적화할 수 있다.
    - 버퍼 크기 조절 : 생성자를 통해 조절 가능
             new BufferedReader(fileReader, bufferSize)
             new BufferedWriter(fileWriter, bufferSize)

단점

  • 해당 클래스들을 사용함으로써 상황에 따라 오히려 코드가 더 복잡해질 수 있다. 초기화 및 예외 처리가 필요하며, 추가적인 코드를 작성해야 할 수도 있다.
  • 자원 관리 : BufferedReader와 BufferedWriter를 사용할 때는 스트림을 명시적으로 닫아야 한다. 그렇지 않으면 리소스 누수가 발생할 가능성이 있다. (close())

이 외에도 버퍼링 지연 등의 단점 등이 있을 수 있다. 그렇기에 대용량의 입출력 스트림 작업이 이루어지지 않는 간단한 콘솔 출력의 경우 print()가 방법이 될 수 있다.

※ Buffer
  Java에서 Buffer는 일반적으로 입출력 작업을 보조하는 임시 저장공간을 말한다. 버퍼는 입출력 작업을 최적화하는 데 사용되며, 특히 데이터를 작은 단위로 여러 번 읽고 쓰는 것보다는, 큰 덩어리로 한 번에 읽고 쓰는 것이 효율적이기 때문에 버퍼링이 필요하다.
  예를 들어, 파일에서 데이터를 한 번에 한 바이트씩 읽는 것은 매우 비효율적일 수 있다. 이렇게 하면 매 바이트마다 디스크에 접근해야 하므로 시간이 많이 소요된다. 이런 경우에 버퍼링을 사용하면, 데이터를 한 번에 큰 덩어리로 읽을 수 있으므로 훨씬 더 빠른 성능을 얻을 수 있다.

'ETC' 카테고리의 다른 글

[Swagger] 스웨거 사용법 (Spring Boot)  (0) 2023.05.30
[DB] 데이터베이스 덤프 (with MySql Workbench)  (0) 2023.05.18
[OOP] SOLID 원칙  (1) 2023.05.12
[Java] 메모리 구조  (1) 2023.05.08
[Java] 자바 접근 제어자  (0) 2023.04.27

SOLID

SOLID는 객체 지향 프로그래밍과 설계에 대한 다섯 가지 핵심 원칙을 나타낸다. 이 원칙들은 코드의 유지 보수성, 가독성, 확장성을 향상시키는 데 큰 도움이 된다.

 

S : 단일 책임 원칙 (Single Responsibility Principle, SRP)

- 클래스는 단 하나의 책임만 가져야 한다는 원칙이다. 다시 말해, 클래스가 변경되어야 하는 이유는 하나뿐이어야 한다. 이 원칙을 따르면 코드의 결합도가 낮아지고 각 클래스가 자신의 역할에 집중할 수 있어 코드가 더욱 명확하고 이해하기 쉬워진다.

 

1. 잘못된 예시

public class User {
	private String name;
	private String email;

	// 생성자, getter, setter 등 생략

	public void saveUser() {
		// 사용자 정보 데이터베이스 저장 로직
	}

	public String renderUser() {
		// 사용자 정보 HTML 렌더링 로직
		return "<div>" + name + " : " + email + "</div>";
	}
}

위의 예에서 'User' 클래스는 사용자 정보를 저장하는 책임, 데이터베이스에 저장하는 책임, HTML로 렌더링하는 책임을 모두 가지고 있다. 이를 SRP에 따라 분리하면 다음과 같이 될 수 있다.

 

2. SRP 적용 예시

먼저, 'User' 클래스는 사용자 정보를 저장하는 역할만 수행하도록 한다.

public class User {
    private String name;
    private String email;

    // 생성자, getter, setter 등은 생략
}

'UserRepository' 클래스는 사용자 정보를 데이터베이스에 저장하는 역할을 수행하도록 한다.

public class UserRepository {
    public void saveUser(User user) {
        // 사용자 정보 데이터베이스 저장 로직
    }
}

'UserRenderer' 클래스는 사용자 정보를 HTML로 렌더링하는 역할을 수행하도록 한다.

public class UserRenderer {
    public String renderUser(User user) {
        // 사용자 정보를 HTML로 렌더링하는 로직
        return "<div>" + user.getName() + " : " + user.getEmail() + "</div>";
    }
}

이제 'User' 클래스가 변경되어도 사용자 정보의 저장 방식이나 렌더링 방식에는 영향을 미치지 않으며, 반대의 경우도 마찬가지이다다. 이러한 방식은 시스템의 각 부분을 독립적으로 개발하고 테스트하는 데도 유용하다.

 

 

 

O : 개방-폐쇄 원칙 (Open-Closed Principle, OCP)

- 클래스는 확장에 대해 열려 있어야 하고, 변경에 대해서는 닫혀 있어야 한다는 원칙이다. , 새로운 기능을 추가할 때 기존 코드를 변경하지 않고 새로운 코드를 추가함으로써 기능을 확장할 수 있어야 한다.

 

1. 잘못된 예시

- 도형의 면적을 계산하는 클래스가 있다고 가정

public class AreaCalculator {
    public double calculateRectangleArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }

    public double calculateCircleArea(Circle circle) {
        return Math.PI * Math.pow(circle.radius, 2);
    }
}

이 코드는 새로운 도형이 추가될 때마다 AreaCalculator 클래스에 새로운 메소드를 추가해야 한다. 따라서 이 코드는 OCP를 위반하고 있다.

이 문제를 해결하기 위해, 각 도형 클래스가 자신의 면적을 계산하는 메소드를 가지도록 하고, AreaCalculator 클래스는 이 메소드를 호출하도록 변경할 수 있다.

 

2. OCP 적용 예시

public interface Shape {
    double calculateArea();
}
public class Rectangle implements Shape {
    public double width;
    public double height;

    public double calculateArea() {
        return width * height;
    }
}
public class Circle implements Shape {
    public double radius;

    public double calculateArea() {
        return Math.PI * Math.pow(radius, 2);
    }
}
public class AreaCalculator {
    public double calculateShapeArea(Shape shape) {
        return shape.calculateArea();
    }
}

이제 AreaCalculator 클래스는 어떤 도형의 면적도 계산할 수 있으며, 새로운 도형이 추가되더라도 AreaCalculator 클래스를 수정할 필요가 없다. 새로운 도형은 단순히 Shape 인터페이스를 구현하면 됩니다. 이로써 OCP를 준수하게 된다.

 

 

L : 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)

부모 클래스를 자식 클래스로 대체할 수 있어야 한다는 원칙이다. , 상속받는 클래스는 상속하는 클래스를 완전히 대체할 수 있어야 한다. 이 원칙을 준수하면 부모 클래스를 사용하는 코드를 변경하지 않고도 자식 클래스를 사용할 수 있다.

 

1. 잘못된 예시

Bird 클래스와 이를 상속받은 Penguin 클래스가 있다고 가정

public class Bird {
    public void fly() {
        System.out.println("Bird is flying.");
    }
}
public class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins can't fly.");
    }
}

이 코드는 Penguin은 Bird의 하위 타입이지만, Bird가 할 수 있는 행동인 fly() 를 Penguin이 수행할 수 없기 때문이다. (펭귄은 날 수 없기 때문) 이 경우 Bird 타입의 참조를 사용하여 fly() 메소드를 호출하면, 이 참조가 실제로 Penguin 인스턴스를 가리키고 있다면 예외가 발생하게 된다.

이 문제를 해결하려면, 공통적인 행동만을 가진 상위 클래스를 정의하고, 특정 행동은 하위 클래스에서 정의하는 것이 좋다.

 

2. LSP 적용 예시

public class Bird {
    public void eat() {
        System.out.println("새가 밥을 먹는다.");
    }
}
public class FlyingBird extends Bird {
    public void fly() {
        System.out.println("새가 난다.");
    }
}
public class Penguin extends Bird {
    // 펭귄은 날 수 없기 때문에, fly() 를 오버라이딩하지 않는다.
}

이제 Bird 타입의 참조는 항상 eat() 메소드를 호출할 수 있으며, FlyingBird 타입의 참조는 fly() 메소드를 호출할 수 있다. Penguin은 이제 공통 기능인 eat()은 정의하지만, fly()를 정의하지 않는다.

 

 

I : 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)

클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다는 원칙이다. 즉, 한 클래스는 자신이 필요로 하는 메소드만을 가진 인터페이스에만 의존해야 한다. 이렇게 하면 클래스간의 의존성이 낮아져 코드의 유연성이 증가하고, 결합도가 낮아진다.

 

1. 잘못된 예시

Animal인터페이스가 fly(), swim(), run() 메소드를 모두 가지고 있다고 가정

public interface Animal {
    void fly();
    void swim();
    void run();
}
public class Dog implements Animal {
    @Override
    public void fly() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void swim() {
        // Swim 로직
    }

    @Override
    public void run() {
        // Run 로직
    }
}

위 코드에서 Dog 클래스는 Animal 인터페이스를 구현하지만, Dog는 fly() 메소드를 사용할 수 없다. 이 문제를 해결하기 위해, 각 행동에 대해 별도의 인터페이스를 만드는 것이 좋다.

 

2. ISP 적용 예시

public interface Flyable {
    void fly();
}
public interface Swimmable {
    void swim();
}
public interface Runnable {
    void run();
}
public class Dog implements Swimmable, Runnable {
    @Override
    public void swim() {
        // Swim 로직
    }

    @Override
    public void run() {
        // Run 로직
    }
}

 

이렇게 인터페이스를 분리하면, 각 클래스는 자신이 필요로 하는 메소드만을 가진 인터페이스를 구현하게 된다.

 

 

D : 의존성 역전 원칙 (Dependency Inversion Principle, DIP)

상위 모듈은 하위 모듈에 의존해서는 안 된다는 원칙이다. 둘 다 추상화에 의존해야 한다. 이 원칙은 구체적인 구현이 아닌 인터페이스에 의존하도록 코드를 작성하면, 코드의 재사용성과 테스트 용이성이 향상된다.

 

1. 잘못된 예시

ZooKeeper 클래스가 Lion 클래스에 의존하는 경우 가정

class Lion {
    public void feed() {
        System.out.println("사자한테 밥 주기");
    }
}
class ZooKeeper {
    private Lion lion;

    public ZooKeeper() {
        this.lion = new Lion();
    }

    public void feedLion() {
        lion.feed();
    }
}

이 코드는 DIP를 위반하고 있다. 왜냐하면 ZooKeeper 클래스가 구체적인 Lion 클래스에 의존하고 있기 때문이다. 만약 ZooKeeper가 Tiger나 다른 동물들에게 밥을 먹이려면 ZooKeeper 클래스도 수정해야 한다.이 문제를 해결하기 위해, Animal이라는 인터페이스를 만들고 ZooKeeper 클래스가 이 인터페이스에 의존하도록 변경할 수 있다.

 

2. DIP 적용 예시

interface Animal {
    void feed();
}
class Lion implements Animal {
    public void feed() {
        System.out.println("사자한테 밥 주기");
    }
}
class Tiger implements Animal {
    public void feed() {
        System.out.println("호랑이한테 밥 주기");
    }
}
class ZooKeeper {
    private Animal animal;

    public ZooKeeper(Animal animal) {
        this.animal = animal;
    }

    public void feedAnimal() {
        animal.feed();
    }
}

이제 ZooKeeper는 Animal 인터페이스에 의존하므로, 어떤 동물이든 밥을 먹일 수 있다.

 

 

 

정    리

이 원칙들은 모두 서로 연결되어 있으며, 하나의 원칙이 깨질 경우 다른 원칙들도 깨질 가능성이 높아진다. 이 원칙들을 잘 이해하고 적용하면 소프트웨어 설계의 품질을 크게 향상시킬 수 있다.

 

예를 들어, 만약 한 클래스가 여러 가지 역할을 맡고 있다면 (SRP 위반), 그 클래스를 수정하는 것이 매우 어려워질 수 있다. 만약 기능을 추가하거나 변경하려면, 그 클래스의 모든 역할에 대해 이해해야 하며, 한 역할에 대한 변경이 다른 역할에 부작용을 일으킬 수 있다.

 

또한, 만약 클래스가 자신이 사용하지 않는 메서드를 가진 인터페이스에 의존하고 있다면 (ISP 위반), 그 클래스는 필요 이상으로 많은 의존성을 가지게 되어 복잡성이 증가하고, 코드 변경이 어려워질 수 있다.

 

SOLID 원칙은 디자인 패턴과 밀접한 관련이 있다. 예를 들어, '전략 패턴'OCP를 준수하는 데 도움이 된다. '템플릿 메소드 패턴'LSP를 준수하는 데 도움이 된다. '팩토리 메소드 패턴''의존성 주입'DIP를 준수하는 데 도움이 된다.

 

이 원칙들은 모든 상황에 적용되는 법칙은 아니지만, 대체로 코드 품질을 향상시키는 데 도움이 되므로 소프트웨어 개발자들은 이 원칙들을 잘 이해하고 있어야 한다.

 

'ETC' 카테고리의 다른 글

[DB] 데이터베이스 덤프 (with MySql Workbench)  (0) 2023.05.18
[Java] BufferedReader, BufferedWriter  (1) 2023.05.15
[Java] 메모리 구조  (1) 2023.05.08
[Java] 자바 접근 제어자  (0) 2023.04.27
[Git] Merge의 개념과 주의사항  (0) 2023.04.21

Java의 메모리 구조

- 자바에서 메모리 구조는 크게 네 가지 영역으로 나뉜다.

- 스택(Stack), (Heap), 메서드(Method Area), 네이티브 메서드 스택(Native Method Stack)

출처 (https://ict-nroo.tistory.com/19)

 

 

1. 스택 (Stack)

  • 스택 메모리는 각 스레드에게 할당되며, 지역 변수와 메서드 호출에 사용한다.
  • 메서드가 호출되면 해당 메서드의 프레임이 스택에 Push되고, 메서드가 종료되면 해당 프레임이 Pop된다.
  • 메서드 호출 순서에 따라 LIFO(Last-In-First-Out) 방식으로 동작한다.
  • 상대적으로 빠르지만 크기가 제한적이기 때문에 큰 데이터를 저장하기에 적합하지는 않다.

 

 

2. 힙 (Heap)

  • 힙 메모리는 동적으로 할당된 객체를 저장하는 공간이다.
  • 자바에서 객체를 생성하면 힙 영역에 할당되며, 가비지 컬렉션(GC)에 의해 더 이상 사용하지 않는 객체가 정리된다.
  • 힙 영역의 크기는 JVM의 시작 시점에 결정되며, 필요에 따라 런타임 동안 조정될 수 있다.
  • 힙 메모리는 스택 메모리보다 느리고 크기가 크기 때문에, 대규모 데이터를 저장하고 관리하는 데 사용된다.

 

3. 메서드 영역 (Method Area)

  • 메서드 영역은 클래스 정보를 저장하는 공간이다.
  • 클래스의 메타데이터, 클래스 변수, 정적 변수, 상수 데이터, 메서드 정보, 생성자 정보 등이 저장된다.
  • 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있다.
  • 클래스 로더에 의해 로드된 클래스 정보가 이 영역에 할당된다.
static
- static이 붙은 자료형인 정적 멤버변수가 해당 메서드 영역에 저장된다.
- 프로그램의 종료 시점까지 메모리에 남아있기 때문에, 시간과 장소에 상관없이 어디서든 사용 가능한 이유이다.
- 따라서 static을 무분별하게 많이 사용하다 보면 메모리가 부족할 우려가 있어, 꼭 필요한 변수에만 사용해야 한다.

 

4. 네이티브 메서드 스택 (Native Method Stack)

  • 네이티브 메서드 스택은 자바가 아닌 다른 언어로 작성된 네이티브 메서드를 위한 메모리 공간이다.
  • JNI(Java Native Interface)를 통해 네이티브 코드와 상호작용하는 데 사용된다.
  • 네이티브 메서드 스택은 스택 메모리와 비슷한 방식으로 동작하지만, 네이티브 메서드를 위한 공간이라는 점에서 차이가 있다.

 

 

 

이러한 각 영역은 서로 다른 목적을 가지고 있지만, JVM에서 연계하여 동작한다. Stack과 Heap 영역은 객체 참조를 통해 서로 상호작용하며, 메서드 영역은 클래스 정보를 저장하고, 네이티브 메서드 스택은 외부 코드와의 상호작용을 위해 사용된다. 이러한 메모리 구조는 자바 프로그램의 성능안정성을 보장하는데 중요한 역할을  수행한다.

 

'ETC' 카테고리의 다른 글

[Java] BufferedReader, BufferedWriter  (1) 2023.05.15
[OOP] SOLID 원칙  (1) 2023.05.12
[Java] 자바 접근 제어자  (0) 2023.04.27
[Git] Merge의 개념과 주의사항  (0) 2023.04.21
[Git] 브랜치 개념과 사용법  (0) 2023.04.20

 

접근 제어자 (Access Modifiers)

- 자바에서 클래스, 인터페이스, 메소드, 변수 등의 가시성과 접근 범위를 제한하기 위해 접근 제어자를 사용한다. 이러한 접근 제어자를 적절히 사용하여 코드의 캡슐화, 보안성, 유지 관리성 등을 높일 수 있다. 그렇기에 클래스와 메소드의 설계와 구현을 잘 고려하여 적절한 접근 제어자를 선택하는 것이 중요하다.

 

1. public

 

  • 자바의 'public' 접근 제어자는 클래스, 인터페이스, 메소드, 변수 등의 요소에 대해 외부에서 자유롭게 접근할 수 있도록 허용한다. 이 접근 제어자가 지정된 요소는 어떤 클래스나 패키지에서든 접근할 수 있다.
  • public 접근 제어자를 사용하면 해당 요소에 대한 접근 제한이 없기 때문에, 클래스 간의 상호 작용 및 데이터 공유를 쉽게 할 수 있다. 그러나 이를 남용하면 캡슐화와 보안성이 저하되어 클래스의 내부 구현을 외부로 노출시킬 위험이 있다.
  • 따라서 클래스 내부 구현의 상세 사항을 숨기고, 인터페이스를 통해 외부와 상호 작용할 수 있게 설계하는 것이 좋다. 이를 위해 꼭 필요한 경우에만 public 접근 제어자를 사용하고, 그 외에는 더 제한적인 접근 제어자를 사용하여 적절한 가시성을 유지하는 것이 좋다.

 

 

2. private

 

  • 자바의 'private' 접근 제어자는 클래스의 멤버 변수와 메소드에 대한 접근을 해당 클래스 내부로만 제한한다. 즉, 클래스 외부에서는 private로 지정된 요소에 직접 접근할 수 없습니다.
  • 클래스의 내부 구현을 외부로부터 숨기고 캡슐화를 강화할 수 있습니다.
  • private 접근 제어자를 사용하면, 클래스의 내부 상태를 보호하고 외부에서 잘못된 수정이나 사용을 방지할 수 있다.
  • 대신, private 멤버 변수에 접근하거나 값을 변경하기 위해 public 또는 protected 메소드를 통해 간접적으로 접근할 수 있도록 설계하는 것이 좋다. (getter, setter 메소드)
  • 주로 클래스의 멤버 변수와 메소드에 사용

 

 

3. protected

 

  • 자바의 'protected' 접근 제어자는 클래스의 멤버 변수와 메소드에 대한 접근을 동일한 패키지 내의 다른 클래스와 해당 클래스를 상속받은 다른 패키지의 클래스에게만 허용한다.
  • 상속 관계에서 부모 클래스의 멤버를 하위 클래스에서 사용하도록 허용하려는 경우에 주로 사용된다.
  • protected 접근 제어자를 사용하면, 하위 클래스가 부모 클래스의 멤버를 사용하고 확장할 수 있으며, 동시에 외부에서의 접근을 제한하여 캡슐화를 유지할 수 있다. (자바의 다형성과 상속 개념과 밀접하게 관련)
  • 상속 관계를 고려한 클래스 설계에서 유용하게 사용된다. 하위 클래스에서 부모 클래스의 멤버에 접근할 수 있게 하려면 protected를 사용하고, 그렇지 않은 경우에는 더 제한적인 접근 제어자를 사용하여 적절한 가시성을 유지하는 것이 좋다.

 

4. default

 

  • 자바에서 'default' 접근 제어자는 클래스, 인터페이스, 필드, 메소드 또는 생성자에 명시적으로 접근 제어자를 지정하지 않았을 때 적용된다.
  • 같은 패키지 내의 클래스에서만 접근 가능하다. 다른 패키지의 클래스에서는 접근할 수 없다.
  • 동일한 패키지 내에서는 public, protected와 마찬가지로 접근할 수 있지만, 다른 패키지에서는 접근할 수 없으므로, public 및 protected보다 가시성이 제한적이다.
  • private보다는 가시성이 더 넓다. (private은 오직 선언된 클래스 내에서만 접근)
  • default 접근 제어자는 같은 패키지 내에서만 공유해야 하는 기능이나 데이터를 구현할 때 유용하며, 다른 패키지에 있는 클래스가 이러한 구현 세부 사항을 수정하거나 사용하는 것을 방지하여 캡슐화를 유지할 수 있습니다.

 

가시성 캡슐화
자바의 가시성과 캡슐화는 객체 지향 프로그래밍의 핵심 원칙으로, 클래스의 구현 세부 사항을 숨기고 외부에서의 접근을 제한하여 코드의 안정성과 유지 보수성을 높이는 데 꼭 필요하다.

- 가시성(Visibility) : 가시성은 클래스, 인터페이스, 필드, 메소드 및 생성자의 접근 가능성을 제어한다. 접근 제어자를 통해 각각 다른 범위의 접근 가능성을 제공하므로, 적절한 가시성을 선택하여 클래스의 내부 데이터와 기능을 외부로부터 보호할 수 있다.

- 캡슐화(Encapsulation) : 캡슐화는 객체의 상태를 나타내는 필드와 상태를 조작하는 메소드를 하나의 단위로 결합하는 프로세스이다. 캡슐화를 통해 클래스의 내부 구현을 숨기고, 데이터를 직접 조작하는 대신 메소드를 통해 간접적으로 조작할 수 있게 함으로써 외부 코드와의 결합도를 낮추고, 코드의 유지 보수성과 안정성을 높인다.

 

 

 

'ETC' 카테고리의 다른 글

[OOP] SOLID 원칙  (1) 2023.05.12
[Java] 메모리 구조  (1) 2023.05.08
[Git] Merge의 개념과 주의사항  (0) 2023.04.21
[Git] 브랜치 개념과 사용법  (0) 2023.04.20
[Git] 커밋 메시지 작성 요령  (0) 2023.04.20

+ Recent posts