├── .gitignore
├── .idea
└── .gitignore
├── LICENSE
├── README.md
├── book_600.png
├── converter.png
└── src
└── main
└── java
└── net
└── mingleup
└── guide
├── MainApp.java
└── employee
├── EmployeePaySwitch.java
├── EmployeePaySwitchRefactor.java
├── abstractfactory
├── AssistPayCalculator.java
├── ContractPayCalculator.java
├── EmployeePayAbstractFatoryMain.java
├── PayCalculator.java
├── PayCalculatorFactory.java
├── RegularPayCalculator.java
└── TemporaryPayCalculator.java
└── strategy
├── AssistPayCalculator.java
├── ContractPayCalculator.java
├── EmployeePayStrategyMain.java
├── PayCalculator.java
├── PayCalculatorFactory.java
├── RegularPayCalculator.java
└── TemporaryPayCalculator.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | replay_pid*
25 |
26 | *.iml
27 | jpa-buddy.xml
28 | *.idea
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Coding Guide for Java Programmer By 코드빌런
2 |
3 | > 본 내용은 한빛미디어에서 출간한 제 책(개발자 기술 면접 노트)의 일부 내용을 보강하기 위해서 만든 코딩 가이드 입니다.
4 | > 설명에서는 타이핑을 줄이기 위해서 존대를 생략했으니 양해 바랍니다.
5 | > 만약 내용에 문제가 있거나 오/탈자가 있을 경우 villainscode@gmail.com으로 메일 부탁드립니다.
6 | >
7 | >
8 | > - Instagram - [https://www.instagram.com/codevillains](https://www.instagram.com/codevillains)
9 | > - email - [villainscode@gmail.com](mailto:villainscode@gmail.com)
10 | > - Yes24 - https://www.yes24.com/Product/Goods/125554439
11 | > - KyoboBooks - https://product.kyobobook.co.kr/detail/S000212738756
12 |
13 | >
14 | > 개정 이력
15 | >
16 | > 현재 버전 v.1.0.0 (2024.05.09)
17 | > - 더 다양한 코드 용례를 추가할 때 마다 개정 이력은 업데이트 될 예정입니다.
18 | >
19 |
20 | - [목차](#--)
21 | - [코딩 가이드](#---------)
22 | * [들어가기에 앞서](#--------)
23 | * [소개](#--)
24 | - [코딩 가이드 개요](#---------)
25 | - [일반적인 자바 코딩 규칙](#-------------)
26 | * [기본적인 용어](#-------)
27 | * [클래스와 객체, 객체와 인스턴스](#-----------------)
28 | * [메서드와 함수](#-------)
29 | * [나쁜 코드](#-----)
30 | * [좋은 코드를 작성하는 몇가지 규칙들](#-------------------)
31 | - [의미 있는 이름](#--------)
32 | + [1. 그릇된 정보를 피해라.](#1------------)
33 | + [2. 의미있게 구분하라.](#2----------)
34 | + [3. 검색하기 쉬운 이름, 발음하기 쉬운 이름을 사용하라.](#3-----------------------------)
35 | + [4. 클래스 이름 규칙](#4----------)
36 | + [5. 메서드 이름 규칙](#5----------)
37 | + [6. 한 개념에 한 단어를 사용하라.](#6-----------------)
38 | - [함수 (메서드)](#--------)
39 | + [1. switch 문](#1-switch--)
40 | + [2. 서술적인 이름을 사용하라.](#2--------------)
41 | + [3. 메서드 인수](#3-------)
42 | + [4. 명령과 조회를 분리해라.](#4-------------)
43 | + [5. DRY (Don't Repaet Yourself)](#5-dry--don-t-repaet-yourself-)
44 | + [6. 주석은 코드로 대체할 수 있어야 한다.](#6---------------------)
45 | - [형식 맞추기](#------)
46 | - [객체와 자료 구조](#---------)
47 | + [디미터의 법칙 : 오직 하나의 점(.)에 의해 호출되는 메서드만 호출해야 한다.](#------------------------------------------)
48 | + [자료 전달 객체](#--------)
49 | - [오류 처리](#-----)
50 | + [오류 코드보다 예외를 사용하라.](#----------------)
51 | + [null을 반환하지 마라.](#null---------)
52 | - [클래스](#---)
53 | + [클래스 작성 규칙](#---------)
54 | + [클래스는 작게 설계 해야한다.](#---------------)
55 | + [SRP](#srp)
56 | + [응집도(Cohesion)](#----cohesion-)
57 | - [코드 기본 사용 규칙 모음](#--------------)
58 | + [규칙 1. 파일은 UTF-8로 인코딩 한다.](#---1-----utf-8--------)
59 | + [규칙 2. 패키지 이름은 소문자이고, 언더라인을 쓰지 않는다.](#---2----------------------------)
60 | + [규칙 3. 클래스 이름은 UpperCamelCase (PascalCase) 로 작성한다. ](#---3---------uppercamelcase--pascalcase---------------------------------------)
61 | + [규칙 4 .메소드 이름은 lowerCamelCase로 작성한다.](#---4---------lowercamelcase-------------------------)
62 | + [규칙 5. 너무 짧은 변수명, 메소드명은 지양하고 의미있는 이름으로 지어야 한다. ](#---5---------------------------------------------------------------------------)
63 | + [규칙 6. 상수는 CONST_CASE 이다. (UPPER_SNAKE_CASE)](#---6-----const-case-----upper-snake-case-)
64 | + [규칙 7. 상수가 아닌 필드는 lowerCamelCase로 작성한다.](#---7------------lowercamelcase------)
65 | + [규칙 8. 변수는 lowerCamelCase로 작성한다.](#---8-----lowercamelcase------)
66 | - [Usage (코드 예제 모음)](#usage-----------)
67 | + [1. 괄호는 Egyptian brackets 을 따른다. ](#1-------egyptian-brackets--------)
68 | + [2. 빈 블럭이나 block-like construct](#2----------block-like-construct---------------------------------------)
69 | + [3. 공백과 빈줄 ](#3-----------)
70 | + [4. Static Factory Method 의 관행적인 명명 ](#4---static-factory-method------------)
71 | + [5. List 타입에서 빈 컬렉션을 반환](#5---list-------------------null---new---------------empty-collection---------gc--------)
72 | + [6. foreach 루프에서 콜렉션의 요소를 추가하거나 삭제하지 말것 ](#6---foreach------------------------------)
73 | + [7. Key, Value 콜렉션에서 null이 저장되는지 체크해두어야 한다. ](#7---key--value-------null-------------------)
74 | + [8. Thread ](#8---thread--)
75 | + [9. String 타입에 적절하지 않은 데이터를 표기하지 말아야 한다. ](#9---string-------------------------------)
76 | + [10. String의 초기화에는 StringUtils.EMPTY을 사용하는 것을 권장한다. ](#10---string--------stringutilsempty----------------)
77 | + [11. 불필요한 연산은 사용하지 말자. ](#11---------------------)
78 | + [12. Optional 사용 ](#12---optional-----)
79 | + [13. Predicate과 Valdator 구분해서 사용 ](#13---predicate--valdator----------)
80 | + [14. 변수가 너무 많은 클래스 구조는 별도의 객체로 분리하자. ](#14-----------------------------------)
81 | + [15. 가급적 예외를 구체화 한다. ](#15-------------------)
82 | + [16. 짧은 try ~ catch 블럭 권장 ](#16------try---catch--------)
83 | + [17. SpringFramework 에서 제공하는 자체 Util 들은 사용하면 안된다. ](#17---springframework------------util--------------)
84 | + [18. Spring Annotation의 적절한 사용 ](#18---spring-annotation----------)
85 | - [DTO (Converter vs Mapper)](#dto--converter-vs-mapper-)
86 |
87 | Table of contents generated with markdown-toc
88 |
89 |
90 | # 📖 코딩 가이드
91 |
92 | ## 들어가기에 앞서
93 | 이 내용은 아래의 서적과 링크를 참고하여 작성하였다. 각종 예제 코드들은 코드빌런 본인의 개인코드를 포함하여 챗GPT의 도움을 받았다.GPT만쉐!
94 |
95 | ## 소개
96 |
97 |
98 |
99 |
100 | >
101 | > 참고 서적 및 링크
102 | > - Effective Java 3/E (Joshua Bloch)
103 | > - Clean Code (Robert C. Martin)
104 | > - [개발자 기술 면접 노트](https://github.com/villainscode) (Technical Interview Notes for Java Developers, Hanbit Media 2024.03.25, Code Villains)
105 | > - https://google.github.io/styleguide/javaguide.html
106 | > - https://www.oracle.com/java/technologies/javase/codeconventions-contents.html
107 |
108 |
109 | # 인프런 강의
110 |
111 |
112 |
113 | - [시니어면접관이 알려주는 개발자 취업과 이직, 한방에 해결하기 이론편](https://www.inflearn.com/course/%EC%8B%9C%EB%8B%88%EC%96%B4-%EB%A9%B4%EC%A0%91%EA%B4%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-%EC%B7%A8%EC%97%85-%EC%9D%B4%EC%A7%81-%EC%9D%B4%EB%A1%A0)
114 | - [시니어면접관이 알려주는 개발자 취업과 이직, 한방에 해결하기 실전편](https://www.inflearn.com/course/%EC%8B%9C%EB%8B%88%EC%96%B4-%EB%A9%B4%EC%A0%91%EA%B4%80-%EC%95%8C%EB%A0%A4%EC%A3%BC%EB%8A%94-%EC%B7%A8%EC%97%85-%EC%9D%B4%EC%A7%81-%EC%8B%A4%EC%A0%84)
115 |
116 |
117 |
118 | # 코딩 가이드 개요
119 |
132 |
133 | # 일반적인 자바 코딩 규칙
134 |
135 | ## 기본적인 용어
136 | >
137 | > - Value Object : 비즈니스 용어를 나타내는 불변 객체
138 | > - Entity : 속성이 아닌 식별성을 기준으로 정의되는 도메인 객체, 여러 Value Object로 구성
139 | > - Service : 도메인 객체에 포함할 수 없는 동작, 로직적인 연산을 갖는 객체
140 | > - Aggregate : 연관된 Value Object와 Entity의 묶음
141 | > - Factory : 복잡한 Entity, Aggregate를 캡슐화하여 여러 객체를 동시에 생성
142 | > - Repository : Aggregate의 영속성 및 등록·수정·삭제·조회 시 일관성 관리
143 |
144 | ## 클래스와 객체, 객체와 인스턴스
145 |
146 | 클래스 : 객체를 생성하기 위한 설계 코드로 아래와 같은 형태를 가지고 있다.
147 |
148 | - 생성자
149 | - 속성
150 | - 메서드
151 |
152 | 구체적인 값을 가진 상태라기 보다는 추상적인 개념
153 |
154 | 객체 : 클래스를 통해 만들어진 구체적인 개체. 클래스에서 정의된 속성과 메서드를 가지고 데이터를 지닌 상태로 메서드를 호출하고 동작을 수행한다. 클래스에 정의된 메서드를 통해 객체의 속성과 행동을 이용하여 실제 프로그램에서 사용된다.
155 |
156 | 클래스 : 클래스는 건축물의 설계도와 같은 개념이고 객체는 설계도를 기반으로 만들어진 집과 같다.
157 | 클래스는 데이터와 그 데이터를 처리하는 메서드의 집합으로 이루어져 있다.
158 | 집이라는 클래스를 보면 방의 수, 크기, 화장실의 수, 외관의 색깔 등의 데이터를 가지고 집으로서의 필수적인 기능들을 수행하는 메서드를 가지고 있다.
159 |
160 | 인스턴스 : 인스턴스는 특정 클래스를 통해 실제로 메모리에 생성된 객체로 저마다 고유한 값(참조값)을 지니고 있다.
161 |
162 | 객체가 [집]이라고 하는 일반적인 개념이라면 인스턴스는 집 객체를 참조하는 [우리집, 영희네 집, 철수네 집] 이라고 생각할 수 있다.
163 | 각각의 집은 서로 다른 속성을 가질수 있지만 모두 집 클래스의 특성과 기능을 공유할 수 있다.
164 | 따라서 클래스와 객체는 1:N의 관계를 가지고, 객체와 인스턴스는 1:N의 관계를 가진다.
165 |
166 | 객체는 런타임(실행 시)에 메모리에 할당되는 반면, 클래스는 컴파일 시 메모리에 로드된다.
167 |
168 | ## 메서드와 함수
169 |
170 | 메서드는 특정 클래스에 속하며, 객체의 속성이나 행동을 정의하는 코드를 의미한다.
171 |
172 | 객체를 통해서만 호출할 수 있으며 객체간의 상호작용과 데이터 은닉을 구현한다.
173 |
174 | 함수는 좀 더 범용적인 개념으로 독립적으로 존재하며 특정 작업이나 기능을 수행하여 값을 반환하는, 자바 이외의 다른 프로그래밍에서도 사용되는 개념이다.
175 |
176 | 개념적으로 함수는 메서드보다 상위의 개념이지만, 주로 자바 진영에서는 초기부터 메서드라는 명명법이 익숙하기 때문에 함수 = 메서드 라고 표현을 할 것이다.
177 |
178 | ## 나쁜 코드
179 |
180 | 나쁜 코드가 쌓일 수록 팀 생산성은 떨어진다.
181 |
182 | 최대한 빠른 개발을 추구하는 가장 간단한 길은 최대한 코드를 클린하게 유지하는 것이다.
183 |
184 | 그러기 위한 몇가지 원칙은 아래와 같다.
185 |
186 | - 중복을 피하라.
187 | - 한기능만 수행하라
188 | - 제대로 표현하라
189 | - 작게 추상화하라.
190 |
191 | ## 좋은 코드를 작성하는 몇가지 규칙들
192 |
193 | 1. 보이스카우트 규칙 : 캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라.
194 | - 변수 이름을 명확히 하고
195 | - 조금 긴 메서드는 분할하고
196 | - 약간의 중복을 제거하고
197 | - 복잡한 if 문 하나를 정리하고…
198 | 2. 객체 지향 설계의 다섯가지 원칙
199 | - SRP(Single Responsibility Principle) : 클래스에는 한 가지 변경 이유만 존재해야 한다.
200 | - OCP(Open Closed Principle) : 클래스는 확장에 열려 있어야 하며 변경에 닫혀 있어야 한다.
201 | - LSP(Liskov Substitution Principle) : 상속받은 클래스는 기초 클래스를 대체할 수 있어야 한다.
202 | - DIP(Dependency Inversion Principle) : 추상화는 의존해야 하며, 구체화는 의존하면 안된다.
203 | - ISP(Interface Segregation Principle) : 클라이언트가 필요로 하는 기능만을 제공한다. (불필요한 기능들을 분리하여 인터페이스를 작게 유지해야 한다)
204 |
205 | # 의미 있는 이름
206 |
207 | 의도를 분명히 밝혀야 한다.
208 |
209 | ```java
210 | // 의미 없는 함수 이름
211 | public void processData(Object data) {
212 | // ...
213 | } // bad
214 |
215 | // 의미 있는 함수 이름
216 | public void calculateStudentGrade(List students) {
217 | // ...
218 | } // good
219 | ```
220 |
221 | 조건의 경우 다음과 같은 코드가 더 명확히 읽힌다.
222 |
223 | 조건문 분리 코드
224 |
225 | ```java
226 | // 중첩 if 문
227 | if (user.isLogIn()) {
228 | if (user.isAdmin()) {
229 | // 관리자 페이지 표시
230 | } else {
231 | // 일반 사용자 페이지 표시
232 | }
233 | } else {
234 | // 로그인 페이지 표시
235 | } // bad
236 | ```
237 |
238 | ```java
239 | // 조건문 분리 및 함수 사용
240 | if (user.isLogIn()) {
241 | renderUserPage(user);
242 | } else {
243 | renderLoginPage();
244 | }
245 |
246 | private void renderUserPage(User user) {
247 | if (user.isAdmin()) {
248 | renderAdminPage(user);
249 | } else {
250 | renderUserPage(user);
251 | }
252 | }
253 |
254 | private void renderLoginPage() {
255 | // 로그인 페이지 표시
256 | } // good
257 | ```
258 |
259 | 조건부 표현식 사용
260 |
261 | ```java
262 | // if 문 사용
263 | if (user.getAge() >= 19) {
264 | // 성인임을 알리는 메시지 출력
265 | } else {
266 | // 미성년임을 알리는 메시지 출력
267 | } // bad
268 | ```
269 |
270 | ```java
271 | boolean isAdult = user.getAge() >= 19;
272 |
273 | if (isAdult) {
274 | // 성인임을 알리는 메시지 출력
275 | } else {
276 | // 미성년임을 알리는 메시지 출력
277 | } // good
278 | ```
279 |
280 | ### 1. 그릇된 정보를 피해라.
281 |
282 | 영문자 O와 숫자 0 과 같이 구분이 어려운 조합을 정보로 제공하지 않아야 한다.
283 |
284 | 유사한 개념은 유사한 표기법을 사용해야 하지만(일관성) 다른 개념과 혼용될 수 있는 정보를 제공해서는 안된다.
285 |
286 | ### 2. 의미있게 구분하라.
287 |
288 | 문자열을 카피할 때 파라미터로 (char a1[], char a2[]) 보다는 (char source[], char destination[]) 이 훨씬 읽기 편하다.
289 |
290 | ### 3. 검색하기 쉬운 이름, 발음하기 쉬운 이름을 사용하라.
291 |
292 | ### 4. 클래스 이름 규칙
293 |
294 | 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
295 |
296 | ex)Customer, Account 등 (info, data, manager 등은 피한다)
297 |
298 | ### 5. 메서드 이름 규칙
299 |
300 | 동사나 동사구가 적합하다.
301 |
302 | ex) registerUser, deletePage
303 |
304 | ### 6. 한 개념에 한 단어를 사용하라.
305 |
306 | ex) fetch, get, retrieve등과 같이 한가지 기능을 하는 여러 표기나 여러 단어는 피하라.
307 |
308 | # 함수 (메서드)
309 |
310 | > - 함수는 작게 만들어야 한다.
311 | > - 한가지만 수행해야 한다.
312 | > - 함수당 하나의 추상화 수준을 유지한다.
313 | > - 위에서 아래로 코드 읽기 : 내려가기 규칙, 위에서 부터 아래로 코드가 이동되어야 한다.
314 | > - 개인적인 의견이지만 한 메서드에 다른 메서드들이 존재한다면 먼저 읽히는 메서드가 바로 밑에 존재하는게 읽기 편하다.
315 | >
316 |
317 | ### 1. switch 문
318 |
319 | switch 문은 기본적인 분기 문법이 길어 작게 만들기 어렵다. 또한 기능상으로 여러개의 조건에 맞는 처리를 하기 때문에
320 | 한가지 기능을 수행하지 못하므로, 이를 저차원 클래스로 숨기고 변경되는 요인에 따른 영향도를 최소화 해야 한다.
321 |
322 | 아래의 예제를 살펴보자. 각 근무형태에 따른 급여를 계산하는 로직이다.
323 |
324 | ```java
325 |
326 | /**
327 | * @author CodeVillains
328 | * @description 직관적으로 switch 조건식을 통해 작성한 급여 계산 로직.
329 | * 장점 : switch 구문을 이용해서 로직을 직관적으로 이해할 수 있다. 단순한 로직의 경우 직접 구현하는 형태로 초보자용 코드로 적합하다.
330 | * 단점 : 코드 중복 - switch 문 내의 각 사례가 서로 다른 계산을 처리하여 중복이 발생하므로 일정 수준의 코드 중복이 발생한다.
331 | * OCP 위반 - 직원 유형이 추가된다거나 급여 계산식이 바뀔 경우 스위치 구문내부가 급격하게 늘어나고 각각의 코드 로직이 복잡해져 유지관리가 어렵다.(개방폐쇄 원칙 위반)
332 | */
333 | public class EmployeePaySwitch {
334 |
335 | public static void main(String[] args) {
336 | String employeeType = "Contract"; // 직원 유형 설정
337 | double hoursWorked = 40; // 근무 시간
338 | double hourlyRate = 15000; // 시간당 급여
339 |
340 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate);
341 | System.out.println("직원 유형: " + employeeType);
342 | System.out.println("근무 시간: " + hoursWorked + "시간");
343 | System.out.println("시간당 급여: " + hourlyRate + "원");
344 | System.out.println("총 급여: " + pay + "원");
345 | }
346 |
347 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) {
348 | double pay = 0;
349 |
350 | switch (employeeType) {
351 | case "Regular":
352 | pay = hoursWorked * hourlyRate + 50000; // 정규직 추가 급여
353 | break;
354 | case "Contract":
355 | pay = hoursWorked * hourlyRate;
356 | break;
357 | case "Temporary":
358 | pay = hoursWorked * hourlyRate * 0.8; // 임시직 급여율
359 | break;
360 | case "Asist":
361 | pay = hoursWorked * hourlyRate * 0.7; // 어시스트 급여율
362 | break;
363 | default:
364 | System.out.println("유효하지 않은 직원 유형입니다.");
365 | }
366 | return pay;
367 | }
368 | }
369 | ```
370 |
371 | 대부분의 경우 이 코드에서 좀 더 개선해보자면 아래와 같은 형태로 작성할 것이다.
372 |
373 | ``` java
374 | public class EmployeePaySwitchRefactor {
375 |
376 | public static void main(String[] args) {
377 | String employeeType = "Contract"; // 직원 유형 설정
378 | double hoursWorked = 40; // 근무 시간
379 | double hourlyRate = 15000; // 시간당 급여
380 |
381 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate);
382 | System.out.println("직원 유형: " + employeeType);
383 | System.out.println("근무 시간: " + hoursWorked + "시간");
384 | System.out.println("시간당 급여: " + hourlyRate + "원");
385 | System.out.println("총 급여: " + pay + "원");
386 | }
387 |
388 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) {
389 | double pay = 0;
390 |
391 | switch (employeeType) {
392 | case "Regular":
393 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0); // 정규직 추가 급여
394 | break;
395 | case "Contract":
396 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0);
397 | break;
398 | case "Temporary":
399 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.8); // 임시직 급여율
400 | break;
401 | case "Assist":
402 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.7); // 어시스트 급여율
403 | break;
404 | default:
405 | System.out.println("유효하지 않은 직원 유형입니다.");
406 | }
407 |
408 | return pay;
409 | }
410 |
411 | private static double calculatePayWithAdjustment(double hoursWorked, double hourlyRate, double adjustment) {
412 | return hoursWorked * hourlyRate * adjustment;
413 | }
414 | }
415 | ```
416 |
417 | switch 구문 내에서 로직이 들어가는 부분을 공통 메서드로 분리하였다.
418 |
419 | 장점으로는 공통 급여 계산 로직을 별도의 메서드로 추출하면 중복이 줄어들고 코드 재사용성이 좋아졌다는 것과
420 | 각 계산 로직이 메서드 호출 형태로 변경 되어 가독성 및 변경 영향도가 줄어들어 유지보수가 더 쉽다는 것을 들 수 있다.
421 | 그러나 아직 switch 문에 로직이 존재하여 근무 형태애 따른 정책이 변경되면 내부 로직에 수정이 필요할 수 있고(OCP) switch 문이 주요 흐름이므로 메서드가 추가 된다거나 하는 등의 확장성에는 완전히 유용해보이지 않는다.
422 |
423 | 따라서 이러한 switch 구문을 어딘가로 위임하거나 상속 관계로 숨겨 실제 로직을 파생 클래스의 메서드를 호출하는 방식으로 변경하는게 더 OOP에 적합하다.
424 |
425 | 여기서 공통 기능을 수행하는 급여 계산은 구현하는 모든 근무형태 클래스에서 각각의 로직을 가져야 하므로 인터페이스로 정의한다.
426 |
427 | ```java
428 | /**
429 | * 급여 계산에 대한 공통 인터페이스 정의
430 | */
431 | public interface PayCalculator {
432 | double calculatePay(double hoursWorked, double hourlyRate);
433 | }
434 | ```
435 |
436 | 정규직인 경우 급여 계산식에서 + 50000이 추가되므로 아래와 같은 계산식이 된다.
437 | ```java
438 | public class RegularPayCalculator implements PayCalculator {
439 |
440 | @Override
441 | public double calculatePay(double hoursWorked, double hourlyRate) {
442 | return hoursWorked * hourlyRate + 50000;
443 | }
444 | }
445 | ```
446 |
447 | 계약직과 임시직, 어시스트 계산식도 각각 구현해준다.
448 | ```java
449 | // 계약직
450 | public class ContractPayCalculator implements PayCalculator {
451 | @Override
452 | public double calculatePay(double hoursWorked, double hourlyRate) {
453 | return hoursWorked * hourlyRate;
454 | }
455 | }
456 |
457 | ```
458 |
459 | ```java
460 | // 임시직
461 | public class TemporaryPayCalculator implements PayCalculator {
462 | @Override
463 | public double calculatePay(double hoursWorked, double hourlyRate) {
464 | return hoursWorked * hourlyRate * 0.8; // Temporary employe
465 | }
466 | }
467 | ```
468 |
469 |
470 | ```java
471 | // 어시스트
472 | class AssistPayCalculator implements PayCalculator {
473 | @Override
474 | public double calculatePay(double hoursWorked, double hourlyRate) {
475 | return hoursWorked * hourlyRate * 0.7; // Assist employee
476 | }
477 | }
478 | ```
479 |
480 | 여기까지가 구체적인 급여 계산 클래스들이다. 이제 이를 호출하는 클라이언트(메인 코드)와 인스턴스를 생성하는 코드를 살펴보자.
481 |
482 | ```java
483 | public class PayCalculatorFactory {
484 |
485 | static PayCalculator createPayCalculator(String employeeType) {
486 | switch (employeeType) {
487 | case "Regular":
488 | return new RegularPayCalculator();
489 | case "Contract":
490 | return new ContractPayCalculator();
491 | case "Temporary":
492 | return new TemporaryPayCalculator();
493 | case "Assist":
494 | return new AssistPayCalculator();
495 | default:
496 | return null;
497 | }
498 | }
499 | }
500 |
501 | ```
502 | createPayCalculator 메서드를 통해 적절한 급여 계산 인스턴스를 생성해온다. 그 결과에 따라 switch 구문에 따른 직원 유형의 계산 결과를 반환한다.
503 |
504 |
505 | ```java
506 | public class EmployeePayAbstractFatoryMain {
507 |
508 | public static void main(String[] args) {
509 | String employeeType = "Contract"; // Employee type setting
510 | double hoursWorked = 40; // Hours worked
511 | double hourlyRate = 15000; // Hourly rate
512 |
513 | PayCalculator payCalculator = PayCalculatorFactory.createPayCalculator(employeeType);
514 | if (payCalculator != null) {
515 | double pay = payCalculator.calculatePay(hoursWorked, hourlyRate);
516 | System.out.println("Employee type: " + employeeType);
517 | System.out.println("Hours worked: " + hoursWorked + " hours");
518 | System.out.println("Hourly rate: " + hourlyRate + " won");
519 | System.out.println("Total pay: " + pay + " won");
520 | } else {
521 | System.out.println("Invalid employee type.");
522 | }
523 | }
524 | }
525 | ```
526 | 실제 호출 부분은 switch 구문이 보이지 않고 팩토리를 통해 생성한 직원 유형에 따른 계산식을 호출 한 뒤 결과값만을 출력(return) 해주는 역할이 끝이다.
527 |
528 |
529 | 이 코드는 각 직원 유형에 대한 급여 계산을 모듈화하고, 추가적인 직원 유형이 추가되거나 변경되더라도 수정을 최소화한다.
530 | 물론 새로운 직원 유형을 추가해야 하는 경우 여전히 스위치 문 내에서 새로운 케이스를 처리해주어야 하기 때문에 OCP를 완전히 구현한 것은 아니다.
531 |
532 | 그러나 원본 코드와 비교한다면 더 유지보수 하기 쉽고 기능별 모듈화를 통해 객체 지향적인 코드의 방향에 맞게 설계가 가능하다는 것을 알수 있다.
533 |
534 | 만약 해당 코드에 switch 구문을 제거하고 전략 패턴으로 좀 더 완벽하게 분리하는 것을 추가 하고 싶다면 아래의 예제 코드들을 살펴보면 도움이 될 것이다.
535 |
536 | >
537 | > https://github.com/villainscode/coding-guide/tree/main/src/main/java/net/mingleup/guide/employee/strategy
538 | >
539 | 비슷한 구조로 리팩토링 되어있으므로 이해하기 어렵지 않을 것이다.
540 |
541 | ### 2. 서술적인 이름을 사용하라.
542 | 메서드가 하는 일을 좀 더 잘 표현할 수 있도록 서술형 이름을 지어야 한다.
543 |
544 | 모듈 내에서 함수 이름은 일관성(같은 문구나 명사, 동사등의 사용 규칙)을 유지해야 한다.
545 |
546 | ### 3. 메서드 인수
547 | 이상적인 인수의 갯수는 '없으면 없을수록 좋다' 이다.
548 | 인수를 주더라도 3개 이상은 지양하고, 4개 부터는 특별한 경우를 제외하고는 객체를 넘겨서 처리하는 것이 낫다.
549 | 또한 인수값으로 flag를 넘기다는 것은 메서드에서 여러개의 처리를 하겠다는 의미이므로 boolean 을 통해 내부에서 값을 조작하는 행위는 지양해야 한다.
550 |
551 | ### 4. 명령과 조회를 분리해라.
552 |
553 | ### 5. DRY (Don't Repaet Yourself)
554 |
555 | ### 6. 주석은 코드로 대체할 수 있어야 한다.
556 | 즉, 주석은 나쁜 코드를 보완하지 못한다.
557 | >
558 | > [Code as Docuementation](https://martinfowler.com/bliki/CodeAsDocumentation.html) by Martin fowler
559 | >
560 | 단순 정보만 주는 주석은 아무런 가치가 없고 오히려 코드의 의도만 해치게 되고 잘못된 정보를 제공할 가능성이 높으므로 필수적인 내용만을 기록하는게 좋다.
561 |
562 | # 형식 맞추기
563 | 들여쓰기, 빈 행으로 가시성을 확보하거나 적절한 수준의 행 길이를 맞추는 등의 노력은 결국 코드의 품질과 가독성에 직결된다.
564 | 20자~60바 정도의 가로행이 적절하고 그보다 더 짧을 수록 인지율이 올라간다.
565 | 또한 팀이라면 팀 내부에서 명명법이나 코딩 규칙을 마련해서 이 형식을 따라 개발을 진행해야 한다.
566 |
567 | # 객체와 자료 구조
568 | ### 디미터의 법칙 : 오직 하나의 점(.)에 의해 호출되는 메서드만 호출해야 한다.
569 |
570 | ```java
571 | public class Car {
572 | private Engine engine;
573 |
574 | public Car(Engine engine) {
575 | this.engine = engine;
576 | }
577 |
578 | public Engine getEngine() {
579 | return engine;
580 | }
581 | }
582 |
583 | public class Engine {
584 | public void start() {
585 | // 엔진을 시작하는 로직
586 | }
587 | }
588 |
589 | public class Driver {
590 | public void drive(Car car) {
591 | // 디미터의 법칙 위반: Car 객체에서 엔진에 직접 접근하여 메서드를 호출함
592 | car.getEngine().start(); // 엔진을 시작하는 메서드 호출
593 | }
594 | }
595 | ```
596 | Driver 클래스의 drive 메서드는 Car 클래스의 getEngine을 통해 start 메서드를 호출한다. 이렇게 하는 대신에 Car 클래스 내에서 엔진을 시작하는 메서드를 호출하도록 변경해야 한다.
597 |
598 | ```java
599 | public class Car {
600 | private Engine engine;
601 |
602 | public Car(Engine engine) {
603 | this.engine = engine;
604 | }
605 |
606 | public void startEngine() {
607 | engine.start();
608 | }
609 | }
610 |
611 | public class Engine {
612 | public void start() {
613 | // 엔진을 시작하는 로직
614 | }
615 | }
616 |
617 | public class Driver {
618 | public void drive(Car car) {
619 | car.startEngine(); // 디미터의 법칙 준수: Car 클래스에서 엔진 시작 메서드를 호출함
620 | }
621 | }
622 | ```
623 | Driver 클래스에서는 Car 객체의 startEngine 메서드만 호출하여 엔진을 시작할 수 있으며, 이로써 Car 클래스가 엔진에 대한 내부 구현을 캡슐화하고 외부로 노출하지 않게 된다.
624 |
625 | ### 자료 전달 객체
626 | DTO(Data Transfer Object)를 통해 전송해야 하는 데이터를 명확하게 정의하고 모듈간의 결합도를 낮춰야 한다.
627 | 데이터를 전달하는 과정에서 객체를 직접 사용하면 객체의 내부 구조에 의존하게 되어 결합도가 높아지고, 유연성과 재사용성이 저하된다.
628 | 또한 다른 서비스를 호출하여 데이터를 전송할 때 불필요하게 많은 데이터가 전송될 가능성이 있으므로 적절한 전달 객체를 통해 데이터 전송의 명확성과 안전성을 확보하고, 시스템의 결합도를 낮추어 유지 보수성과 확장성을 향상시켜야 한다.
629 |
630 | # 오류 처리
631 |
632 |
633 | ### 오류 코드보다 예외를 사용하라.
634 | 오류 코드 대신에 예외를 사용하는 것을 권장한다. 예외를 사용하면 코드의 가독성이 향상되고 오류 처리가 명확해지며, 코드의 유연성과 안정성이 향상된다.
635 |
636 | ````java
637 | public class Calculator {
638 | public int divide(int dividend, int divisor) {
639 | if (divisor == 0) {
640 | return -1; // 오류 코드 반환
641 | }
642 | return dividend / divisor;
643 | }
644 | }
645 | ````
646 | 이런 코드는 예외가 발생한건지 로직에서 더 이상 수행할 수 없는 것인지 판단하기 어렵다.
647 | 아래와 같이 바꿔야 한다.
648 |
649 | ```java
650 | public class Calculator {
651 | public int divide(int dividend, int divisor) {
652 | if (divisor == 0) {
653 | throw new IllegalArgumentException("Divisor cannot be zero"); // 예외 발생
654 | }
655 | return dividend / divisor;
656 | }
657 | }
658 | ```
659 |
660 | ### null을 반환하지 마라.
661 | null을 반환하는 것은 호출자가 해당 값이 null인지 확인하고 처리해야 하므로 코드의 가독성과 안전성이 저하될 수 있다.
662 | 따라서 return null 과 같은 코드를 넘겨주지 말고 명시적으로 예외를 발생시켜야 한다.
663 | ```java
664 | if (user == null) {
665 | throw new IllegalArgumentException("User not found with username: " + username);
666 | }
667 | ```
668 |
669 | # 클래스
670 |
671 | ### 클래스 작성 규칙
672 | 클래스 체계상 클래스를 정의하는 표준 자바 규칙은 아래와 같다.
673 | 1. 상수(Constant): 클래스 수준에서 사용되는 상수를 정의. 정적 공개 상수가 맨 처음 나오고 다음으로 정적 비공개가 나온다.
674 | 2. 멤버 변수(Fields): 클래스의 상태를 표현하는 멤버 변수를 정의.
675 | 3. 생성자(Constructor): 클래스의 객체를 생성할 때 호출되는 생성자를 정의.
676 | 4. 메서드(Methods): public 메서드가 나오고 그 뒤에 private 호출 메서드를 순차적으로 작성해준다.
677 |
678 | 아래의 예제를 보자.
679 | ```java
680 | public class ExampleClass {
681 |
682 | // 상수
683 | private static final int MAX_SIZE = 100;
684 | private static final String DEFAULT_NAME = "Default";
685 |
686 | // 멤버 변수
687 | private int size;
688 | private String name;
689 |
690 | // 생성자
691 | public ExampleClass() {
692 | this.size = 0;
693 | this.name = DEFAULT_NAME;
694 | }
695 |
696 | public ExampleClass(int size, String name) {
697 | this.size = size;
698 | this.name = name;
699 | }
700 |
701 | // 메서드
702 | public int getSize() {
703 | return size;
704 | }
705 |
706 | public void setSize(int size) {
707 | this.size = size;
708 | }
709 |
710 | public String getName() {
711 | return name;
712 | }
713 |
714 | public void setName(String name) {
715 | this.name = name;
716 | }
717 |
718 | // 중첩 클래스
719 | private static class NestedClass {
720 | // 중첩 클래스의 멤버 변수, 생성자, 메서드 등
721 | }
722 | }
723 | ```
724 |
725 | ### 클래스는 작게 설계 해야한다.
726 | ### SRP
727 |
728 | SRP(Single Responsibility Principle, 단일 책임 원칙)는 소프트웨어 개발에서 클래스나 모듈은 하나의 책임만 가져야 한다는 의미로, 이는 클래스가 변경되어야 하는 이유가 단 하나뿐이어야 함을 의미한다.
729 | ```java
730 | public class ReportGenerator {
731 |
732 | public void generateReport() {
733 | // 보고서 생성 로직
734 | String data = fetchDataFromDatabase();
735 | formatData(data);
736 | saveReportToFile(data);
737 | }
738 |
739 | private String fetchDataFromDatabase() {
740 | // 데이터베이스에서 데이터를 가져오는 로직
741 | return "Data from database";
742 | }
743 |
744 | private void formatData(String data) {
745 | // 데이터 포맷팅 로직
746 | System.out.println("Formatting data: " + data);
747 | }
748 |
749 | private void saveReportToFile(String data) {
750 | // 파일에 보고서를 저장하는 로직
751 | System.out.println("Saving report to file: " + data);
752 | }
753 | }
754 | ```
755 | 이 코드는 보고서를 생성할 때 각종 타입에 따라 여러 역할을 수행하도록 구현되어 있다.
756 | 이는 SRP 위반이고 클래스가 변경 되어야 하는 이유가 다양하기 때문에 유지보수가 어려워진다. 따라서 아래의 방식으로 변경해야 한다.
757 |
758 | ```java
759 | public class ReportGenerator {
760 |
761 | private DatabaseFetcher databaseFetcher;
762 | private DataFormatter dataFormatter;
763 | private FileSaver fileSaver;
764 |
765 | public ReportGenerator(DatabaseFetcher databaseFetcher, DataFormatter dataFormatter, FileSaver fileSaver) {
766 | this.databaseFetcher = databaseFetcher;
767 | this.dataFormatter = dataFormatter;
768 | this.fileSaver = fileSaver;
769 | }
770 |
771 | public void generateReport() {
772 | String data = databaseFetcher.fetchData();
773 | String formattedData = dataFormatter.formatData(data);
774 | fileSaver.saveToFile(formattedData);
775 | }
776 | }
777 | ```
778 |
779 | ```java
780 | public class DatabaseFetcher {
781 |
782 | public String fetchData() {
783 | // 데이터베이스에서 데이터를 가져오는 로직
784 | return "Data from database";
785 | }
786 | }
787 | ```
788 | ```java
789 | public class DataFormatter {
790 |
791 | public String formatData(String data) {
792 | // 데이터 포맷팅 로직
793 | return "Formatted data: " + data;
794 | }
795 | }
796 | ```
797 |
798 |
799 | ```java
800 | public class FileSaver {
801 |
802 | public void saveToFile(String data) {
803 | // 파일에 보고서를 저장하는 로직
804 | System.out.println("Saving report to file: " + data);
805 | }
806 | }
807 | ```
808 | ReportGenerator 클래스가 보고서 생성만 담당하도록 변경되었고 데이터 가져오기(DatabaseFetcher), 데이터 포맷팅(DataFormatter), 파일 저장(FileSaver) 등의 역할은 각각의 클래스로 분리되었다.
809 |
810 | 이렇게 하면 각 클래스는 단일 책임을 갖게 되고, 변경이 발생할 경우 해당 클래스만 수정하면 되므로 유지 보수가 용이해진다.
811 |
812 | ### 응집도(Cohesion)
813 | 응집도가 높은 코드는 가급적 작게 나눠서 응집도를 낮춰야 한다. 단, 한가지 기능만을 수행하기 위한 목적이라면 상관없다.
814 |
815 |
816 |
817 | # 코드 기본 사용 규칙 모음
818 | ****
819 | 용례를 살펴보기 전에 알아둬야 할 것
820 | >
821 | >
822 | > 하나의 코드 블럭으로 표현한 부분은 상단 설명에 대한 코드 샘플이다.
823 | >
824 | > 상단 설명은 **Bold**로 표기 하였다.
825 | >
826 | > 코드를 비교해야 하는 부분은 위와 아래로 나눠 위는 BadCase, 아래는 GoodCase로 기술하였다.
827 | >
828 |
829 | ### 규칙 1. 파일은 UTF-8로 인코딩 한다.
830 |
831 | import 구문은 줄 바꿈 하지 않는다. (한 줄에 전체를 기술한다.)
832 |
833 | ### 규칙 2. 패키지 이름은 소문자이고, 언더라인을 쓰지 않는다.
834 |
835 | 패키지 구조는 최상위 도메인부터 역순으로 표기하는것이 컨벤션이다.
836 |
837 | ```java
838 | com.company.project
839 | ```
840 |
841 | ### 규칙 3. 클래스 이름은 UpperCamelCase (PascalCase) 로 작성한다. 단어와 단어 사이에는 대문자로 표기하고 명사로 명명한다.
842 |
843 | 약어를 사용하지 않는다. 인터페이스도 동일하다.
844 |
845 | ```bash
846 | CustomerController # 클래스
847 | CustomerService # 클래스
848 | Runnable # 인터페이스
849 | ```
850 |
851 | ### 규칙 4 .메소드 이름은 lowerCamelCase 로 작성한다. 메소드명은 동사 또는 동사구이다.
852 |
853 | 네이밍은 동사나 동사구로 짓고, boolean 값을 반환하는 경우는 is나 has로 시작하고 형용사로 기능하는 단어로 끝난다.
854 |
855 | 객체의 타입을 바꿔서, 다른 타입의 또 다른 객체를 반환하는 인스턴스 메서드의 이름은 보통 toType 형태로 짓는다.(toString, toArray)
856 |
857 | ```
858 | getName()
859 | isEmpty()
860 | hasLevel()
861 | toString()
862 | ```
863 |
864 | ### 규칙 5. 너무 짧은 변수명, 메소드명은 지양하고 의미있는 이름으로 지어야 한다. 메소드 명이나 변수명은 의미를 전달하는 용도로 작성해야 한다.
865 |
866 | ### 규칙 6. 상수는 CONST_CASE 이다. (UPPER_SNAKE_CASE)
867 |
868 | - 상수는 내용이 변경 불가능 해야 한다.
869 | - static final 필드이어야 한다.
870 |
871 |
875 |
876 | ### 규칙 7. 상수가 아닌 필드는 lowerCamelCase로 작성한다.
877 |
878 | 매개변수의 이름은 lowerCamelCase로 작성한다. 매개변수가 4개를 초과할 경우 Object 타입으로 정의하는게 OCP(**Open-Closed Principle)** 설계에 맞는 동작 방식이다.
879 |
880 | 지역변수 이름은 lowerCamelCase로 작성한다. 변수 이름은 짧지만 의미가 있어야 하고, 언더스코어나 특수문자를 쓰면 안된다.
881 |
882 | > *상수는 static final 키워드로 정의된 필드이다. 이 필드의 내용은 불변하며, 메소드는 부작용이 있으면 안된다. 이는 원시 자료형, String, 불변 타입, 불변 타입의 불변 collection을 포함한다. 만약 어떤 인스턴스의 상태가 바뀔 수 있다면 이는 상수가 아니다.*
883 | >
884 |
885 | ```java
886 | // 상수
887 | static final int NUMBER = 5;
888 | static final ImmutableList NAMES = ImmutableList.of("Ed", "Ann");
889 | static final ImmutableMap AGES = ImmutableMap.of("Ed", 35, "Ann", 32);
890 | static final Joiner COMMA_JOINER = Joiner.on(','); // Joiner는 불변이기 때문에.
891 | static final SomeMutableType[] EMPTY_ARRAY = {};
892 | enum SomeEnum { ENUM_CONSTANT }
893 |
894 | // 상수 아님
895 | static String nonFinal = "non-final"; // final 없음
896 | final String nonStatic = "non-static"; // static 아님
897 | static final Set mutableCollection = new HashSet(); // 가변타입 String 사용
898 | static final ImmutableSet mutableElements = ImmutableSet.of(mutable); // 가변타입의 변수로 초기화 함
899 | static final ImmutableMap mutableValues =
900 | ImmutableMap.of("Ed", mutableInstance, "Ann", mutableInstance2); // 가변타입의 변수로 초기화 함
901 | static final Logger logger = Logger.getLogger(MyClass.getName());
902 | static final String[] nonEmptyArray = {"these", "can", "change"};
903 |
904 | ```
905 |
906 | ### 규칙 8. 변수는 lowerCamelCase로 작성한다.
907 |
908 | 단, 변수에서 boolean 타입을 정의 할 경우 메소드와는 다르게 isXXX 와 같은 이름은 피하도록 한다. 일부 직렬화 예외가 발생할 수 있다.
909 |
910 | 임시 변수 (반복문을 순회하는 경우) 라도 가급적 한글자의 변수명 보다는 의미가 있는 이름으로 짓는것이 낫다.
911 |
912 | # Usage (코드 예제 모음)
913 |
914 | ---
915 |
916 | ### 1. 괄호는 Egyptian brackets 을 따른다.
917 |
918 | ```java
919 | return () -> {
920 | while (condition()) {
921 | method();
922 | }
923 | };
924 |
925 | return new MyClass() {
926 | @Override public void method() {
927 | if (condition()) {
928 | try {
929 | something();
930 | } catch (ProblemException e) {
931 | recover();
932 | }
933 | } else if (otherCondition()) {
934 | somethingElse();
935 | } else {
936 | lastThing();
937 | }
938 | }
939 | };
940 | ```
941 |
942 | ### 2. 빈 블럭이나 block-like construct 는 아래와 같이 사용 가능하지만 멀티 블럭에 섞어서 사용할순 없다.
943 |
944 | ```java
945 | // 가능
946 | void doNothing() {}
947 |
948 | // 가능
949 | void doNothingElse() {
950 | }
951 | // 불가능 : multi-block 에서 괄호의 줄바꿈과 빈 블럭을 동시에 사용하지 말것
952 | try {
953 | doSomething();
954 | } catch (Exception e) {}
955 | ```
956 |
957 | ### 3. 공백과 빈줄
958 |
959 | - 들여쓰기 (Indentation)은 4칸의 공백이어야 한다. (4 spaces key)
960 | - “if”, “while”, “return”, “catch”, “switch”, “for” 등의 키워드와 이어지는 괄호에는 공백이 있어야 한다.
961 | - 세미콜론, 쉼표 뒤에는 공백이 있어야 한다.
962 | - 코드의 주요 부분을 식별하기 위해 빈 줄을 넣어준다. 예를 들어, 변수 선언 부분과 변수를 이용한 로직이 들어간다면 이 부분은 구분을 해주기 위해 빈 줄을 삽입해주는것이 좋다.
963 |
964 | ### 4. Static Factory Method 의 관행적인 명명
965 |
966 | - from: 하나의 매개변수를 받아서 해당 타입의 인스턴스를 생성 (형변환)
967 | - valueOf : 매개변수와 동일한 값을 갖는 인스턴스를 반환
968 | - of: 여러개의 매개변수를 받아서 인스턴스를 생성 (aggregate)
969 | - instance or getInstance: (매개변수를 받는다면 매개변수로 명시한 ) 인스턴스를 반환하지만 동일한 인스턴스임을 보장하지 않는다.
970 | - 보통 singleton을 구현할때 많이 사용하는 네이밍이지만 싱글톤의 경우 getInstance는 매개변수를 사용하지 않고 유일한 인스턴스를 반환한다.
971 | - create or newInstance: getInstance 와 유사하지만 매번 새로운 인스턴스를 반환한다.
972 | - getType: getInstance와 같으나 호출하는 클래스가 아닌 다른 타입의 인스턴스를 반환할때 사용
973 | - newType — newInstance와 비슷하지만 호출하는 클래스가 아닌 다른 타입의 인스턴슬 반환할 때 사용. Type은 팩토리 메서드에서 반환된 객체의 유형을 나타낸다.
974 |
975 | ### 5. List 타입에서 빈 컬렉션을 반환할때는 null이나 new 생성 리스트 타입 보다는 Empty Collection을 사용하는것이 GC에 유리하다.
976 |
977 | ```java
978 | List list = new ArrayList();
979 | Set set = new HashSet();
980 | Map map = new HashMap();
981 | ```
982 |
983 | ```java
984 | List list = Collections.emptyList();
985 | Set set = Collections.emptySet();
986 | Map map = Collections.emptyMap()
987 | ```
988 |
989 | ### 6. foreach 루프에서 콜렉션의 요소를 추가하거나 삭제하지 말것
990 |
991 | - Iterator 를 통해 요소를 조작하도록 한다.
992 |
993 | ```java
994 | List a = new ArrayList();
995 | a.add("1");
996 | a.add("2");
997 | for (String temp : a) {
998 | if ("2".equals(temp)){
999 | a.remove(temp);
1000 | }
1001 | } // bad
1002 | ```
1003 |
1004 | ```java
1005 | Iterator it = a.iterator();
1006 | while (it.hasNext()) {
1007 | String temp = it.next();
1008 | if (delete condition) {
1009 | it.remove();
1010 | }
1011 | } // good
1012 | ```
1013 |
1014 | ### 7. Key, Value 콜렉션에서 null이 저장되는지 체크해두어야 한다.
1015 |
1016 | | Collection | Key | Value | Super | Note |
1017 | | --- | --- | --- | --- | --- |
1018 | | Hashtable | Null is not allowed | Null is not allowed | Dictionary | Thread-safe |
1019 | | ConcurrentHashMap | Null is not allowed | Null is not allowed | AbstractMap | Segment lock |
1020 | | TreeMap | Null is not allowed | Null is allowed | AbstractMap | Thread-unsafe |
1021 | | HashMap | Null is allowed | Null is allowed | AbstractMap | Thread-unsafe |
1022 |
1023 | ### 8. Thread
1024 |
1025 | 의미 있는 스레드 이름은 오류 정보를 추적하는 데 도움이 되므로 스레드 또는 스레드 풀을 생성할 때 이름을 지정하기를 추천한다.
1026 |
1027 | ```java
1028 | public class TimerTaskThread extends Thread {
1029 | public TimerTaskThread() {
1030 | super.setName("TimerTaskThread");
1031 | ...
1032 | }
1033 | }
1034 | ```
1035 |
1036 | 스레드는 스레드 풀에서 제공되어야 한다. 명시적으로 스레드를 생성하는것은 허용되지 않는다.
1037 |
1038 | - 스레드 풀을 사용하면 스레드 생성 및 소멸 시간을 줄이고 시스템 리소스를 절약할 수 있다.
1039 | - 스레드 풀을 사용하지 않으면 유사한 스레드가 많이 생성되어 "메모리 부족" 또는 오버 스위칭 문제가 발생한다.
1040 |
1041 | ### 9. String 타입에 적절하지 않은 데이터를 표기하지 말아야 한다.
1042 |
1043 | ```java
1044 | String value = “True”; // bad
1045 | ```
1046 |
1047 | ```java
1048 | boolean value = true; // good
1049 | ```
1050 |
1051 | ### 10. String의 초기화에는 StringUtils.EMPTY을 사용하는 것을 권장한다.
1052 |
1053 | ```java
1054 | String value = “”; // bad
1055 | ```
1056 |
1057 | ```java
1058 | String value = StringUtils.EMPTY; // good
1059 | ```
1060 |
1061 | ### 11. 불필요한 연산은 사용하지 말자.
1062 |
1063 | ```java
1064 | public class BadExample {
1065 | public void foo() {
1066 | boolean flag = false;
1067 | if (flag == true) { // bad
1068 | // some code
1069 | } else {
1070 | // some code
1071 | }
1072 | }
1073 | }
1074 | ```
1075 |
1076 | ```java
1077 | public class GoodExample {
1078 | public void foo() {
1079 | boolean flag = false;
1080 | if (flag) { // good
1081 | // some code
1082 | } else {
1083 | // some code
1084 | }
1085 | }
1086 | }
1087 | ```
1088 |
1089 |
1090 | ### 12. Optional 사용
1091 |
1092 | Exception 혹은 null을 반환하는 method에는 Optional을 사용한다.
1093 |
1094 | - Optional 값은 postfix로 Optional을 사용한다.
1095 | - Exception은 정말 Exception이 필요한 부분에서 사용한다. 정상적인 흐름을 통제하기 위해서는 Exception 보다는 Optional을 사용한다.
1096 | - Optional은 null을 반환될 가능성이 있다는 것을 명시적으로 제시한다.
1097 | - Collection을 반환하는 경우에는 Optional을 사용하지 않는다.
1098 |
1099 | ```java
1100 | public ExportService getExportRequest() {
1101 | ...
1102 | ExportService exportInfo = getExportRequest();
1103 | if(exportInfo == null){ // bad
1104 | ...
1105 | }
1106 | }
1107 | ```
1108 | ```java
1109 | public Optional getExportRequest(){
1110 | ...
1111 | Optional exportInfoOptional = getExportRequest();
1112 | if(exportInfoOptional.isEmpty()){
1113 | // handling the null
1114 | }
1115 | ExportService exportInfo = exportInfoOptional.get();
1116 | }
1117 | ```
1118 |
1119 | ### 13. Predicate과 Valdator 구분해서 사용
1120 | Predicate 와 Validator 의 용도가 다르다.
1121 |
1122 | Predicate
1123 | - https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html
1124 | - Represents a predicate (boolean-valued function) of one argument.
1125 |
1126 |
1127 | Validator
1128 | - https://en.wikiversity.org/wiki/Object-Oriented_Programming/Validation
1129 | - Validate data Generate and handle exceptions Use assertions to validate parameter assumptions
1130 |
1131 |
1132 | 그러므로 정리하면
1133 |
1134 | Predicate는 1개의 arg 을 받아서 true/false 리턴하는 용도으로 사용한다.
1135 |
1136 | Validator는 데이타 유효성을 판단하는 용도으로 경우에 따라서 exception 발생시킨다.
1137 |
1138 | ### 14. 변수가 너무 많은 클래스 구조는 별도의 객체로 분리하자.
1139 |
1140 | ```java
1141 | public class BadExample { //bad
1142 | private int x;
1143 | private int y;
1144 | private int z;
1145 | private int w;
1146 |
1147 | public void foo() {
1148 | // some code
1149 | }
1150 | }
1151 | ```
1152 |
1153 |
1154 | ```java
1155 | public class GoodExample { // good
1156 | private Point position;
1157 |
1158 | public void move(int x, int y) {
1159 | // some code
1160 | }
1161 | }
1162 | ```
1163 | ### 15. 가급적 예외를 구체화 한다.
1164 |
1165 | ```java
1166 | public class BadExample {
1167 | public void foo() {
1168 | try {
1169 | // some code
1170 | } catch (Exception e) {
1171 | // some code
1172 | }
1173 | }
1174 | }
1175 | ```
1176 |
1177 | ```java
1178 | public class GoodExample {
1179 | public void foo() throws SpecificException {
1180 | // some code
1181 | }
1182 | }
1183 | ```
1184 |
1185 | ### 16. 짧은 try ~ catch 블럭 권장
1186 |
1187 | 가독성면에서 try catch 블록은 짧아야 하며, 가급적 `exception` 블럭만 처리하여 가독성을 확보해야 한다.
1188 |
1189 | ```java
1190 | void tryCatchBlock() { // bad
1191 | try {
1192 | statement1;
1193 | statement2;
1194 | statement3; // can throw
1195 | statement4;
1196 | } catch (...) {
1197 | ….
1198 | }
1199 | }
1200 | ```
1201 |
1202 | ```java
1203 | void tryCatchBlock() { // good
1204 | statement1;
1205 | statement2;
1206 | boolean success = false;
1207 | try {
1208 | statement3; // can throw
1209 | success = true;
1210 | } catch (...) {
1211 | …
1212 | }
1213 |
1214 | if (success) {
1215 | statement4;
1216 | }
1217 | }
1218 | ```
1219 |
1220 | 이에 대한 설명으로는 아래 링크를 참고한다.
1221 |
1222 | tight try catch block (https://stackoverflow.com/questions/2633834/should-java-try-blocks-be-scoped-as-tightly-as-possible)
1223 | ```text
1224 | the size of a try block makes no difference in performance. Performance is affected by actually raising exceptions at runtime, and that's independent of the size of the try block.
1225 |
1226 | However, keeping try blocks small can lead to better programs.
1227 |
1228 | You might catch exceptions to recover and proceed, or you might catch them simply to report them to the caller (or to a human, via some UI).
1229 |
1230 | In the first case, failures from which you can recover are often very specific, and this leads to smaller try blocks.
1231 |
1232 | In the second case, where an exception is caught so that it can be wrapped by another exception and re-thrown, or displayed to the user, small try blocks mean that you know more precisely which operation failed, and the higher-level context in which that call was made. This allows you to create more specific error reports.
1233 | ```
1234 |
1235 |
1236 |
1237 |
1238 | ### 17. SpringFramework 에서 제공하는 자체 Util 들은 사용하면 안된다.
1239 |
1240 | - 이는 SpringFramework 내부에서 쓰고자 만든것으로, 만약 Utils성격의 클래스를 쓰고 싶다면 Apache Commons StringUtils를 사용해야 한다.
1241 |
1242 | ### 18. Spring Annotation의 적절한 사용
1243 |
1244 | - @Component는 일반적인 스프링 빈으로 등록된(관리하는) 컴포넌트를 의미한다.
1245 | - @Service @Controller, @Repository 등은 Component 임과 동시에 세부적인 기능을 구분하는 명시를 위한 어노테이션이다.
1246 | - @Controller는 Presentation Layer를 담당하고, @Service는 서비스의 로직, @Repository 어노테이션은 DB에서 동작하는 Persistence 레이어를 표시한다.
1247 |
1248 | 기능 역할을 명확하게 하는 레이어라면 Component 보다는 구체적인 스테레오 타입인 하위 어노테이션을 권장한다.
1249 |
1250 | # DTO (Converter vs Mapper)
1251 |
1252 |
1253 |
1254 | 실제 어플리케이션에서는 Entity 로 조회한 DB의 객체를 비즈니스 논리 계층에서 사용하기 위해 DTO에 매핑되어야 하는데 이를 치환하기 위해 수많은 DTO의 Setter, Getter 중복이 발생하게 된다. 데이터베이스의 테이블인 Entity들을 바로 API의 결과로 노출하면 안된다.
1255 |
1256 | 이를 해결 하기 위해 DTO 레이어를 두어야 하며, 이 DTO들의 Converter 를 작성하여 리턴해주는 패턴으로 개발을 해야 한다.
1257 |
1258 | 양방향이 아닌 단방향 변환의 경우에는 Mapper (한 개체의 속성을 다른 개체로 복사할 경우) 라고 명명하여 사용한다.
1259 |
1260 | Converter 패턴의 목적은 해당 형식 간에 일반적인 양방향 변환 방법을 제공하여 깔끔한 구현을 허용하는 것이다. Converter 패턴을 통해 양방향 매핑을 변환함으로써 기존 코드들의 쓸데없는 Setter/Getter 반복 작업을 혁신적으로 줄일 수 있다.
1261 |
1262 | 아래의 예제를 참고하자.
1263 |
1264 | ```java
1265 | public class Converter {
1266 |
1267 | private final Function fromDto;
1268 | private final Function fromEntity;
1269 |
1270 | public Converter(final Function fromDto, final Function fromEntity) {
1271 | this.fromDto = fromDto;
1272 | this.fromEntity = fromEntity;
1273 | }
1274 |
1275 | public final U convertFromDto(final T dto) {
1276 | return fromDto.apply(dto);
1277 | }
1278 |
1279 | public final T convertFromEntity(final U entity) {
1280 | return fromEntity.apply(entity);
1281 | }
1282 |
1283 | public final List createFromDtos(final Collection dtos) {
1284 | return dtos.stream().map(this::convertFromDto).collect(Collectors.toList());
1285 | }
1286 |
1287 | public final List createFromEntities(final Collection entities) {
1288 | return entities.stream().map(this::convertFromEntity).collect(Collectors.toList());
1289 | }
1290 | }
1291 | ```
1292 |
1293 | 변환 하고자 하는 대상 객체는 위의 기본 Converter 제네릭을 상속하여 구현한다.
1294 |
1295 | ```java
1296 | public class UserConverter extends Converter {
1297 |
1298 | public UserConverter() {
1299 | super(UserConverter::convertToEntity, UserConverter::convertToDto);
1300 | }
1301 |
1302 | private static UserDto convertToDto(User user) {
1303 | return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId());
1304 | }
1305 |
1306 | private static User convertToEntity(UserDto dto) {
1307 | return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail());
1308 | }
1309 |
1310 | }
1311 | ```
1312 |
1313 | UserDto 와 User Entity 의 변환은 아래와 같이 간단하게 처리 할 수 있다.
1314 |
1315 | ```java
1316 | var userConverter = new UserConverter();
1317 | var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com");
1318 | var user = userConverter.convertFromDto(dtoUser);
1319 | ```
1320 |
1321 | 
1322 |
1323 | 그림 2
1324 |
1325 | UserConvert 를 통해 dto와 entity 사이의 양방향 변환을 좀 더 명확하고 심플하게 처리할 수 있다.
1326 |
1327 | 제너릭 코드의 확장성에 대해서 굉장히 유연한 코드들을 작성할 수 있게 되므로써 Converter 코드들도 라이브러리등을 통해 코드를 자동으로 변환하게 만들수도 있다.
1328 |
1329 | 대표적으로 ModelMapper (참고 링크 : [https://modelmapper.org/getting-started/](https://modelmapper.org/getting-started/))가 유용한데, 아래와 같이 제너릭으로 확장해서 클래스간의 변환을 간편하게 적용할 수 있다.
1330 |
1331 | ```java
1332 | /**
1333 | * @author CodeVillains
1334 | */
1335 | @Component
1336 | public class EntityConverter {
1337 | private final ModelMapper modelMapper;
1338 | public EntityConverter(ModelMapper modelMapper) {
1339 | this.modelMapper = modelMapper;
1340 | }
1341 |
1342 | public D convertToDto(S entity, Class dto) {
1343 | return modelMapper.map(entity, dto);
1344 | }
1345 |
1346 | public D convertToEntity(S dto, Class entity) {
1347 | return modelMapper.map(dto, entity);
1348 | }
1349 |
1350 | }
1351 | ```
1352 |
1353 | 이 코드를 호출하는 부분에서 아래와 같이 사용할 수 있다.
1354 |
1355 | ```java
1356 | private final GoalService goalService;
1357 | private final EntityConverter entityConverter;
1358 | ...
1359 |
1360 | public GoalResponse findById(Long id) {
1361 | Goal goal = goalService.findById(id).orElseThrow(() -> new GoalNotFoundException(id));
1362 | GoalResponse response = entityConverter.convertToDto(goal, GoalResponse.class); // entity to dto
1363 | return response;
1364 | }
1365 | ```
1366 |
1367 | Converter 클래스보다 ModelMapper 라이브러리를 이용한 EntityConverter 가 코드상으로 훨씬 이해하기 편할 것이다.
1368 |
1369 | 만약 List 타입의 오브젝트 매핑을 하고자 한다면 아래와 같이 변환 코드를 만들어서 사용할 수 있다.
1370 |
1371 | ```java
1372 | public List convertListToDto(List sourceList, Class destinationType) {
1373 | return sourceList.stream()
1374 | .map(source -> modelMapper.map(source, destinationType))
1375 | .collect(Collectors.toList());
1376 | }
1377 |
1378 | public List convertListToEntity(List sourceList, Class destinationType) {
1379 | return sourceList.stream()
1380 | .map(source -> modelMapper.map(source, destinationType))
1381 | .collect(Collectors.toList());
1382 | }
1383 | ```
1384 |
1385 | 실제 호출하는 부분의 코드는 아래와 같다.
1386 |
1387 | ```java
1388 | List dtoList = converter.convertListToDto(entityList, DtoClass.class);
1389 | for (DtoClass dto : dtoList) {
1390 | System.out.println(dto.getId());
1391 | System.out.println(dto.getName());
1392 | }
1393 |
1394 | List convertedEntityList = converter.convertListToEntity(dtoList, EntityClass.class);
1395 | for (EntityClass entity : convertedEntityList) {
1396 | System.out.println(entity.getId());
1397 | System.out.println(entity.getName());
1398 | }
1399 | ```
1400 |
1401 |
--------------------------------------------------------------------------------
/book_600.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villainscode/coding-guide/eafaa0b93d2dd279e8035798d6630b850e666640/book_600.png
--------------------------------------------------------------------------------
/converter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/villainscode/coding-guide/eafaa0b93d2dd279e8035798d6630b850e666640/converter.png
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/MainApp.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class MainApp {
8 |
9 | public static void main(String[] args) {
10 | System.out.println("hello world");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/EmployeePaySwitch.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description 장점 : switch 구문을 이용해서 로직을 직관적으로 이해할 수 있다. 단순한 로직의 경우 직접 구현하는 형태로 초보자용 코드로 적합하다. 단점 : 코드 중복 - switch 문 내의 각 사례가 서로 다른 계산을 처리하여 중복이 발생하므로
6 | * 일정 수준의 코드 중복이 발생한다. OCP 위반 - 직원 유형이 추가된다거나 급여 계산식이 바뀔 경우 스위치 구문내부가 급격하게 늘어나고 각각의 코드 로직이 복잡해져 유지관리가 어렵다.(개방폐쇄 원칙 위반)
7 | */
8 | public class EmployeePaySwitch {
9 |
10 |
11 | public static void main(String[] args) {
12 | String employeeType = "Contract"; // 직원 유형 설정
13 | double hoursWorked = 40; // 근무 시간
14 | double hourlyRate = 15000; // 시간당 급여
15 |
16 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate);
17 | System.out.println("직원 유형: " + employeeType);
18 | System.out.println("근무 시간: " + hoursWorked + "시간");
19 | System.out.println("시간당 급여: " + hourlyRate + "원");
20 | System.out.println("총 급여: " + pay + "원");
21 | }
22 |
23 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) {
24 | double pay = 0;
25 |
26 | switch (employeeType) {
27 | case "Regular":
28 | pay = hoursWorked * hourlyRate + 50000; // 정규직 추가 급여
29 | break;
30 | case "Contract":
31 | pay = hoursWorked * hourlyRate;
32 | break;
33 | case "Temporary":
34 | pay = hoursWorked * hourlyRate * 0.8; // 임시직 급여율
35 | break;
36 | case "Asist":
37 | pay = hoursWorked * hourlyRate * 0.7; // 어시스트 급여율
38 | break;
39 | default:
40 | System.out.println("유효하지 않은 직원 유형입니다.");
41 | }
42 | return pay;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/EmployeePaySwitchRefactor.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description : switch 구문 내에서 로직이 들어가는 부분을 공통 메서드로 분리하였다.
6 | * 장점 : 공통 급여 계산 로직을 별도의 메서드로 추출하면 중복이 줄어들고 코드 재사용성이 좋아졌다. 각 계산 로직이 메서드 호출 형태로 변경 되어 가독성 및 변경
7 | * 영향도가 줄어들어 유지보수가 더 쉽다.
8 | * 단점 : 아직 switch 문에 로직이 존재하여 수정이 필요할 수 있고 (OCP) switch 문이 주요 흐름이므로 확장성에는 완전히 유용해보이지 않는다.
9 | */
10 | public class EmployeePaySwitchRefactor {
11 |
12 |
13 | public static void main(String[] args) {
14 | String employeeType = "Contract"; // 직원 유형 설정
15 | double hoursWorked = 40; // 근무 시간
16 | double hourlyRate = 15000; // 시간당 급여
17 |
18 | double pay = calculatePay(employeeType, hoursWorked, hourlyRate);
19 | System.out.println("직원 유형: " + employeeType);
20 | System.out.println("근무 시간: " + hoursWorked + "시간");
21 | System.out.println("시간당 급여: " + hourlyRate + "원");
22 | System.out.println("총 급여: " + pay + "원");
23 | }
24 |
25 | public static double calculatePay(String employeeType, double hoursWorked, double hourlyRate) {
26 | double pay = 0;
27 |
28 | switch (employeeType) {
29 | case "Regular":
30 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0); // 정규직 추가 급여
31 | break;
32 | case "Contract":
33 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 1.0);
34 | break;
35 | case "Temporary":
36 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.8); // 임시직 급여율
37 | break;
38 | case "Assist":
39 | pay = calculatePayWithAdjustment(hoursWorked, hourlyRate, 0.7); // 어시스트 급여율
40 | break;
41 | default:
42 | System.out.println("유효하지 않은 직원 유형입니다.");
43 | }
44 |
45 | return pay;
46 | }
47 |
48 | private static double calculatePayWithAdjustment(double hoursWorked, double hourlyRate, double adjustment) {
49 | return hoursWorked * hourlyRate * adjustment;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/AssistPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | class AssistPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate * 0.7; // Assist employee
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/ContractPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class ContractPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/EmployeePayAbstractFatoryMain.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class EmployeePayAbstractFatoryMain {
8 |
9 | public static void main(String[] args) {
10 | String employeeType = "Contract"; // Employee type setting
11 | double hoursWorked = 40; // Hours worked
12 | double hourlyRate = 15000; // Hourly rate
13 |
14 | PayCalculator payCalculator = PayCalculatorFactory.createPayCalculator(employeeType);
15 | if (payCalculator != null) {
16 | double pay = payCalculator.calculatePay(hoursWorked, hourlyRate);
17 | System.out.println("Employee type: " + employeeType);
18 | System.out.println("Hours worked: " + hoursWorked + " hours");
19 | System.out.println("Hourly rate: " + hourlyRate + " won");
20 | System.out.println("Total pay: " + pay + " won");
21 | } else {
22 | System.out.println("Invalid employee type.");
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/PayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description : 급여 계산에 대한 공통 인터페이스 정의
6 | */
7 | public interface PayCalculator {
8 | double calculatePay(double hoursWorked, double hourlyRate);
9 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/PayCalculatorFactory.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class PayCalculatorFactory {
8 |
9 | static PayCalculator createPayCalculator(String employeeType) {
10 | switch (employeeType) {
11 | case "Regular":
12 | return new RegularPayCalculator();
13 | case "Contract":
14 | return new ContractPayCalculator();
15 | case "Temporary":
16 | return new TemporaryPayCalculator();
17 | case "Assist":
18 | return new AssistPayCalculator();
19 | default:
20 | return null;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/RegularPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class RegularPayCalculator implements PayCalculator {
8 |
9 | @Override
10 | public double calculatePay(double hoursWorked, double hourlyRate) {
11 | return hoursWorked * hourlyRate + 50000;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/abstractfactory/TemporaryPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.abstractfactory;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class TemporaryPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate * 0.8; // Temporary employe
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/AssistPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class AssistPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate * 0.7; // Assist employee
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/ContractPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class ContractPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate;
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/EmployeePayStrategyMain.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class EmployeePayStrategyMain {
8 | public static void main(String[] args) {
9 | String employeeType = "Contract";
10 | double hoursWorked = 40;
11 | double hourlyRate = 15000;
12 |
13 | // factory를 통해 지정된 유형의 인스턴스를 얻는다.
14 | PayCalculator payCalculator = PayCalculatorFactory.getPayCalculator(employeeType);
15 | // 해당 인스턴스의 계산 로직을 호출
16 | double pay = payCalculator.calculatePay(hoursWorked, hourlyRate);
17 |
18 | System.out.println("직원 유형: " + employeeType);
19 | System.out.println("근무 시간: " + hoursWorked + "시간");
20 | System.out.println("시간당 급여: " + hourlyRate + "원");
21 | System.out.println("총 급여: " + pay + "원");
22 | }
23 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/PayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description : 급여 계산에 대한 공통 인터페이스 정의
6 | */
7 | public interface PayCalculator {
8 | double calculatePay(double hoursWorked, double hourlyRate);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/PayCalculatorFactory.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * @author CodeVillains
8 | * @description :
9 | */
10 | public class PayCalculatorFactory {
11 | private static final Map calculatorMap = new HashMap<>();
12 |
13 | static {
14 | // 각 계산기 인스턴스
15 | calculatorMap.put("Regular", new RegularPayCalculator());
16 | calculatorMap.put("Contract", new ContractPayCalculator());
17 | calculatorMap.put("Temporary", new TemporaryPayCalculator());
18 | calculatorMap.put("Assist", new AssistPayCalculator());
19 | }
20 |
21 | static PayCalculator getPayCalculator(String employeeType) {
22 | return calculatorMap.getOrDefault(employeeType, (hoursWorked, hourlyRate) -> {
23 | System.out.println("유효하지 않은 직원 유형입니다.");
24 | return 0.0;
25 | });
26 | }
27 | }
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/RegularPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class RegularPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate + 50000; // regula employee
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/net/mingleup/guide/employee/strategy/TemporaryPayCalculator.java:
--------------------------------------------------------------------------------
1 | package net.mingleup.guide.employee.strategy;
2 |
3 | /**
4 | * @author CodeVillains
5 | * @description :
6 | */
7 | public class TemporaryPayCalculator implements PayCalculator {
8 | @Override
9 | public double calculatePay(double hoursWorked, double hourlyRate) {
10 | return hoursWorked * hourlyRate * 0.8; // Temporary employee
11 | }
12 | }
--------------------------------------------------------------------------------