본문 바로가기

TIL

[TIL] 210806

1. Dependency
2. 프로젝트 설계
3. 프로젝트 구현

1. Dependency

구현 시 자주 사용하는 라이브러리 : javafaker, Lombok

 

(1) javafaker

1) maven 라이브러리 검색 사이트 : https://search.maven.org/

2) Gradle Groovy DSL 부분 코드 복사

JavaFaker의 예시

3) build.gradle 파일 dependency 설정

dependencies

(2) Lombok

1) lombok 설치 : https://projectlombok.org/

2) javaFaker와 같은 방법으로 dependency 설정

3) 따로 플러그인을 설치해야 사용 가능

[Files] -> [setting] -> [Plugins] -> [lombok] 플러그인 설치

 

 

사용 예시

JavaFaker

자동으로 데이터를 생성해줌

        Faker faker = new Faker();
        String title = faker.name().fullName();
        String title2 = faker.starTrek().character();
        String title3 = faker.starTrek().location();
        long r = faker.number().randomNumber();


	// ... 
        
	//randomDigit() : 0~9 까지의 숫자 중에서 출력
        Stream.generate(() -> faker.number().randomDigit())
                .limit(10)
                .forEach(System.out::println);



	//1 ~ 9 까지의 숫자를 중복되지 않게 10개 뽑아서 출력
        Stream.generate(() -> faker.number().randomDigitNotZero())
                .distinct()
                .limit(10)
                .forEach(System.out::println);

	//위의 결과를 객체로 저장
        Integer[] nums = Stream.generate(() -> faker.number().randomDigitNotZero())
                .distinct()
                .limit(10)
                .toArray(Integer[]::new);

        IntStream nums2 = Stream.generate(() -> faker.number().randomDigitNotZero())
                .distinct()
                .limit(10)
                .mapToInt(i -> Integer.valueOf(i));

 

 

Lombok

자주 사용하는 class의 메서드를 제공해주는 애너테이션 제공

/*
@AllArgsConstructor //Lombok : 모든 constructer를 포함하는 생성자를 만들어줘라
@NoArgsConstructor
@ToString
@EqualsAndHashCode
@Getter
@Setter
*/
@Data //위에있는 애너테이션은 자주 쓰는 것이기 때문에 Data에서 제공해준다.
public class User {
    private int age;
    private String name;
}

2. 프로젝트 설계

1) 요구사항 파악하기

- 게임의 룰 이해

프로젝트를 설계할 때 숫자야구 게임을 만드는 것을 예로 든다면, 게임의 규칙을 이해하는 것이 중요하다. 예를 들어, 게임을 실행할 GUI는 어떤것인지, 데이터의 범위는 어떻게 되는지가 게임 규칙의 예시이다.

ex) 

- 3자리 숫자 사용 (확장 가능)

- 중복된 숫자를 사용하지 않음

- 1~9 를 사용 (0은 사용하지 않음)

- Console 상에서 동작하는 프로그램을 제작

2) 일을 객체로 나누기/객체를 연관짓기

  • 게임이 중심이 되는 BaseBall 객체를 만든다.
  • 게임을 실행하려면 첫번째로 랜덤의 숫자를 생성하는 NumberGenerator 객체가 필요하다.
  • NumberGenerator가 정답을 만들어내면 loop가 시작되고, 사용자에게 비교값을 입력받기 위한 Input 객체가 있어야 한다.
  • 입력값을 받고난 후 1~9에 해당하는 숫자가 입력되어있는지 확인할 parse 메서드가 필요하다.
  • 입력값과 정답을 비교할 ballCount 메서드가 필요하고, 비교 후 결과를 출력할 Output 객체가 필요하다.
  • BaseBall 입장에서 Input과 Output이 어떤 방식으로 진행되는지 중요하지 않기 때문에 두 객체는 interface로 만들고, 
  • 게임 룰에 Console에서 실행하는 룰이 있으므로 두객체는 Console에서 구현한다.
  • 비교 결과가 3 스트라이크 일 경우에는 정답을 출력하고 아닐 경우에는 다시 비교값을 입력받는다.

 

이런 흐름에 따라서 생성되는 객체는 BaseBall, NumberGenerator, Input, Output, Console이다.

 

3) 핵심로직 설계하기

 

  • 게임이 중심이 되는 BaseBall 객체를 만든다.
  • 게임을 실행하려면 첫번째로 랜덤의 숫자를 생성하는 NumberGenerator 객체가 필요하다.
  • NumberGenerator가 정답을 만들어내면 loop가 시작되고, 사용자에게 비교값을 입력받기 위한 Input 객체가 있어야 한다.
  • 입력값을 받고난 후 1~9에 해당하는 숫자가 입력되어있는지 확인 parse 메서드가 필요하다.
  • 입력값과 정답을 비교 ballCount 메서드가 필요하고, 비교 후 결과를 출력 Output 객체가 필요하다.
  • BaseBall 입장에서 Input과 Output이 어떤 방식으로 진행되는지 중요하지 않기 때문에 두 객체는 interface로 만들고, 
  • 게임 룰에 Console에서 실행하는 룰이 있으므로 두객체는 Console에서 구현한다.
  • 비교 결과가 3 스트라이크 일 경우에는 정답을 출력하고 아닐 경우에는 다시 비교값을 입력받는다.

 

 


3. 프로젝트 구현

 

  • 옆의 그림은 최종적으로 완성한 프로젝트의 구조이다. 
  • engine 패키지 내부는 설계 시 객체로 생성하고자 했던 것들을 포함시킨다.
  • 가장 핵심이 되는 BaseBall 클래스에 프로그램을 실행하는데에 꼭 필요한 로직을 설계한다. 가장 먼저 구현하게되는 클래스이다. 다른 객체들에서 필요한 기능들을 미리 명시해 둠으로써 구현해야할 메서드를 미리 생각해 둘 수 있다.
  • Runnable 인터페이스를 implement하여 run() 메서드를 통해 프로젝트를 실행하기위한 로직을 구현하고, 이때 필요한 객체를 하나씩 생성한다.

 

 

 

(1) engine 패키지 내부

@Override
    public void run() {
        Numbers answer = generator.generate(COUNT_OF_NUMBERS);

        while(true){
            String inputString = input.input("숫자를 맞춰보세요. :");
            Optional<Numbers> inputNumbers = parse(inputString);

            if(inputNumbers.isEmpty()){
                output.inputError();
                continue;
            }

            BallCount bc = ballCount(answer,inputNumbers.get());

            output.ballCount(bc);

            if(bc.getStrike() == COUNT_OF_NUMBERS){
                output.correct();
                break;
            }
        }
    }
  • 위 코드는  BaseBall 클래스의 run()메서드를 구현한 부분이다. 
  • 1. 가장 먼저 정답코드 생성을 위한 NumberGenerator 클래스를 만든다.
    • 랜덤으로 생성하기 위해서 Faker 라이브러리를 사용한다.
    • 하지만 engine 핵심 구현부에서 dependency를 사용하지 않는 편이 좋기 때문에 engine.io 패키지에 NumberGenerator 인터페이스를 생성한 후 이 인터페이스를 implement를 받아 구현부분에서 생성 하도록 한다. 
      • 1-1) NumberGenerator를 implement한 FakerNumberGenerator 클래스를 생성한다.
      • stream() 을 이용해서 count 갯수 만큼의 랜덤한 숫자를 생성한다.
      • public Numbers generate(int count) {
        	return new Numbers(
        		Stream.generate(() -> faker.number().randomDigitNotZero())
                                .distinct()
                                .limit(count)
                                .toArray(Integer[]::new)
        		);
        	}
  • 2. 생성한 정답 코드를 저장할 Numbers 클래스를 만든다.
    • engine.Model 패키지에 클래스를 저장한다.
    • Model이 되는 클래스는 따로 모아서 저장
  • 3. while문을 통해 무한 루프 시작
  • 4. loop 안에서 비교할 값을 입력받고 출력할 Input, Output 클래스를 생성한다.
    • engine.io 패키지에 클래스를 저장한다. 
    • 입출력과 관계있는 클래스는 따로 모아서 저장
  • 5. Optional 타입을 응용해서 null값을 가지는 객체를 생성하지 않도록 하고, 입력받은 값을 parse() 메서드를 통해 유효한 값인지 검증한다.
  • 6. ballCount() 메서드로 strike와 ball의 갯수를 확인한다.
    • private BallCount ballCount(Numbers answer, Numbers inputNumbers) {
              AtomicInteger strike = new AtomicInteger();
              AtomicInteger ball = new AtomicInteger();
              
              answer.indexdeForEach((a,i) ->{
                  inputNumbers.indexdeForEach((n,j)->{
      
                      if(!a.equals(n)) return;
                      if(i.equals(j)) strike.addAndGet(1);
                      else ball.addAndGet(1);
      
                  });
              });
              return new BallCount(strike.get(), ball.get());
          }
    • 이때 주의해야할 점은 int, Integer, AtomicInteger에 대한 내용이다. 나중에 따로 정리하면 좋을 것 같다.
    • strike와 ball 갯수를 확인한 값을 저장할 BallCount클래스를 생성한다. 
      • 마찬가지로 engine.Model 패키지에 클래스를 저장한다.
  • 7. strike의 갯수가 지정한 처음 지정한 3개라면 correct() 메서드를 실행하여 정답임을 출력한다.

(2) engine 패키지 외부

  • 1. 직접 코드를 실행하는 부분인 App을 작성한다. 이부분을 호스트 코드라고 한다.
    • engine 패키지 내부와 같은 핵심 부분은 구현코드라고 한다.
    • public class App {
          public static void main(String[] args) {
              NumberGenerator generator = new HackFakerNumberGenerator();
              Console console = new Console();
      
              new BaseBall(generator, console, console).run();
          }
      }
    • 위 코드처럼 실행에 필요한 객체들을 선언해주고 바로 실행한다.
  • 2. 입출력과 관계있는 Console 클래스를 구현한다.
    • Console은 Input, Output 두가지와 관련있기 때문에 두가지 모두 implements 한다.
      • 입출력에 필요한 Scanner를 상수로 선언한다.
      • 필요한 구현코드를 override 해서 작성한다.
  • 3. 이전에 만들어두었던 NumberGenerator를 구현할 FakerNumberGenerator 클래스를 생성하고, 메서드를 구현한다.

이렇게 객체를 나누어가면서 설계한 적은 처음인 것 같다.

그동안은 객체를 생성할 때 class 하나에 필요한 기능을 중복해서 넣어가면서 인터페이스나 상속을 사용하지 않았는데 이런 방식이 굉장히 효율적인 것 같고 이런 흐름으로 프로그램을 설계하는 연습을 좀 더 해서 자연스럽게 나올 수 있었으면 좋겠다.

 

 

 

 

'TIL' 카테고리의 다른 글

[TIL] 210810  (0) 2021.08.10
[TIL] 210809  (0) 2021.08.10
[TIL] 210805  (0) 2021.08.06
[TIL] 210804  (0) 2021.08.05
[TIL] 210803  (0) 2021.08.05