diff --git a/8week/CQRS.md b/8week/CQRS.md new file mode 100644 index 00000000..228ca24a --- /dev/null +++ b/8week/CQRS.md @@ -0,0 +1,74 @@ +# CQRS + +--- + +## CQRS 란? + +- Command and Query Responsibility Segregation (명령과 조회의 책임 분리) 의 약자이며 이름 처럼 시스템의 명령을 처리하는 책임 / 조회를 처리하는 책임 분리 + +💁 명령 : CQRS 에서 시스템의 상태를 변경하는 작업 + +💁 조회 : CQRS 에서 시스템의 상태를 반환하는 작업 + +간단하게 CRUD ⇒ CUD / R + +--- + +## 🤔 CQRS이 왜 생겨났는가? + +- 전통적인 CRUD 아키텍쳐 기반에서 application 은 운영하다 보면 Domain Model 의 복잡도가 점점 증가 하고 이에 따라 유지 보수의 Cost 도 높아지며 Domain Model 의 변질까지 생기는 경우가 있다. + +- 이에 DDD(Domain Model Design) 의 문제점을 해결하기 위해 CQRS가 생겨 나게 됐다. + +--- + +## CQRS 예시 + +![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/3d67082a-3273-4dd6-9096-89f51eeeaeb7/_2021-03-22__11.05.42.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/3d67082a-3273-4dd6-9096-89f51eeeaeb7/_2021-03-22__11.05.42.png) + +- 위 그림은 CQRS 가 적용되지 않은 애플리케이션의 모델이다. 이는 간단한 애플리케이션에는 적합하지만 복잡해 질수록 위에서 말했던 문제점이 발생되게 된다. + - 모양이 다른 DTO 의 반환 + - 병렬작업시 경합 발생 + - 정보 검색을 위한 쿼리의 복잡성으로 성능 저하 + - 각 엔티티가 읽기 및 쓰기 작업의 대상이 되어 잘못된 데이터 노출 + +**CQRS 적용시** + +![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/06c4bda0-0818-4ff7-b5c8-946e62fa02aa/_2021-03-22__11.25.12.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/06c4bda0-0818-4ff7-b5c8-946e62fa02aa/_2021-03-22__11.25.12.png) + +- Read / Write 분리 +- 명령 데이터 중심이 아닌 작업 기반 +- 명령 동기 X 비동기 +- 쿼리가 데이터 베이스 수정 X + +읽기 저장소는 쓰기 저장소의 읽기 전용 복제본이거나 읽기 및 쓰기 저장소가 전혀 다른 구조일수 있다. + +![https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d2d90f98-1bc8-4c86-be49-f6c86e55581c/_2021-03-22__12.36.04.png](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d2d90f98-1bc8-4c86-be49-f6c86e55581c/_2021-03-22__12.36.04.png) + +읽기 전용 복제본을 여러개 사용하면 읽기 전용 복제본이 응용 프로그램 인스턴스에 가까운 위치에 있는 분산 시나리오에서 쿼리 성능을 향상 시킬수 있다. + +CQRS의 일부 구현에서는 이벤트 소싱 패턴 ( 각 이벤트의 변경에 대한 값을 저장 해놓는 집합 도메인은 현재 상태만 기록함 )을 사용한다. 이는 다른 구성요소를 알리는데 동일한 이벤트를 사용할수있다는 이점을 가지고 있고 쿼리보다 효과적이다. But 복잡성을 추가하게 된다. + +--- + +## ? 그렇다면 CQRS 언제 사용을 할까 + +- 여러 사용자가 동일한 데이터를 동시에 엑세스하는 공동작업시 +- 복잡한 도메인 모델을 사용하는 프로세스를 구현할때 +- 데이터의 성능을 데이터 쓰기의 성능과 별도로 세부적으로 조정해야 할때 +- 개발자가 쓰기, 읽기에 각각 집중을 할때 + +**권장 하지 않는 경우** + +- 도메인, 비즈니스 규칙이 간단할때 +- 간단한 CRUD 스타일로 작업이 충분할때 + +--- + +## Reference + +[https://code-masterjung.tistory.com/80](https://code-masterjung.tistory.com/80) + +[https://www.popit.kr/cqrs-eventsourcing/](https://www.popit.kr/cqrs-eventsourcing/) + +[https://justhackem.wordpress.com/2016/09/17/what-is-cqrs/](https://justhackem.wordpress.com/2016/09/17/what-is-cqrs/) \ No newline at end of file diff --git a/8week/racingcar/.gitignore b/8week/racingcar/.gitignore new file mode 100644 index 00000000..76611b07 --- /dev/null +++ b/8week/racingcar/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar +/out/ +/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr \ No newline at end of file diff --git a/8week/racingcar/build.gradle b/8week/racingcar/build.gradle new file mode 100644 index 00000000..95df5f5c --- /dev/null +++ b/8week/racingcar/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' +} + +group 'org.example' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/8week/racingcar/document/README.md b/8week/racingcar/document/README.md new file mode 100644 index 00000000..80371684 --- /dev/null +++ b/8week/racingcar/document/README.md @@ -0,0 +1,84 @@ +# RacingCar 기능정리 + +--- +## Domain + +### Car +* 아는 것 + * 차의 이름 + * 차의 위치 + * 랜덤 범위 +* 하는 것 + * 차 이동 + * 이름 검증 (공백, 길이) + +### Cars +* 아는 것 + * 복수의 차 +* 하는 것 + * 차 전체 이동 + * 가장 멀리간 차의 위치 찾기 + * 우승자 찾기 + * 차 이름 중복 검증 + +### Number +* 아는 것 + * 숫자 값 +* 하는 것 + * 변수형 검 + +--- + +## Service + +### RacingCarGame +* 아는 것 +* 하는 것 + * 게임 시작 + +### RacingCarGameController +* 아는 것 +* 하는 것 + * 차 생성 + * count 생성 + * 경주 실행 + +--- + +## Utils + +### Input +* 아는 것 +* 하는 것 + * 숫자 입력 + * 문자 입력 + +### RandomUtils +* 아는 것 + * 랜덤 라이브러리 +* 하는 것 + * 범위 내의 랜덤값 생 + + +### View +* 아는 것 +* 하는 것 + * 거리 출력 + * 차 이름 출력 + * 우승자 출력 + + + +보장된 숫자 입력 + +보장된 문자 입력 + +보장된 숫자가 아닐 시 에러가 발생한다. + +자동차 이름 중복 제거 + +자동차 이름의 길이가 5이하 보장 + +범위내의 랜덤값 생성 보장 + +String 입력을 각 항목별 분리 \ No newline at end of file diff --git a/8week/racingcar/gradle/wrapper/gradle-wrapper.jar b/8week/racingcar/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..62d4c053 Binary files /dev/null and b/8week/racingcar/gradle/wrapper/gradle-wrapper.jar differ diff --git a/8week/racingcar/gradle/wrapper/gradle-wrapper.properties b/8week/racingcar/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..a4b44297 --- /dev/null +++ b/8week/racingcar/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/8week/racingcar/gradlew b/8week/racingcar/gradlew new file mode 100755 index 00000000..fbd7c515 --- /dev/null +++ b/8week/racingcar/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/8week/racingcar/gradlew.bat b/8week/racingcar/gradlew.bat new file mode 100644 index 00000000..5093609d --- /dev/null +++ b/8week/racingcar/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/8week/racingcar/settings.gradle b/8week/racingcar/settings.gradle new file mode 100644 index 00000000..563821bb --- /dev/null +++ b/8week/racingcar/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'racingcar' + diff --git a/8week/racingcar/src/main/java/Application.java b/8week/racingcar/src/main/java/Application.java new file mode 100644 index 00000000..e4975541 --- /dev/null +++ b/8week/racingcar/src/main/java/Application.java @@ -0,0 +1,7 @@ +import service.RacingGame; + +public class Application { + public static void main(String[] args) { + RacingGame.start(); + } +} diff --git a/8week/racingcar/src/main/java/domain/Car.java b/8week/racingcar/src/main/java/domain/Car.java new file mode 100644 index 00000000..41328194 --- /dev/null +++ b/8week/racingcar/src/main/java/domain/Car.java @@ -0,0 +1,54 @@ +package domain; + +public class Car implements Comparable { + private final String name; + private int position; + private static final int CONTROL_POINT = 4; + private static final int MAX_INPUT_LENGTH = 5; + + public Car(final String name) { + validateLength(name); + validateInput(name); + this.name = name; + this.position = 0; + } + + public void movePosition(int randomValue) { + if (isMoveAble(randomValue)) { + this.position = position + 1; + } + } + + private boolean isMoveAble(int randomValue) { + return randomValue > CONTROL_POINT; + } + + private void validateLength(String name) { + if (name.length() > MAX_INPUT_LENGTH) { + throw new IllegalArgumentException("[ERROR] 이름의 길이는 5를 넘을수 없습니다."); + } + } + + public int getPosition(){ + return position; + } + + public String getName() { + return name; + } + + private void validateInput(String buffer) { + if (buffer.contains(" ")) { + throw new IllegalArgumentException("[ERROR] 잘못된 입력입니다."); + } + } + + public boolean isSamePosition(Car other) { + return other.position == this.position; + } + + @Override + public int compareTo(Car other) { + return this.position - other.position; + } +} diff --git a/8week/racingcar/src/main/java/domain/Cars.java b/8week/racingcar/src/main/java/domain/Cars.java new file mode 100644 index 00000000..48937677 --- /dev/null +++ b/8week/racingcar/src/main/java/domain/Cars.java @@ -0,0 +1,52 @@ +package domain; + +import utils.RandomUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + + +public class Cars { + private final List cars; + private static final int START_NUMBER = 0; + private static final int FINISH_NUMBER = 10; + + public Cars (List carNames) { + validateCars(carNames); + this.cars = carNames.stream() + .map(carName->new Car(carName)) + .collect(Collectors.toList()); + } + + private void validateCars(List carNames) { + Boolean value = (int) carNames.stream() + .distinct() + .count() != carNames.size(); + if (value) { + throw new IllegalArgumentException("[ERROR] 이름 중복"); + } + } + + public List getCars() { + return Collections.unmodifiableList(this.cars); + } + + public void moveCars() { + cars.stream() + .forEach(Car-> Car.movePosition(RandomUtils.randomIntGenerator(START_NUMBER, FINISH_NUMBER))); + } + + private Car findMaxPosition() { + return cars.stream() + .max(Car::compareTo) + .orElseThrow(() -> new IllegalArgumentException("[ERROR] 차량 리스트가 비었습니다.")); + } + + public List getWinner() { + return cars.stream() + .filter(car -> car.isSamePosition(findMaxPosition())) + .collect(Collectors.toCollection(ArrayList::new)); + } +} diff --git a/8week/racingcar/src/main/java/domain/Number.java b/8week/racingcar/src/main/java/domain/Number.java new file mode 100644 index 00000000..4c87be0d --- /dev/null +++ b/8week/racingcar/src/main/java/domain/Number.java @@ -0,0 +1,22 @@ +package domain; + +public class Number { + private final int value; + + public Number(String value) { + validateNumber(value); + this.value = Integer.parseInt(value); + } + + private void validateNumber(String value) { + try { + Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 숫자여야 한다."); + } + } + + public int getCount() { + return value; + } +} diff --git a/8week/racingcar/src/main/java/service/RacingGame.java b/8week/racingcar/src/main/java/service/RacingGame.java new file mode 100644 index 00000000..a11c1388 --- /dev/null +++ b/8week/racingcar/src/main/java/service/RacingGame.java @@ -0,0 +1,8 @@ +package service; + +public class RacingGame { + public static void start() { + RacingGameController controller = new RacingGameController(); + controller.run(); + } +} diff --git a/8week/racingcar/src/main/java/service/RacingGameController.java b/8week/racingcar/src/main/java/service/RacingGameController.java new file mode 100644 index 00000000..f12f5c0e --- /dev/null +++ b/8week/racingcar/src/main/java/service/RacingGameController.java @@ -0,0 +1,33 @@ +package service; + +import domain.Cars; +import domain.Number; + +import utils.Input; +import utils.View; +import java.util.List; + +public class RacingGameController { + private Cars createCar() { + List carNames = Input.stringInput(); + return new Cars(carNames); + } + + private Number createCount() { + return Input.numberInput(); + } + + public void run() { + Cars cars = createCar(); + Number count = createCount(); + int times = count.getCount(); + while (times-- > 0) { + cars.moveCars(); + cars.getCars().stream() + .forEach(Car -> View.carStatusView(Car)); + View.spacingWord(); + } + View.resultView(cars.getWinner()); + } +} + diff --git a/8week/racingcar/src/main/java/utils/Input.java b/8week/racingcar/src/main/java/utils/Input.java new file mode 100644 index 00000000..0c3bd4b3 --- /dev/null +++ b/8week/racingcar/src/main/java/utils/Input.java @@ -0,0 +1,37 @@ +package utils; + +import domain.Number; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + +public class Input { + private static final String SPLIT_RULE = ","; + private static final BufferedReader BUFFERED_READER = new BufferedReader(new InputStreamReader(System.in)); + + private Input() {}; + + public static List stringInput() { + try { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String buffer = BUFFERED_READER.readLine(); + return Arrays.asList(buffer.split(SPLIT_RULE)); + } catch (IOException e) { + throw new IllegalArgumentException("[ERROR] 잘못된 입력입니다."); + } + } + + public static Number numberInput() { + try { + System.out.println("시도할 회수는 몇회인가요?"); + String buffer = BUFFERED_READER.readLine(); + Number number = new Number(buffer); + return number; + } catch (IOException e) { + throw new IllegalArgumentException("[ERROR] 잘못된 입력입니다."); + } + } +} diff --git a/8week/racingcar/src/main/java/utils/RandomUtils.java b/8week/racingcar/src/main/java/utils/RandomUtils.java new file mode 100644 index 00000000..ceff946b --- /dev/null +++ b/8week/racingcar/src/main/java/utils/RandomUtils.java @@ -0,0 +1,24 @@ +package utils; + +import java.util.Random; + +public class RandomUtils { + private static final Random RANDOM = new Random(); + + private RandomUtils() { + } + + public static int randomIntGenerator(final int startNumber, final int lastNumber) { + if (startNumber > lastNumber) { + throw new IllegalArgumentException(); + } + if (startNumber < 0) { + throw new IllegalArgumentException(); + } + if (startNumber == lastNumber) { + return startNumber; + } + return startNumber + RANDOM.nextInt(lastNumber - startNumber + 1); + } + +} diff --git a/8week/racingcar/src/main/java/utils/View.java b/8week/racingcar/src/main/java/utils/View.java new file mode 100644 index 00000000..c708060f --- /dev/null +++ b/8week/racingcar/src/main/java/utils/View.java @@ -0,0 +1,43 @@ +package utils; + +import domain.Car; + +import java.util.List; +import java.util.stream.Collectors; + +public class View { + private static final String FINAL_WINNER = "최종 우승자 : "; + private static final String COLON = " : "; + private static final String COMMA = ", "; + private static final String POSITION_VIEW = "-"; + + private View() {}; + + public static void distanceView(int position) { + while (position-- > 0){ + System.out.print(POSITION_VIEW); + } + System.out.println(); + } + + public static void carNameView(final String name){ + System.out.print(name + COLON); + } + + public static void resultView(final List cars){ + System.out.print(FINAL_WINNER); + String winner = cars.stream() + .map(Car::getName) + .collect(Collectors.joining(COMMA)); + System.out.println(winner); + } + + public static void carStatusView(Car car) { + View.carNameView(car.getName()); + View.distanceView(car.getPosition()); + } + + public static void spacingWord() { + System.out.println(); + } +} diff --git a/8week/racingcar/src/test/java/StreamTest.java b/8week/racingcar/src/test/java/StreamTest.java new file mode 100644 index 00000000..61e36dcf --- /dev/null +++ b/8week/racingcar/src/test/java/StreamTest.java @@ -0,0 +1,19 @@ +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class StreamTest { + @Test + public void 테스트() { + List name = new ArrayList<>(Arrays.asList("동현", "정윤", "태정", "문기", "윤지", "수연")); + List collects = name.stream() + .sorted() + .collect(Collectors.toList()); + for(String collect : collects) { + System.out.println(collect); + } + } +} diff --git "a/8week/\353\224\224\353\257\270\355\204\260 \353\262\225\354\271\231.md" "b/8week/\353\224\224\353\257\270\355\204\260 \353\262\225\354\271\231.md" new file mode 100644 index 00000000..3de0be52 --- /dev/null +++ "b/8week/\353\224\224\353\257\270\355\204\260 \353\262\225\354\271\231.md" @@ -0,0 +1,120 @@ +## 디미터 +--- + +- 디미터 법칙의 시초 : 디미터라는 프로젝트를 진행중 다른 객체와의 협력을 하는 객체지향프로그래밍에서 협력의 경로를 제한 시켜두면 결합도가 낮아진다는 점을 발견하고 만들어낸 법칙이다. + +**간단하게 한줄에 . 하나만 찍는다고 표현한다.** + +--- + +## Don't talk strangers + +**핵심** + +- 객체의 구조를 따라 멀리 떨어져있는 객체를 get 을 통해 불러오거나, 메세지를 보내지마라 + - 객체 내부에 있는 내용, 메세지를 통해 얻은 정보만 사용해야한다. + + → 객체가 알아야하는 다른 객체의 내용을 최소화 하라! + + 💁 **Principle of least knowledge ( 최소 지식 원칙 )** + +**EX)** + +- getter를 사용해 현재 객체에서 다른 객체의 내용을 불러온다 ⇒ 디미터 법칙 위반 + +```java +public class Post { + private final List comments; + + public Post(List comments) { + this.comments = comments; + } + + public List getComments() { + return comments; + } +} +``` + +```java +public class Board { + private final List posts; + + public Board(List posts) { + this.posts = posts; + } + + public void addComment(int postsId, String content) { + posts.get(postId).getComments().add(new Comment(content)); + } + +} +``` + +- 위 코드에서 Board 객체의 addComment 부분이 디미터 법칙에 위반되는 부분이다. + +--- + +## 그렇다면 왜? + +**IF** + +- 위에 코드에서 List comments ⇒ Comments 라는 일급 컬렉션으로 수정이 된다면? + +```java +public void addComment(int postId, String content) { + posts.get(postId).getComments().add(new Comment(content)); +} +``` + +- 다음 코드에서 오류가 나게 된다. 이렇게 된다면 Board 객체는 Post 객체의 변화에도 영향을 받고 Comment 객체의 변화에도 영향을 준다. + +--- + + + +## 규칙화 + +- 노출을 제한 하기 위해 다음과 같은 규칙을 지켜야한다. + +1. 객체 자신의 메서드 사용 +2. 메서드의 파라미터로 넘어온 객체들의 메서드 사용 +3. 메서드 내부에서 생성, 초기화된 객체의 메서드만 사용 +4. 인스턴스 변수로 가지고 있는 객체가 소유한 메서드 사용 + +```java +Class Demeter { + private Member member; + + public myMethod(OtherObject other) { + } + + public okLawOfDemeter(Paramemter param) { + myMethod(); // 1 + param.paramMethod(); // 2 + Local local = new local(); // 3 + local.localMethod(); + member.memberMethod(); // 4 + } +} +``` + +- 이 규칙을 지킨다면 결합도가 내려가 에러가 적고, 변화에 유연하게 대처가 가능한 클래스를 만들수있다. + +--- + + + +## 주의사항 + +- 자료구조라면 디미터 법칙을 거론할 필요 X + - 자료구조는 내부 구조를 노출 해야하므로 디미터 법칙 필요 X +- 하나의 .을 강제 하는 규칙이 아니다. + - 결합도 문제 ⇒ 객체 내부 구조가 외부로 노출 + - 따라서 .을 사용하더라도 객체 내부 구조가 캡슐화 되어 외부로 노출 되지 않는다면 상관없다. + +--- + +## Reference + +[https://dundung.tistory.com/203](https://dundung.tistory.com/203) \ No newline at end of file diff --git "a/8week/\354\235\274\352\270\211 \354\273\254\353\240\211\354\205\230.md" "b/8week/\354\235\274\352\270\211 \354\273\254\353\240\211\354\205\230.md" new file mode 100644 index 00000000..50b52e4a --- /dev/null +++ "b/8week/\354\235\274\352\270\211 \354\273\254\353\240\211\354\205\230.md" @@ -0,0 +1,182 @@ +--- + +# 일급 컬렉션 (First Class Collection) + +- 일급 컬렉션이란? 간단하게 말하면 Collection 들을 감싼 컬렉션을 의미함 + +```java +public class Car { + private String name; + + public Car(String name) { + this.name = name; + } +} + +Car kia = new Car("k5"); +Car hyundai = new Car("sonata"); + +public class Cars { //일급 컬렉션 + private List cars; + + public Cars(List cars) { + this.cars = this.cars; + } + + public List getCars() { + return cars; + } +} // 생성, 조회만 가능해야함 수정 X +``` + +--- + +## 일급 컬렉션 규칙 + +규칙 자체는 매우 간단하다. + +- Collection 을 포함한 객체는 다른 멤버변수를 가질수 없다. + +```java +public class Cars { //일급 컬렉션 + private List cars; + + public Cars(List cars) { + this.cars = this.cars; + } + + public List getCars() { + return cars; + } +} // 생성, 조회만 가능해야함 수정 X +``` + +--- + +## 일급 컬렉션의 이점 + +- 비즈니스에 종속적인 자료구조 +- Collection의 불변성을 보장 +- 상태와 행위를 한 곳에서 관리 +- 이름이 있는 컬렉션 + +**비즈니스에 종속적인 자료구조** + +```java +public class LottoService { + + private static final int LOTTO_NUMBERS_SIZE = 6; + + public void createLottoNumber() { + List lottoNumbers = createNonDuplicationNumbers(); + validateSize(lottoNumbers); + validateDuplicate(lottoNumbers); + } +} +``` + +- 위 코드에는 로또 서비스에서 로또번호를 만들때마다 필요한 모든 장소에서 검증 로직이 들어가게 된다. +- 위 코드만 봤을 경우에는 LottoService 안에 로또번호를 발생하기 위한 검증이 들어가게 된다. 이는 좀 어색 한것 같다. → LottoService 에서 로또번호를 만드는 부분은 분리를 해야될거 같다. +- 또 서비스의 부분이 아니라 로또 티켓이라는 도메인으로 선언을 해주고 동시에 검증을 해야할것 같다. +- 이를 해결하기위해 해당 조건으로만 생성 할수 있는 자료구조를 만들게 되면 해결할수 있다. + +```java +public class LottoTicket { + private static final int LOTTO_NUMBERS_SIZE = 6; + + private final List lottoNumbers; + + public LottoTicket(List lottoNumbers) { + validateSize(lottoNumbers); + validateDuplicate(lottoNumbers); + this.lottoNumbers = lottoNumbers; + } +} + +public class LottoService { + + public void createLottoNumbers() { + LottoTicket lottoTicket = new LottoTicket(createNonDuplicateNumbers()); + } +} + +``` + +- 다음과 같이 일급 컬렉션을 사용해 비즈니스에 종속적인 자료 구조를 만들어주면 좀더 깔끔한 코드를 만들수 있게 된다. + +**불변성 보장** + +☝️ final = 불변을 만들어주는게 아니라 재할당만 금지 시켜주는것 + +즉 new 를 통해서 재할당은 막아주지만 set 을 통한 내부값 변경은 막지 못함 + +**따라서** + +일급 컬렉션을 사용해 이를 막아줄수있다. ( set 을 포함하고 있지않은 일급 컬렉션 ) + +**상태와 행위를 한곳에서 관리** + +```java +List pays = Arrays.asList( + new Pay(NAVER_PAY, 10000), + new Pay(NAVER_PAY, 15000); +} + +Long naverPaySum = pays.stream() + .filter(pay -> pay.getPayType().equals(NAVER_PAY)) + .mapToLong(Pay::getAmount) + .sum(); + +``` + + + +- 위 코드를 살펴보면 List 에 데이터를 담고 +- Service 에서 필요한 로직을 수행한다. + + 이때 컬렉션과 계산 로직은 서로 관계가 있지만 위 코드에서는 표현이 안되어있다. + +의도는 pay 타입에 따라 지정된 메소드에서 연산이 되길 원하지만 위 코드에서는 불가능하다. 이는 메소드 중복 생성과 계산 메소드 누락을 불러올수 있다. + +이를 해결하기위해 컬렉션과 계산 메소드를 함께 두면 된다. + +```java +public class payGroups { + private List pays; + + public PayGroups(List pays) { + this.pays = pays; + } + + public Long getNaverPaySum() { + return pays.stream() + .filter(pay -> PayType.isNaverPay(pay.getPayType())) + .mapToLong(Pay::getAmount) + .sum(); + } +} +``` + +**이름이 있는 컬렉션** + +- kakaoPay 와 naverPay 의 list 는 다르다 이둘을 구분 하는 방법은 변수명을 다르게 하는건데 이때 단점이 있다. + - 검색의 어려움 + - 명확한 표현 불가능 + + 이 를 해결하기 위해 일급 컬렉션을 사용하면 + +```java +NaverPays naverPays = new NaverPays(createNaverPays()); +KakaoPays kakaoPays = new KakaoPays(createKakaoPays()); + +``` + +--- + +# Reference + +[https://jojoldu.tistory.com/412](https://jojoldu.tistory.com/412) + +[https://jackjeong.tistory.com/107](https://jackjeong.tistory.com/107) + +--- \ No newline at end of file