[20230711] 클라우드 플랫폼 기반 웹서비스 개발자 양성 과정 4일차.

양희정·2023년 7월 11일
0

오늘의 과정 목록

  • 주차장 관리 시스템 코드 작성
  • 객체 지향 프로그래밍 / SOLID 원칙

주차장 관리 시스템

필요한 코드)
주차 등록
주차 확인
출차

관리 데이터)
차량번호, 차종, 주차공간, 층수, 입차시간, 출차시간, 요금, 차량수

층 수 : 10층 | 층 당 : 30대
경차 : 1000원 | 중대형 : 1500원 | SUV : 2000원

  • 코드를 짜기 전 결과를 먼저 생각하고 코드를 적는 것이 좋다.
  • 위의 데이터에서는 입차 기능의 결과가 중요하며 주차 공간이 채워져야 한다.
  • 비어 있는 주차 공간의 falres를 true 값으로 바군 뒤 입력받은 정보를 기록한다.

기본 베이스

public class ParkingApplication {
	public static void main(String[] arge) {
	// 주차 공간
    /*
    2차원 배열을 사용한다.
    10층에 각 30개의 공간 배열.
    주차가 되어 있는가 안 되어 있는가 
    - true와 false로 판단하기 위해 boolean을 사용.
    - true면 자리가 없고. false면 주차 공간이 있음.
    */
    boolean[][] parkingSpaces = new boolean[10][30];
    // 주차한 차량 번호
    String[][] registerNumberes = new String[10][30];
    // 입차 시간
    int[][] getInTimes = new int[10][30];
    // 차종 (지정할 차 종류가 정해져 있으므로 인트로 해도 무방함)
    String[][] types = new String[10][30];

2차원 배열

  • 위와 같은 케이스인 경우 일반 배열이 아닌 2차원 배열을 사용할 수 있다.
  • 예시)
    boolean[][] list = new boolean[3],[5];
    읽기 순서 : 좌->우
    3개의 배열을 먼저 만들고
    {a, b, c}
    이 a,b,c의 배열 하나하나에 5개의 요소가 들어있다.
    {{t,t,f,f,t}, {t,t,f,f,t}, {t,t,f,f,t}}
    이중 참조가 되어 있는 형태이며 복잡해서 잘 쓰이지는 않는다.

기능 구현

  • 전체 여유 공간
		int totalFreeSpace = 0; // 하나의 데이터 공간이기 때문에 일반 변수로 둠.
        // 2차원 for문이기 때문에 두 번 for문을 쓰게 됨. 3차원이면 3번.
		for(boolean[] layer: parkingSpaces) { 
			for(boolean space: layer) {
				if(!space) totalFreeSpace++; 
			}
		}
		System.out.println("여유 공간 :  " + totalFreeSpace);
        
/*
출력 결과)
여유 공간 : 300
*/
  • 층별 여유 공간
		int[] layerFreeSpaces = new int[10];
//		for(boolean[] layer: parkingSpaces) 
//		-> 몇 번째 층인지 알 수 없기 때문에 일반 for문을 사용해야 한다.
		for(int layer = 0; layer < parkingSpaces.length; layer++) {
        	// parkingSpaces에 있는 layer 인덱스를 지정한다. 즉 parkingSpaces[layer]은 하나의 배열
			for(boolean space: parkingSpaces[layer]) { 
				if(!space) layerFreeSpaces[layer]++; 
			}
		}
		
		for(int layer = 0; layer < layerFreeSpaces.length; layer++) {
			System.out.println(layer + 1 + "층 여유 공간 : " + layerFreeSpaces[layer]);
		}
		
		Scanner scanner = new Scanner(System.in);

/*
출력 결과)
1층 여유 공간 : 30
2층 여유 공간 : 30
3층 여유 공간 : 30
4층 여유 공간 : 30
5층 여유 공간 : 30
6층 여유 공간 : 30
7층 여유 공간 : 30
8층 여유 공간 : 30
9층 여유 공간 : 30
10층 여유 공간 : 30
*/

입차 (차량번호, 차종, 입차시간, 주차할 공간)

		System.out.print("차량 번호 : ");
		String registerNumber = scanner.nextLine(); 
		
		System.out.print("차종 : ");
		String type = scanner.nextLine();
		
		System.out.print("입차 시간 (0 ~ 24) : ");
		int getInTime = scanner.nextInt();
		
		System.out.print("주차 층 (0 ~ 9) : ");
		int parkingLayer = scanner.nextInt();
		
		System.out.print("주차 공간 (0 ~ 29) : ");
		int parkingSpace = scanner.nextInt();
 
 /*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
*/ 

검증

  • 입력 -> 입력 검증. 로직상에 문제나 범위 값이 아닌 값이 입력되어 생기는 문제 등등 다양한 문제의 원인들 때문에 입력 검증은 필수로 해야한다.

차량 번호 검증

		// .isBlank() : 빈 값을 받지 못하도록 하는 메서드. 사용하기 직전에 null인지 아닌지 체크가 필수이다.
        if(registerNumber != null && registerNumber.isBlank()) { 
			System.out.println("차랑번호를 반드시 입력하세요.");
			return;
		}
/*
차량 번호 : 
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
차랑번호를 반드시 입력하세요.
*/
  • String과 같이 앞글자가 대문자면 참조형 데이터 타입이라고 본다.
  • 변수에 주소값이 들어가기 때문에 주소가 저장되어 있다.
  • 기본형 데이터 타입이 가지고 있지 않는 null값을 가질 수 있다.
  • String은 원래 new를 사용해야 하지만 자주 쓰는 데이터 타입이기 때문에 ""로 대신하여 사용한다.
  • .isBlank() : 빈 값을 받지 못하도록 하는 메서드로서 사용하기 직전에 null인지 아닌지 체크가 필수이다. 체크하지 못하면 터질 수 있다.
  • 외부에서 데이터를 받아올 때 무조건 null인지 아닌지 확인 과정을 거쳐야 한다.
    if (registeNumber != null && registeNumber.isBlank()) 에서 사용된
    registeNumber != null 은 null이 아닌이란 뜻

차종 검증. 경차 또는 중대형 또는 SUV가 아니면 리턴.

		if(type != null && !type.equals("경차") && !type.equals("중대형") && !type.equals("SUV")) {
			System.out.println("경차, 중대형, SUV 중에 하나를 입력하세요.");
			return;
		}
/*
차량 번호 : 가123 12
차종 : BMW
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
경차, 중대형, SUV 중에 하나를 입력하세요.
*/
  • 논리 상으로는 type != null이 뒤에 와도 값은 같지만 실제로는 null 값이 먼저 걸러져야 하기 때문에 순서가 뒤로 가버리면 의미가 없어진다.

입차시간 검증 (0 ~ 24)

		if(getInTime < 0 || getInTime > 24) {
			System.out.println("입차 시간은 0 ~ 24 사이어야 합니다.");
			return;
		}
/*
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 222
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
입차 시간은 0 ~ 24 사이어야 합니다.
*/
  • 기본 타입인 int이기 때문에 null값을 가질 수 없어 null검증을 할 필요가 없다.

주차 층 검증

		if(parkingLayer < 0 || parkingLayer > 9) {
			System.out.println("주차 층은 0 ~ 9 사이어야 합니다.");
			return;
		}
        
/*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 50
주차 공간 (0 ~ 29) : 10
주차 층은 0 ~ 9 사이어야 합니다.
*/

주차 공간 검증

		if(parkingSpace < 0 || parkingSpace > 29) {
			System.out.println("주차 공간은 0 ~ 29 사이어야 합니다.");
			return;
		}
/*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 100
주차 공간은 0 ~ 29 사이어야 합니다.
*/

해당 주차 공간이 비었는지 검증

		if(parkingSpaces[parkingLayer]/*층*/[parkingSpace]/*공간*/) { // -> true일 때
			System.out.println("이미 주차된 공간입니다.");
			return;
		}

주차

		parkingSpaces[parkingLayer][parkingSpace] = true;
		registerNumberes[parkingLayer][parkingSpace] = registerNumber;
		getInTimes[parkingLayer][parkingSpace] = getInTime;
		types[parkingLayer][parkingSpace] = type;

주차 위치 확인

		scanner = new Scanner(System.in);
		System.out.print("확인할 차량 번호 : ");
		registerNumber = scanner.nextLine();

		if(registerNumber != null && registerNumber.isBlank()) { 
			System.out.println("차랑번호를 반드시 입력하세요.");
			return;
		}
/*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 
차랑번호를 반드시 입력하세요.
*/

		boolean found = false;
        // 2차 배열에는 무조건 2중 for문을 사용해야함.
		for(int layer = 0; layer < registerNumberes.length; layer++) { 
			// 인덱스 값이 필요해서 일반 for문을 사용함
            for(int space = 0; space < registerNumberes[layer].length; space++) { 
				
                // registerNumberes[layer][space] 문자열의 타입. 참조. null오류가 뜨게 된다. 
                // registerNumberes와 registerNumber의 위치를 바꿔도 된다. 
//				if(registerNumberes[layer][space].equals(registerNumber)) { 
//					System.out.println(layer + "층 " + space + "번에 위치합니다.");
//				}
				
				// 위에서 넘어왔기 때문에 null이 아님. null이 온다고 해도 오류가 생기지 않는다.
                if(registerNumber.equals(registerNumberes[layer][space])) { 
					System.out.println(layer + "층 " + space + "번에 위치합니다.");
					found = true; // 루프를 돌며 if문 조건에 맞는 값을 찾게 된다면 found 값이 true로 바뀐다.
					break; // 원하는 값을 찾게 되어 루프를 끝낸다.
				}
			}
			if (found) break; 
		}
		if(!found) {
			System.out.println("찾을 수 없는 차량 번호입니다.");
			return;
		}

/*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 가123 12
5층 10번에 위치합니다.
*/ 
/*
출력 결과2)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 123456
찾을 수 없는 차량 번호입니다.
*/

출차 (차량번호 or 공간) 정산할 금액, 차량번호, 출차 시간.

		scanner = new Scanner(System.in);
		System.err.print("출차할 차량번호 : ");
		registerNumber = scanner.nextLine();
		
		System.out.println("출차 시간 : ");
		int leaveTime = scanner.nextInt();
		
		if(registerNumber != null && registerNumber.isBlank()) {  // 검증
			System.out.println("차랑번호를 반드시 입력하세요.");
			return;
		}
		
		if(leaveTime < 0 || leaveTime > 24) {
			System.out.println("출차 시간은 0 ~ 24 사이어야 합니다.");
			return;
		}
		
		found = false;
		int foundLayer = -1; // 차량이 위치한 층. // 00값이 나왔을 때 찾아서 00이 나온건지, 못찾아서 00이 나온건지 알 수 없기 때문에 애초에 넣을 수 없는 번호로 초기화 한다.
		int foundSpace = -1; // 차량이 자리한 공간.
		for(int layer = 0; layer < registerNumberes.length; layer++) {
			for(int space = 0; space < registerNumberes[layer].length; space++) {
				if(registerNumber.equals(registerNumberes[layer][space])) {
					foundLayer = layer;
					foundSpace = space;
					found = true;
					break;
				}
			}
			if(found) break;
		}
		
		if(!found) {
			System.out.println("찾을 수 없는 차량 번호입니다.");
			return;
		}
		
		if(leaveTime < getInTimes[foundLayer][foundSpace]) {
			System.out.println("출차 시간이 입차 시간보다 작을 수 없습니다.");
			return;
		}
/*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 가123 12
5층 10번에 위치합니다.
출차할 차량번호 : 가123 12
출차 시간 : 
22

출력 결과2)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 가123 12
5층 10번에 위치합니다.
출차할 차량번호 : 123456
출차 시간 : 
22
찾을 수 없는 차량 번호입니다.

출력 결과 3)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 가123 12
5층 10번에 위치합니다.
출차할 차량번호 : 가123 12
출차 시간 : 
1
출차 시간이 입차 시간보다 작을 수 없습니다.
*/

최종금액 (출차시간 - 입차시간) * 차량별 금액

		int typeAmount = 
				// 삼항 연산자 조건에 따라 결과 값을 다르게 만들어진다.
                types[foundLayer][foundSpace].equals("경차") ? 1000 : 
					types[foundLayer][foundSpace].equals("중대형") ? 1500 : 2000;
		
		int result = (leaveTime - getInTimes[foundLayer][foundSpace]) * typeAmount;
		System.out.println("최종 금액은 " + result + "원 입니다.");
		
        // 출차 후 남은 자리
		parkingSpaces[foundLayer][foundSpace] = false;
		registerNumberes[foundLayer][foundSpace] = null;
		getInTimes[foundLayer][foundSpace] = 0;
		types[foundLayer][foundSpace] = null;
	}
}
/*
출력 결과)
차량 번호 : 가123 12
차종 : 경차
입차 시간 (0 ~ 24) : 2
주차 층 (0 ~ 9) : 5
주차 공간 (0 ~ 29) : 10
확인할 차량 번호 : 가123 12
5층 10번에 위치합니다.
출차할 차량번호 : 가123 12
출차 시간 : 
22
최종 금액은 20000원 입니다.
*/

객체 지향 프로그래밍 (Object Oriented Programming, OOP)

  • 위의 시스템과 같이 길고 복잡하며 보기 어렵고, 반복되는 메소드가 많아 가독성이 좋지 않다.
  • 이러한 문제점을 객체 지향 자바를 이용하여 해결할 수 있다.
  • 코드의 재사용성, 모듈성, 유지 보수성을 향상시키고, 복잡한 소프트웨어 시스템을 효과적으로 설계하고 구현하는 데 도움을 준다.
  • 즉. 코드의 중복을 제거함으로써 유지보수성을 높이고 모듈화를 가능하게 한다는 것이다.

SOLID 원칙

  • 이러한 객체 지향 프로그래밍을 사용 하기 우해서 지켜야할 규칙들이 있다.
  • 나쁜 코드, 코드가 돌아가지 않을 수 있기 때문에 반드시 지켜야 한다.
  1. Single Responsibility Principle (SRP) - 단일 책임 원칙 (클래스)
  • 하나의 클래스는 하나의 책임만 가져야 한다.
  • 클래스가 너무 많은 책임을 지게 되면 클래스가 복잡해지고 유지 관리하기 어렵게 만들 수 있다는 개념에 기반을 두고 있다.
  • 복잡하고 유지 관리하기 어렵게 만들기 때문에 가독성과 유지 보수성의 향상을 위해 필요로 하다.
  • 문제를 최소화 하기 위한 하나의 클래스에 하나의 책임.
  • 절대적인 것. 절대적으로 지켜야 하는 규칙이다.
  1. Open/Close Principle (OCP) - 개방/폐쇄 원칙
  • 소프트웨어의 구성요소(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다는 원칙이다.
  • 개방원칙 (Open for extension) : 새로운 기능이 필요하면 기존 코드를 변경하지 않고 기능을 추가하거나 확장할 수 있어야 한다.
  • 폐쇄원칙 (Close for modification) : 기존 코드는 이미 테스트되었고 작동한다고 검증되었기 때문에, 기존 코드는 가능한 한 변경되지 않아야 한다.
  • 즉. 새로운 기능은 개방의 원칙에 따라 기능을 추가하여 확장시기고, 원래 잘 돌아가고 있는 코드를 바꾸려고 하면 안전성이 떨어지기 때문에 폐쇄원칙에 따라 코드를 갈아끼우는 것을 최소화 해야 한다.
  • 또한 절대적으로 지켜야 하는 규칙이다.

Liskov Substitution Principle (LSP) - 리스코프 치환 원칙

  • 상속 관계에 있어서 부모 클래스와 자식 클래스 간의 동작 일관성을 보장하는 것에 대한 원칙이다.
  • 프로그램은 자식 클래스를 부모 클래스로 치환해도 제대로 동작해야 한다.
  • 즉. 부모클래스와 자식클래스는 동작을 똑같이 해야한다.

Interface Segregation Principle (ISP) - 인터페이스 분리 원칙 (인터페이스)

  • 한 클래스는 자신이 필요로 하는 메서드만을 갖는 인터페이스에만 의존해야 하며, 불필요한 메서드를 포함한 인터페이스에 의존하면 안된다.
  • SRP 원칙과 연결되어 있다.
  • 최소한의 사이즈로 최대한 분리하고 모든 기능이 다 구현되어 있어야 한다.
  • 불필요한 의존성을 제거하고, 시스템의 결합도를 낮추는 데 도움이 된다. 이는 각 클래스의 변경이 다른 클래스에 미치는 영향을 줄이고, 시스템의 유지 보수성을 향상시킨다.
  • ex)
    전화인터페이스에 메세지 인터페이스와 연결되어 있으면 안 된다.
    (SRP랑 같다. 클래스냐 인터페이스냐에 따라 달라진다)

Dependency Inversion Principle (DIP) - 의존성 역전 원칙

  • 상위 모듈은 하위 모듈에 의존해서는 안 된다.
  • 우리가 하고자 하는 행위에 의존되어 그 행위에 거쳐 기능이 진행되면 안 된다.
  • 결합도를 줄이고, 유연성과 재사용성을 높이는 데 도움을 준다.

0개의 댓글