블로그
전체 72024. 03. 13.
0
[디자인 패턴] 싱글톤 패턴
싱글톤 패턴(Singletone Pattern)정의 : 유일한 객체를 만들기 위한 코드 패턴장점 : 인스턴스를 추가로 생성하지 않고, 기존에 생성된 인스턴스를 재사용하기 때문에 리소스를 절약할 수 있다.단점 : 대표적인 객체 지향의 안티 패턴,싱글톤 객체에 여러 모듈이 의존하면서 결합도 상승 (OCP 위배)싱글톤 객체가 지는 책임이 많음 (SRP 위배)모듈들이 인터페이스가 아니라 구체적인 객체에 의존하게 만듦 (DIP 위배)단, 스프링 프레임워크를 사용하면 Ioc(Inversion Of Control)를 통해 일반 객체도 싱글톤 객체처럼 쓸 수 있게 함으로 싱글톤의 단점을 상쇄할 수 있다.구현Bill Pugh Solution (LazyHolder) -> 장: 필요할 때 로드되어 성능이 좋음 단: 클라이언트가 임의로 싱글톤 파괴 가능class Singleton { private Singleton() {} // static 중첩 클래스 사용 // Holder를 통해 클래스 로드 시점을 getInstance의 메소드 호출 시점으로 미룸(Lazy) // 유일한 객체 생성을 위해 생성자 private private static class SingleInstanceHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingleInstanceHolder.INSTANCE; } }Enum -> 장: 클라이언트가 임의로 싱글톤 파괴 불가능 단: 멀티톤으로 전환 시 코드를 다시 작성해야 함enum SingletonEnum { INSTANCE; private final Client dbClient; SingletonEnum() { dbClient = Database.getClient(); } public static SingletonEnum getInstance() { return INSTANCE; } public Client getClient() { return dbClient; } } public class Main { public static void main(String[] args) { SingletonEnum singleton = SingletonEnum.getInstance(); singleton.getClient(); } }참고 글: https://inpa.tistory.com/entry/JAVA-☕-열거형Enum-타입-문법-활용-정리 [Inpa Dev 👨💻:티스토리]
백엔드
・
디자인패턴
・
싱글톤
2024. 03. 12.
0
[도서 정리] - 결합도와 응집도 - { 오브젝트 : 코드로 이해하는 객체지향 설계 }
결합도 : 의존성의 정도, 다른 모듈에 대해 얼마나 많은 지식을 가지고 있는 지를 나타내는 척도.'결합도가 높다'는 두 가지 의미로 해석 될 수 있다.A라는 모듈이 B 모듈의 내부 구현을 자세히 알고 있어, B의 작은 변경에도 A까지 변경해야 할 경우.A라는 모듈을 B, C, D, E 등 다수의 모듈이 의존하고 있어. A를 변경할 시 나머지 모듈도 함께 변경해야 할 경우.즉, '결합도가 낮다'라고 것은 단순히 의존하는 모듈이 적은 것이 아니다. 변경이 일어날 수 있는 부분을 모두 철저히 캡슐화하여, 내부 구현 변경에 의하여 의존하는 다른 모듈이 영향을 받지 않도록 억제 하는 것이다.응집도 : 모듈 내부 요소들이 서로 연관돼 있는 정도.'응집도가 낮다'면 다음과 같은 특성이 나타난다.클래스 변경되는 이유가 두 가지 이상이다.클래스의 인스턴스를 초기화하는 시점에 경우에 따라 다른 속성들을 초기화하고 있다.메서드 그룹이 속성 그룹을 사용하는 여부가 갈린다.위의 3 경우는 해당 클래스의 응집도가 낮다는 지표임으로, 위의 증상이 나타난다면 클래스를 분리할 것을 고려해야 한다. ✒ 정리결국, 모든 것은 변경과 관련되어 있다. 변경이 일어날 수 있는 부분을 추상화하여 구현을 감추고 객체가 인터페이스에만 의존하게 한다면 자연스레 결합도가 낮아진다.서로 다른 속성을 써서 작동하는 비슷한 메소드를 캡슐화하여 새로운 객체로 추출하면, 자연스레 응집도가 높아지게 된다.그렇기에 우리는 적절하게 도메인을 뽑아낸 뒤, 협력에서 책임을 찾고 이를 적절하게 분배해야 할 것이다. 그리고 구현을 하며, 끊임 없이 리팩토링을 한다면 객체지향이 잘 지켜진 코드를 얻을 수 있을 것이다.
백엔드
・
결합도
・
응집도
・
객체지향
・
OOP
2024. 03. 09.
0
[강의 정리] - 컨벤션 교정 - {Java/Spring 주니어 개발자를 위한 오답노트}
해당 글은 김우근 강사님의 "Java/Spring 주니어 개발자를 위한 오답노트"를 수강한 내용을 바탕으로 정리한 글입니다!!!강의 링크!!컨벤션이란?Convetion은 사전상 관습을 의미한다. 즉, 코딩에서 컨벤션은 문맥상 코드를 작성할 때 지켜야하는 관례나 관습을 통칭한다. 컨벤션을 지킴으로써 읽고, 관리하기 쉬운 코드를 작성할 수 있게 된다. 이는 개발자들의 협업을 용이하게 하고, 유지 보수를 쉽게 한다.Java 표기법자바의 표기법은 아래와 같다.변수 이름 : camelCase함수 이름 : camelCase클래스 이름 : PascalCase패키지 이름 : alllowercase상수 : UPPER_SNAKE_CASE변수의 타입의 약어로 변수명을 시작하는 표기법을 헝가리안 표기법이라 하는데, 이는 과거 IDE가 좋지 않은 시절의 산물임으로 사용하지 않는다.변수명 작명 규칙줄여 쓰지 않기 줄여 썼을 때, 다른 의미의 단어임에도 축약어가 같은 경우가 존재한다. 그럴 경우, 변수명만을 보고 해당 객체가 어떤 동작을 할 지 알 수가 없다.ex) webSocket -> ws , webServer -> ws단, Identifier -> Id , application -> app 과 같이 관례상 굳어져 축약어를 사용해도 무관한 경우가 존재한다.축약어에 대한 관례코딩이 아닌 글을 쓸 때, 축약어는 모든 글자를 대문자로 쓰는 것이 관례다. 그러나 코딩에서 축약어를 모두 대문자로 쓰게 되면 상수와 구분이 어려워지고, 축약어로 변수명을 시작할 경우 첫글자를 소문자로 해야한다는 규칙과 충돌한다. 따라서, 축약어도 일반 명사로 보고 같은 규칙을 적용한다.Util이라는 이름 작명 지양하기Util이 붙게되면 모든 static 메소드가 모인다. static이 모였다고 해서 반드시 Util인 것이 아니다. 의미에 맞는 이름을 선정해야 한다. (이 부분은 경험이 부족하여 머리로는 이해하였으나, 감각적으로는 이해되지 않았다.)get vs findget -> return type이 T인 경우find -> return typd이 Optional인 경우get 남발하지 않기get은 객체가 가지고 있는 필드값을 그대로 가져올 때만 사용한다. 추가적인 연산을 하고 가져온다면 get이 아닌 적절한 변수명을 짓도록 하자.롬복 getter setter 지양하기getter는 캡슐화를 저해하고 (private로 숨겨둔 필드가 모두 드러난다.) setter는 객체를 불변하지 않게 한다.start end 값 설정하기메소드의 파라미터로 시작값과 끝값을 받는다면, 시작값은 자신을 포함하고 끝값은 자신을 포함하지 않는다. 강의중 몰랐던 단어 간단히 집고 가기!일급 컬렉션일급 컬렉션 -> Collection을 Wrapping 하면서 그 외의 다른 멤버 변수가 없는 상태public class Cars { private List carList; public Cars(List carList) { this.carList = carList; } }일급 컬렉션을 사용하면 얻는 이점비지니스에 종속적인 자료구조Collection의 불변성을 보장상태와 행위를 한 곳에서 관리이름이 있는 컬렉션
백엔드
・
Java
・
Convention
2024. 03. 07.
0
[인프런 워밍업 0기] Visitor Pattern 적용해보기!
이는 프로젝트 3단계를 해결하며, 새롭게 공부한 Visitor Patter에 대한 기록입니다.저는 연차 기록(LeaveRecord)과 출퇴근 기록(WorkRecord)을 근무 상태(AttendanceStatus)의 서브클래스로 상정했고, 추후에 출장이나 결근과 같이 상태가 확장될 수도 있다고 보았습니다.프로젝트 3단계, 요청 달의 연차 기록과 근무 기록을 모두 출력하는 기능을 구현하는데 있어 위의 도메인 구조는 다음과 같은 고려 사항이 있었습니다.요청 달의 모든 연차 기록과 근무 기록을 부모 클래스로 모두 호출한다.연차 사용여부를 근무를 한 일자에는 false로 하지만, 연차를 사용한 일자에는 true로 반환한다.저는 이 부분에 있어 다른 두 객체가 같은 결과를 내지만 처리 방식이 다른 경우라고 생각했습니다.그래서 이를 어떻게 해결할 지 인터넷을 뒤지다가 Visitor 패턴이라는 것을 발견하게 됩니다.Vistor 패턴Vistor 패턴은 서로 다른 상태를 가진 객체가 공통 로직을 수행해야 할 때 유용합니다.여러 객체의 공통 목표의 수행 방식이 다른 로직을 해당 객체가 아닌, Visitor라는 객체에게 위임합니다.이렇게 되면, 다음과 같은 이점이 발생합니다.객체는 자신과 관계성이 떨어지는 로직을 다른 객체에게 위임함으로써 응집도가 상승합니다.여러 객체가 공통 목표로 가진 로직이, Visitor 객체에게 집중됨으로써 관리가 용이해집니다.기존 설계를 크게 해치지 않고, 다양한 로직을 추가할 수 있습니다. Visitor 패턴을 사용하기 위해서는 2가지가 필요합니다.객체들이 Visitor를 수용할 수 있게하는 Element 인터페이스와 Visitor 인터페이스입니다.// Element 인터페이스 public interface AttendanceElement { Detail accept(Visitor visitor); } // Visitor 인터페이스 public interface Visitor { Detail visitWorkRecord(WorkRecord workRecord); Detail visitLeaveRecord(LeaveRecord leaveRecord); }Element인터페이스는 Visitor를 수용할 수 있게하는 accept 메소드만을 구현하고, 그렇게 각 객체에서 받아들여진 Visitor는 객체의 상태에 맞게 원하는 메소드를 실행하게 하면 됩니다.public abstract class AttendanceStatus implements AttendanceElement { @Override abstract public Detail accept(Visitor visitor); } public class LeaveRecord extends AttendanceStatus { @Override public Detail accept(Visitor visitor) { return visitor.visitLeaveRecord(this); } } public class WorkRecord extends AttendanceStatus { @Override public Detail accept(Visitor visitor) { return visitor.visitWorkRecord(this); } }이렇게 Element인터페이스를 상속받은 각 객체들은 accept를 구현하게 되고, Visitor를 수용해 자신의 상태에 맞는 메소드를 실행할 수 있게 되었습니다.public class AttendanceVisitor implements Visitor{ @Override public Detail visitWorkRecord(WorkRecord workRecord) { return new Detail( workRecord.getAttendanceDate(), workRecord.getWorkingMinute(), false); } @Override public Detail visitLeaveRecord(LeaveRecord leaveRecord) { return new Detail( leaveRecord.getAttendanceDate(), 0L, true); } }Service 단에서 로직을 처리할 수 있는 Visitor를 전달함으로써 손쉽게 기존 설계를 해치지 않고 로직을 처리할 수 있게 되었습니다!이번 프로젝트를 해결하며 새로운 디자인 패턴을 공부하고 적용해볼 수 있어 좋은 기회였다 생각합니다!
JPA
・
visitor-pattern
・
워밍업클럽
2024. 03. 03.
0
[인프런 워밍업 0기] JPA findById() vs getReferenceById()
다음은 JPA에서 연관관계 매핑이 되어있는 객체를 save하는 상황이다. @Transactional public void recordArrivalTime(Long memberId, LocalDate attendanceDate, LocalTime startTime) { Member member = memberRepository.findById(memberId); workTimeRecordService.recordArrivalTime(member, attendanceDate, startTime); }WorkTimeRecord를 save하기 위해서는 Member를 알아야만 했고,Member를 찾기 위해서 기존에는 Id값으로 Member를 찾아왔지만, 문득 이는 굉장히 '비효율적'이라는 생각이 들었다.WorkTimeRecord를 저장하기 위해 필요한 것은 Member의 Id 값이지, Member의 모든 정보를 찾아올 필요는 없었기 때문에 불필요한 select문이 발생하는 셈이다.그래서, 이리저리 알아보던 차, Proxy객체라는 존재와 getReferenceById()라는 JpaRepository의 메소드를 알게 되었다.Proxy는 여러 곳에서 쓰이는 용어이지만, JPA를 구현하고 있는 hibernate에서는 Proxy객체는 실제 Entity(여기서는 Member)를 상속 받은 껍데기 객체이다.Entity를 상속받았기 때문에 Entity의 모든 역할을 대신 수행할 수 있으나, 내부의 값은 모두 비어있는 상태가 된다.만일, 상태가 필요한 순간이 되면 그때 JPA의 영속성 컨텍스트를 통하여 실제 Entity를 조회하여 값을 찾아오게 된다.위와 같은 기술을 Lazy라고 한다.즉, 나의 처음의 고민은 이를 통해 해결할 수 있게 되었다. @Transactional public void recordArrivalTime(Long memberId, LocalDate attendanceDate, LocalTime startTime) { Member member = memberRepository.getReferneceById(memberId); workTimeRecordService.recordArrivalTime(member, attendanceDate, startTime); }이렇게, Proxy객체로 Member를 찾게 되면, 실제로 select문은 발생하지 않고 정상적으로 WorkTimeRecord에 Member값을 할당할 수 있게 된다.
백엔드
・
JPA
・
인프런워밍업
・
MVC
2024. 02. 23.
1
[인프런 워밍업 0기 Day5] 한 걸음 더! 객체 지향으로 클린코딩!
!! 해당 글은 독자가 인프런 워밍업 0기를 수강하고 있다는 전제 하에 작성되었습니다 !!과제 수행에 있어 스프링부트 3.2.2 버전을 사용하고 있다는 점을 미리 알려드립니다!안녕하세요🙌! 인프런 워밍업 5일차 과제입니다!이번에는 클린코드의 중요성에 대하여 학습하고 클린코드를 작성하는 방법에 대해 배웠습니다!😎저는 이번 과제를 그동안 책으로만 공부했던 객체 지향을 적용하여 해결해보고자 했습니다.이론으로만 공부했기 때문에 많이 서툴 수 있다는 점! 그렇기에, 저의 말이 정답이 아니라는 점을 미리 말씀 드리며!지금부터 객체 지향을 향한 저의 여정을 소개하겠습니다! 🤸♂️💡과제 살펴보기아래는 과제로 주어진 지저분한 코드입니다! 바라보기만 해도 머리가 어지러운데요.. 😥public class Main { public static void main(String[] args) throws Exception { System.out.print("숫자를 입력하세요 : "); Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(); int r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0; for (int i= 0; i = 0 && b = 1 && b = 2 && b = 3 && b = 4 && b = 5 && b 위 코드는 결국 다음과 같은 동작을 수행합니다!주어지는 숫자를 하나 받는다.해당 숫자만큼 주사위를 던져, 각 숫자가 몇 번 나왔는지 알려준다.위 코드처럼 로직을 열거하여 프로그래밍 하는 방식을 절차 지향 프로그래밍이라 말하며, 저는 이 코드를 클린코드로 수정해야 합니다!위의 코드는 클린 코딩의 중요성을 위한 극단적인 예시일 뿐, 절차 지향이라 지저분한 것이 아닙니다!지나친 추상화는 오히려 코드의 가독성을 떨어트릴 수 있으니 무조건 '객체 지향이 좋다!'의 글이 아니라는 점 알아주세요!저는 위의 코드를 깔끔하게 바꾸기 위해서 아래와 같이 객체 지향의 형태로 수정하여 과제를 완수했습니다!public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Dice luckyDice = UnknownDice.decideFaces(6); Note memoPad = new MemoPad(); Dealer dealer = Dealer.playWith(luckyDice, memoPad); System.out.print("숫자를 입력하세요 : "); dealer.rollDiceMultipleTimes(scanner.nextInt()); dealer.tellTheResult(); } }어떤가요? 내부 로직이 어떻게 되는지는 모르겠지만, 메소드명만을 보고도 원래의 코드와 똑같은 동작을 수행할 것이 기대할 수 있습니다!그렇다면, 내부는 어떻게 구현됐을까요? 내부 구현을 함께 살펴보기 전에 객체 지향이 무엇인지 짧게 설명 드리고 가겠습니다! 💡객체 지향 프로그래밍이란?객체 지향은 '프로그램이'라는 거대한 로직을 '객체'라는 작은 역할로 나누고, 그 '객체'들끼리 상호 협력하여 데이터를 처리하는 방식을 말합니다!어젯밤에 저는 오렌지를 하나 먹었는데요! 이 오렌지가 집에 오기 까지의 과정을 '프로그램'이라고 보겠습니다.그리고, 지금부터 오렌지의 여정을 절차 지향적으로 설명해보겠습니다! 😎먼저 오렌지 나무를 볕 좋은 곳을 찾아서 심고, 거름도 포대를 찢어서 뿌리 주변에 뿌려주고, 해충도 유기농을 위해 핀셋으로 잡아주고.. 설명이 끝도 없이 길어집니다!그렇다면 이번엔 오렌지의 여정을 객체 지향적으로 설명해볼까요?오렌지를 농부가 '재배'하고, 운송 회사가 '운송'하고, 마트에서 '판매'되어 저의 집까지 왔습니다!어떤가요? 훨씬 설명이 쉽고 이해하기 좋지 않은가요? 오렌지가 어떻게 재배되었는지, 운송되었는지, 판매되었는지 우리는 구체적으로 알 필요가 없습니다! 궁금하지도 않고요!이렇게 내부 구현을 숨기고, 역할 만을 외부에 공개하여 코드의 가독성을 올리고 협업을 용이하게 하는 것이 객체 지향의 장점입니다!이는 개체 지향의 단편적인 장점입니다! 더 많은 장점이 있지만, 지금은 클린코딩과 관련하여 추상화와 캡슐화로 인한 장점 만을 이야기하고 넘어가겠습니다! 😭😭 💡도메인 선정하기! 자! 이제 다시 과제로 넘어와 보겠습니다~ 위에서 객체 지향에서 중요한 것은 역할과 협력이라고 했습니다!우리는 요구 사항을 잘 읽고 작은 역할을 찾고 그 역할을 수행할 주인공(도메인)을 선정해야 합니다. 😎사용자로부터 숫자를 하나 입력 받는다.해당 숫자만큼 주사위를 던져, 각 숫자가 몇 번 나왔는지 알려준다. -> 핵심 로직!!저는 핵심 로직에서 두 가지 역할을 찾았습니다! 하나는 무작위의 수를 생성하는 것, 다른 하나는 생성된 수를 각각 세는 것입니다.그리고, 발견한 역할을 바탕으로 이를 수행할 두 가지 도메인을 만들었습니다.무작위 수를 생성하는 🎲주사위(Dice)와 이를 기록해주는 📃노트(Note)입니다!그렇다면, 이 둘을 재빠르게 설계해 볼까요?abstract public class Dice { private final int faces; protected Dice(int faces) { this.faces = faces; } protected int getFaces() { return this.faces; } abstract int throwDice(); } public interface Note { void record(Integer number); void printTheResult(); }다양한 경험을 위해, 저는 주사위는 추상 클래스로 노트는 인터페이스로 만들었습니다!Dice의 throwDice()는 주사위를 굴리는 행위를 나타내며 무작위 수를 생성합니다!Note의 record()는 입력되는 수를 기록하고 기록된 수는 printTheResult()를 통해 출력할 예정입니다!이렇게, 추상 클래스와 인터페이스로 만드는 이유는 해당 동작을 수행할 수 있다면 그게 무엇이든 역할을 대체할 수 있게 하기 위함입니다! 숫자를 기록하고 출력할 수 있다면 메모지던, 스케치북이던, 스마트폰이던 상관이 없습니다!이제 이 둘을 구현해보겠습니다!public class UnknownDice extends Dice { private UnknownDice(int faces) { super(faces); } public static UnknownDice decideFaces(int faces) { return new UnknownDice(faces); } @Override public int throwDice() { return (int) (Math.random() * super.getFaces()) + 1; } } import java.util.HashMap; import java.util.Map; public class MemoPad implements Note { private final Map page = new HashMap(); @Override public void record(Integer number) { page.put(number, page.getOrDefault(number, 0) + 1); } @Override public void printTheResult() { for(Map.Entry number : page.entrySet()){ System.out.printf("%d은(는) %d번 나왔습니다.\n", number.getKey(), number.getValue()); } } }이렇게, 구현이 끝이 났습니다! 그런데, 이럴 수가! 여전히 문제가 있습니다. 도메인을 구현한 것 만으로는 로직을 수행할 수가 없습니다..! 😥바로, 주사위와 노트를 어떻게 사용할 것인지 맥락(컨텍스트)이 없기 때문입니다! 💡컨텍스트 만들기!주사위는 무작위 수를 생성하고! 노트는 기록을 합니다! 제가 생성한 도메인은 자신의 역할을 잘 수행합니다!그러나 노트는 숫자라면 무엇이든 잘 기록할 수 있습니다! 그게 꼭 주사위의 숫자가 아니어도 상관이 없습니다.그렇기에, 우리는 맥락(컨텍스트)이 필요한 것입니다. 도메인을 연결하여 의미가 있는 역할을 수행하게 하는 것이죠!그렇게 저는 딜러(Dealer)라는 새로운 객체를 만들었습니다! 요청을 받아 주사위를 굴리는 게 게임 같았거든요..!public class Dealer { private final Dice dice; private final Note note; private Dealer(Dice dice, Note note) { this.dice = dice; this.note = note; } public static Dealer playWith(Dice dice, Note note) { return new Dealer(dice, note); } public void rollDiceMultipleTimes(int numberOfRoll) { for (int i=0; i딜러의 역할은 게임의 진행입니다! 사용자에게 요청 받은 숫자만큼 주사위를 굴려주고! 노트에 결과를 전달하고 기록된 결과를 사용자에게 알려주는 역할을 수행합니다! 😃주사위와 노트는 딜러의 게임 진행이라는 맥락(컨텍스트) 아래에서 자신들의 역할을 수행합니다! 어떤가요? 객체들이 서로 협업 하며 역할을 잘 수행하여 로직을 수행하고 있습니다!또한, 흥미로운 점은 딜러는 주사위와 노트가 자신의 역할만 잘 수행할 수 있다면(인터페이스를 충실히 구현했다면) 얼마든지 다른 주사위나 노트로 바꿀 수 있습니다! 사기를 칠 지도 모르겠군요! 😜 💡정리하며...자, 이제 클린코딩을 수행한 코드를 다시 보겠습니다! 현재의 내부 구현은 모두 알지만, 구현체인 주사위와 노트는 언제든지 바뀔 수 있습니다!그러나, 우리는 주사위와 노트가 자신의 역할만 잘 수행할 수 있다면, 그것이 바뀌어도 상관이 없다는 사실도 알고 있습니다!이것이 객체 지향이 주는 다형성이라는 장점입니다! 글이 너무 길어져서 짧게 설명하는 점 죄송합니다...😭public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Dice luckyDice = UnknownDice.decideFaces(6); Note memoPad = new MemoPad(); Dealer dealer = Dealer.playWith(luckyDice, memoPad); System.out.print("숫자를 입력하세요 : "); dealer.rollDiceMultipleTimes(scanner.nextInt()); dealer.tellTheResult(); } }객체 지향 정말 매력적이지 않은가요? 책을 통해 이론으로만 배운 객체 지향을 직접 설계부터 구현하며 쓴 저의 긴 기록을 지금까지 읽어주셔서 감사드리며, 객체 지향을 모르시던 분들께 조금이라도 도움이 되었으면 합니다!사실.. SOILD부터 대뜸 외우라고 하면, 어렵습니다 객체 지향..남은 스터디 기간도 다들 즐거운 코딩하시길 바라겠습니다! 🙇♂️
백엔드
・
객체지향
・
클린코딩
・
스프링부트
・
인프런
・
워밍업클럽스터디
・
도메인
・
컨텍스트
・
추상화
・
캡슐화
2024. 02. 20.
3
[인프런 워밍업 0기 Day2] 한 걸음 더! 메서드로 코드 재사용해보기!
!! 해당 글은 독자가 인프런 워밍업 0기를 수강하고 있다는 전제 하에 작성되었습니다 !!과제 수행에 있어 스프링부트 3.2.2 버전을 사용하고 있다는 점을 미리 알려드립니다! 안녕하세요🙌! 인프런 워밍업 2일차 과제입니다!2일차에는 API를 작성하는 방법에 대해서 배우고 그에 대한 과제를 받았습니다😎과제를 자세히 살펴보는 도중, 문제 1번과 3번 모두 덧셈을 수행하는 공통로직이 있다는 사실을 발견했습니다!따라서! 오늘은 제가 한 걸음 더 성장하기 위해 메서드를 통해 공통로직을 처리한 과정을 공유할 예정입니다~ ⚙️메서드 설계하기메서드를 만들 때, 제가 중요하다고 생각하는 것은 3가지입니다!메서드가 무엇을 할 것인가 (메서드 명)메서드가 무엇을 반환할 것인가 (리턴 타입)메서드가 무엇을 필요로 하는가 (파라미터)여기서, 중요한 점은 어떻게는 생각하지 않는다는 것입니다! 🙅♂️위의 3가지만 만족한다면 공통로직으로 볼 수 있기 때문에 메소드의 내부 로직은 추후에 생각해도 무방합니다! 자, 그렇다면 지금부터 문제 1번과 3번에서 각 절차를 수행해보겠습니다.첫째, 무엇을 할 것인가?문제 1번과 3번 모두 2개 이상의 숫자를 더해야 하는 상황입니다! 따라서, 저는 메서드 명을 addNumbers로 정했습니다.둘째, 무엇을 반환할 것인가?우리는 두 수를 더 한 값을 반환 받아야 합니다! int와 Inteager 등 다양한 숫자가 가능하겠지만, 저는 Inteager를 택했습니다!셋째, 무엇을 필요로 하는가?우리는 숫자를 더하기 위해, 2개 이상의 숫자를 필요로 합니다! 또한, 문제 3번의 경우 몇 개의 숫자가 입력될 지 알 수가 없습니다! 따라서 저는 List numbers로 파라미터 값을 설정했습니다.이제 위의 세 부분을 합치면 제가 만들 메서드는 아래와 같은 형식이 될 것입니다!public Integer addNumbers(List numbers);💡 int 타입의 경우 List로 받을 수가 없어 Integer를 반환 값으로 설정하였습니다! ⚙메서드 구현하기이제 설계가 끝났으니, 메서드를 구현할 시간입니다! for each문으로 List에 담긴 값을 하나씩 꺼내 더하여 값을 반환만 해주면 구현은 어렵지 않게 끝이 납니다!😎 public Integer addNumbers(List numbers) { Integer result = 0; for (Integer number : numbers) { result += number; } return result; }자~ 이제 구현이 끝났으니 메서드를 적용해봐야겠죠? ⚙메서드 적용하기문제 3번먼저, 문제 3번입니다! 3번부터 풀이하는 이유는 1번 문제를 풀며 발생하는 문제 때문입니다!DTO (데이터를 전송 받고 보내는 객체)@Getter @Setter public class NumbersRequest { private List numbers; }Controller @PostMapping("/api/v1/calc") public Integer addNumbers(@RequestBody NumbersRequest numbersRequest) { return addNumbers(numbersRequest.getNumbers()); }List를 필드 값으로 갖는 NumberRequest를 입력받아 addNumbers()에 필드값을 전달했습니다!입력받은 숫자가 몇 개던지 메서드 내부에서 값이 잘 합산되어 반환될 것입니다! 문제 1번이번엔, 문제 1번을 해결해보죠! 😎DTO@Getter @Setter public class CalcResponse { private int add; private int minus; private int multiply; }Controller @GetMapping("/api/v1/calc") public CalcResponse calc(Integer num1, Integer num2) { List numbers = new ArrayList(); numbers.add(num1); numbers.add(num2); CalcResponse response = new CalcResponse(); response.setAdd(addNumbers(numbers)); // minus와 multiply도 메서드로 구현해주었습니다! response.setMinus(minusNumbers(numbers)); response.setMultiply(multiplyNumbers(numbers)); return response; }값을 Integer num1과 Integer num2를 입력받기 때문에 입력 값들을 List로 변환해줘야 합니다.이렇게 하여도 문제는 없겠지만, 코드가 지저분한 게 영 마음에 들지 않습니다!그래서, 다음과 같이 코드를 변경해보았습니다!DTO (Request) 추가@Getter @Setter public class CalcRequest { private Integer num1; private Integer num2; public List getNumbers() { List numbers = new ArrayList(); numbers.add(this.num1); numbers.add(this.num2); return numbers; } }입력 값을 각각의 값이 아닌 하나의 객체로 변환하여 받고, 내부에 getNumbers()를 구현하여 입력받은 값을 바로 List로 변환하여 받도록 하였습니다!이 DTO를 적용하면 Controller의 코드가 다음과 같이 바뀌게 됩니다! @GetMapping("/api/v1/calc") public CalcResponse calc(@ModelAttribute CalcRequest calcRequest) { CalcResponse response = new CalcResponse(); response.setAdd(addNumbers(calcRequest.getNumbers())); response.setMinus(minusNumbers(calcRequest.getNumbers())); response.setMultiply(multiplyNumbers(calcRequest.getNumbers())); return response; }훨씬 간결해졌죠? 참고로 @ModelAttribute는 쿼리 스트링으로 입력받은 값들을 확인하여 파라미터 타입으로 지정한 타입의 필드 값과 일치하면 자동으로 객체로 변환해주는 Annotation입니다!이렇게, 메서드를 사용하면 공통로직을 단일 메서드 내부에서 관리할 수 있게 되고, 코드의 반복을 줄일 수 있게 됩니다! 여러분도 코드의 중복이 보인다면 메서드로 한 번 만들어보는 것은 어떨까요? 지금 까지의 내용을 정리하면서 2일차 포스팅을 마치도록 하겠습니다! 💡정리 💡메서드를 설계할 때 중요한 것!메서드를 설계할 때는 어떻게는 생각하지 말자!무엇을 하고, 반환하고, 필요한 지 3가지만 충족된다면 공통로직으로 만들 수 있다!객체 내부에서 변환로직 만들기!여러 개의 Integer를 Controller에서 List로 직접 변환할 시 코드가 지저분해진다! @ModelAttribute를 활용하여 객체 내부에서 값을 변환하여 반환하자!
백엔드
・
Spring
・
인프런워밍업
・
메서드
・
API
・
인프런
・
SpringBoot