Theory36 의식적으로 지금길 사용하기 지름길이란 고민없이 있는 그대로 코딩해 OOP와 스프링의 장점을 다 날려버리는 것을 의미한다. 이 책을 보는 사람들은 이 지름길이 옳은 것인지 또는 기술부채를 쌓아가는 것인지 확인해봐야 한다. 왜 지름길은 깨진 창문 같을까? 깨진창문이론이란 "어떤 것이 멈춘 것처럼 보이고, 망가져 보이고, [부정적인 형용자를 넣어보자], 혹은 관리되지 않는다고 여겨지면 인간의 뇌는 이를 더 멈추고, 망가뜨리고, [부정적인 형용사를 넣어보자]해도 된다고 생각하게 된다."는 것을 뜻한다. 기물 파손이 흔한 동네에서는 방치된 차를 도둑질하거나 망가뜨리는 일이 더 쉽게 일어난다. '좋은' 동네라도 차의 창문이 깨져있다면 차를 망가뜨리는 일이 쉽게 일어난다. 침실이 정돈돼 있지 않으면 옷을 옷장에 넣는 대신 바닥에 아무렇게나 던.. 2022. 7. 12. 아키텍처 경계 강화하기 경계와 의존성 경계를 강화한다는 것은 어떤 의미일까? 의존성을 항상 안쪽으로 흐르게 하는 것이다. 빨간선으로 되어 있는 잘못된 방향을 없애 경계를 강화해야 한다. 접근 제한자 package-private 제한자는 상당히 중요하다. 다른 패키지에서 접근하지 못하게 하며 모듈의 진입점만 public으로 하여 의존성 규칙을 위반할 위험을 줄어들게 만든다. buckpal |--- account |--- adapter | |--- in | | |--- web | | |--- o AccountController | |--- out | | |--- persistence | | | |--- o AccountPersistenceAdapter | | | |--- o SpringDataAccountRepository |--.. 2022. 7. 11. 애플리케이션 조립하기 왜 조립까지 신경 써야 할까? 코드의 의존성이 올바른 방향(도메인 방향)을 가르키기 위해서 유스케이스와 어댑터를 필요할 때 인스턴스화 하면 안된다. 유스케이스가 영속성 어댑터를 호출해야 하고 스스로 인스턴스화한다면 코드 의존성이 잘못된 방향으로 만들어진 것이다. 이것이 바로 아웃고잉 포트 인터페이스를 생성한 이유다. 유스케이스는 인터페이스만 알아야하고, 런타임에 이 인터페이스의 구현을 제공 받아야 한다. 그렇다면 객체 인스턴스를 생성할 책임은 누구에게 있을까? 그리고 어떻게 의존성 규칙을 어기지 않으면서 그렇게 할 수 있을까? 위 그림처럼 아키텍처에 대해 중립적이고 인스턴스 생성을 위해 모든 클래스에 대한 의존성을 가지는 설정 컴포넌트가 있어야 한다. 설정 컴포넌트의 역할 · 웹 어댑터 인스턴스 생성 ·.. 2022. 7. 10. 경계 간 매핑하기 매핑에 따른 의견 매핑에 찬성하는 개발자 두 계층 간에 매핑을 하지 않으면 양 계층에서 같은 모델을 사용해야 하는데 이렇게 하면 두 계층이 강하게 결합된다. 매핑에 반대하는 개발자 두 계층 간에 매핑을 하게 되면 보일러 플레이트 코드를 너무 많이 만들게 된다. 많은 유스케이스들이 오직 CRUD만 수행하고 계층에 걸쳐 같은 모델을 사용하기 때문에 계층 사이의 매핑은 너무 과하다. 매핑하지 않기 매핑하지 않는 전략을 사용하게 된다면 그림은 위 처럼 된다. 송금하기 위한 모든 클래스들이 Account 클래스를 참조하고 있다. 도메인과 어플리케이션 계층은 웹이나 영속성과 관련된 특수한 요구사항에 관심이 없음에도 불구하고 Account 도메인 모델 클래스는 모든 요구사항을 다뤄야 한다. Account클래스는 웹,.. 2022. 7. 9. 아키텍처 요소 테스트하기 테스트 피라미드 테스트의 기본 전제는 만드는 비용이 적고, 유지보수하기 쉽고, 빨리 실행되고, 안정적인 작은 크기의 테스트들에 대해 높은 커버리지를 유지해야 한다는 것이다. 이 테스트는 하나의 '단위'가 제대로 동작하는지 확인 할 수 있는 단위 테스트들이다. 테스트 피라미드가 위로 올라갈수록 비용이 더 비싸지고 실행이 느리며 깨기지 쉽다. 또, 새로운 기능을 만드는 것보다 테스트를 만드는 시간을 더 쓰게 된다. 테스트 피라미드는 테스트의 커버리지 목표를 낮게 잡아야 한다는 것을 의미한다. 맥락에 따라 테스트 피라미드에 포함되는 계층은 달라질 수 있다. '단위 테스트', '통합테스트', '시스템 테스트'의 정의는 맥락에 따라 다를 수 있다. 단위 테스트는 하나의 클래스를 인스턴스화하고 클래스의 인터페이스를.. 2022. 7. 5. 영속성 어댑터 구현하기 의존성 역전 영속성 어댑터는 아웃고잉 어댑터이다. 어플리케이션에 의해 호출될 뿐, 어플리케이션을 호출하지는 않는다. 영속성 어댑터의 책임 1. 입력을 받는다. 2. 입력을 데이터베이스 포맷으로 매핑한다. 3. 입력을 데이터베이스로 보낸다. 4. 데이터베이스 출력을 어플리케이션 포맷으로 매핑한다. 5. 출력을 반환한다. 영속성 어댑터의 핵심은 영속성 어댑터 내부에 있는 것이 아니라 커플리케이션 코어에 있기 때문에 영속성 어댑터 내부를 변경하는 것이 코어에 영향을 미치지 않는다. 영속성 어댑터는 데이터베이스에 쿼리를 날리고 쿼리 결과를 받아온다. 데이터베이스 응답을 포트에 정의된 출력 모델로 매핑해서 반환한다. 포트 인터페이스 나누기 데이터베이스 연산에 의존하는 각 서비스는 단 하나의 메서드만 사용하는 것이.. 2022. 7. 2. 웹 어댑터 구현하기 의존성 역전 웹 컨트롤러에서 외부로부터 청을 받아 어플리케이션 코어를 호출하고 무슨 일을 해야 할지 알려준다. 이 중간에 웹 어댑터와 어플리케이션 계층이 통신 할 수 있는 포트를 제공한다. 이 포트를 통해 외부와 어떤 통신이 일어나고 있는지 알 수 있다. 위 그림은 컨트롤러에서 service로 아웃고잉 어댑터를 만들 수 있다. 하지만 한 어댑터에서 둘다 수행해도 상관없다. 웹 어댑터의 책임 웹 어댑터는 URL경로, HTTP 메서드, 콘텐츠 타입과 같은 특정 기준을 만족하는 HTTP요청을 수신 후 역직렬화 한다. 1. HTTP 요청을 자바 객체로 매핑 2. 권한 검사 3. 입력 유효성 검증 4. 입력을 유스케이스의 입력 모델로 매핑 5. 유스케이스 호출 6. 유스케이스의 출력을 HTTP로 매핑 7. HTT.. 2022. 6. 29. 유스케이스 작성하기 육각형 아키텍처는 도메인 주임의 아키텍에 적합하기 때문에 도메인 엔티티를 만드는 것으로 시작한 후 해당 도메인 엔티티 중심으로 유스케이스를 구현하겠다. 도메인 모델 구현하기 송금하는 유스케이스를 구현해보자. OOP의 관점으로 Account라는 객체를 만들어 현재 잔고를 스냅샷을 하도록 한다. 계좌에 대한 모든 입금과 출금은 Activity 엔티티에 포착하고 모든 Activity에 대해 메모리를 올리는 것은 낭비이기 때문에 ActivityWindow를 통해 일정 기간만 보유한다. @AllArgsConstructor(access = AccessLevel.PRIVATE) public class Account { /** * The unique ID of the account. */ @Getter private .. 2022. 6. 21. 코드 작성하기 계층으로 구성하기 코드를 구조화하는 첫 번째 접근법으로 계층을 이용하는 것으로서, 다음과 같이 코드를 구성할 수 있다. buckpal |--- domain | |--- Account | |--- Activity | |--- AccountRepository | |--- AccountService |--- persistence | |--- AccountRepositoryImpl |--- web |--- AccountController 웹 계층, 도메인 계층, 영속성 계층 각각에 대해 전용 패키지인 web, domain, persistence를 뒀다. 의존성 역전 원칙을 적용해서 의존성이 domain 패키지에 있는 도메인 코드만을 향하도록 했다. domain에 AccountRepository 인터페이스를 추가.. 2022. 6. 20. 의존성 역전하기 단위 책임 원칙 단위 책임 원칙은 "하나의 컴포넌트는 오로지 한 가지 일만 해야하고, 그것을 올바르게 수행해야 한다."고 알고 있지만 실제 의도는 "컴포넌트를 변경하는 이유는 오직 하나뿐이어야한다."이다. 변경할 이유가 한가지라면 수행해야 하는 일도 한가지이다. 만약 컴포넌트를 변경할 이유가 한 가지라면 우리가 어떤 다른 이유로 소프트웨어를 변경하더라도 이 컴포넌트에 대해서는 전혀 신경 쓸 필요가 없다. E의 경우에는 의존하고 있는 컴포넌트가 없기 때문에 자기 자신이 변경할 이유가 있을 경우에만 수정하면 된다. 반면 A는 너무 많은 컴포넌트를 의존하고 있기 때문에 다른 어떤 컴포넌트가 바뀌든지 같이 바뀌어야 한다. 의존성 역전 원칙 의존성 역전 원칙이란 "코드상의 어떤 의존성이든 그 방향을 바꿀 수(역전.. 2022. 6. 19. 계층형 아키텍쳐의 문제는 문제일까? 계층형 아키텍쳐란? 계층으로 구성된 어플리케이션은 일반적인 3계층 아키텍처를 포함한다. 웹 계층에서는 요청을 받아 도메인 혹은 비즈니스 계층에 있는 서비스로 요청을 보낸다. 서비스에서는 필요한 비즈니스 로직을 수행하고, 도메인 엔티티의 현재 상태를 조회하거나 변경하기 위해 영속성 계층의 컴포넌트를 호출한다. 계층형 아키텍처의 문제는 무엇일까? 1. 계층형 아키텍처는 데이터베이스 주도 설계를 유도한다. 전통적인 계층형 아키텍처의 토대는 데이터베이스이다. 웹 계층은 도메인 계층에 의존하고, 도메인 계층은 영속성 계층에 의존하기 때문에 자연스레 데이터베이스에 의존하게 된다. 하지만 대부분의 어플리케이션의 목적은 규칙이나 정책을 반영한 모델을 만들어서 사용자가 이러한 규칙과 정책을 더욱 편리하게 활용 할 수 있.. 2022. 6. 16. MVC Pattern MVC란 MVC는 Model, View, Contorller의 약자이다. 하나의 애플리케이션, 프로젝트를 구성할 때 그 구성 요소를 세 가지의 역할로 구분한 패턴이다. 위 그림에서 USER가 이벤트를 발생 시키면 CONTROLLER가 MODEL을 통해 데이터를 가지고 온다. 그 결과물을 VIEW에서 받아 USER에서 전달하게 되는 형식이다. 모델(Model) DATA, 정보, 초기 값, 변수, 상수 또는 데이터를 가공하는 컴포넌트 등을 뜻한다. 모델의 규칙 1. 사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 한다. 2. 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야 한다. 3. 변경이 일어나면, 변경 통지에 대한 처리 방법을 구현해야만 한다. - 변경 통지를 CONTROLLER에게 해야 한다. .. 2022. 5. 23. Visitor Pattern Visitor Pattern object구조의 요소들을 수행 할 operation을 나타낸다. Visitor는 요소 class의 변화없이 새로운 operation을 정의 할 수 있다. 위 그림은 Composite pattern에서 사용하였던 구조이다. 위와 같은 구조가 있을 때 각 요소에 대한 operation은 직접정의 할 수 있겠지만 공통된 operation은 visitor pattern내에 정의하여 사용 할 수 있다. 코드의 재사용성이 올라가며 추가적인 operation이 필요할 때 모든 요소들에 추가하는 것이 아니라 visitor에 추가하면 되기 때문에 유지보수에도 용이하다. solution object구조의 요소들을 대신 수행 할 Visitor object를 정의한다. 이 object는 요소들을.. 2022. 5. 22. Template Method Pattern Template Method Pattern 알고리즘에서 특정 step만을 수정할 때 사용한다. subclass의 operation을 바꾸고 싶을 때 parent class에서 상속받아 재정의 한다. 하지만 이것은 전체를 다시 다 정의해야 하며 특정 부분만을 수정하는 것이 불가능하다. template method는 불변하는 코드와 변하는 코드를 나눠 불변하는 코드의 중복을 없애는 방법이다. Solution 변하는 코드를 함수(primitive)로 바꾸어 subclass에서 상속하여 변경시킨다. 반면에 불변 코드는 상위 클래스에 그대로 둔다. Motor public abstract class Motor { protected Door door; private MotorStatus motorStatus; pub.. 2022. 5. 22. Strategy Pattern Strategy Pattern 알고리즘의 family를 정의하고 캡슐화한다. 또한 서로 상호교환도 가능하다. Strategy Pattern은 알고리즘을 독립적으로 다양하게 사용 할 수 있게 한다. 어떻게 알고리즘을 직접 구현하는 대신에 run-time에 환경설정을 할 수 있을까? 어떻게 알고리즘을 run-time에 교환하거나 선택할 수 있을까? Context내에 알고리즘을 switch나 if-else문으로 작성한다면 나중에 독립적으로 수정하기가 어렵다. 또한, 클래스가 복잡해지고 재사용성이 어려워진다. 알고리즘을 개발기간동안 확장되어지고 최적화되며 대체되기도 하기 때문에 결합을 약하게 해야만 한다. Solution 이 패턴의 목적은 상속을 통해 독립적으로 알고리즘을 구현하는데 있다. Strategy ob.. 2022. 5. 22. State Pattern State Pattern internal state가 변경되었을 때 행위를 교체하는 것을 허용한다. internal state가 변경되었을 때 행동을 어떻게 교체할까? 어떻게 state기반 행동을 명시하고나서 새로운 state가 추가하거나 이미 존재하는 state의 행동을 독립적으로 변경할까? 주문 시스템에서 주문 Object가 있다고 하자. 이 주문을 취소하는 상태가 있을수도 있고, 취소했다가 재구매하는 상태 등 여러가지 상태가 존재 할 수 있다. State Pattern은 주문 or 상품에 대해서 독립적으로 state를 만들어 새로운 state가 필요하거나 operation을 독립적으로 교환 할 수 있도록 해준다. Solution Context와 State를 분리하고, State를 상속받아 State.. 2022. 5. 21. Observer Pattern Observer Pattern object사이에 one-to-many 의존성을 정의하여 one object가 변경될 때 이것을 의존하고 있는 모든 object들을 자동적으로 업데이트 및 통지하는 것이다. 어떻게 one-to-many object들 사이에 의존성을 강한 결합없이 정의 할 수 있을까? 어떻게 one object가 many object에게 통지 할 수 있을까? 위 그림과 같이 수정이나 이벤트가 있을 경우 모두 등록된 object에게 자동적으로 수정 분을 공지하고 업데이트 하는 것이다. Solution Subject는 Observer의 container를 가지고 있으며, Observer를 추가하고 업데이트 하는 함수를 갖는다. public abstract class Subject { privat.. 2022. 5. 21. Memento Pattern Memento Pattern 캡슐화를 위배하지 않고, object의 내부 state를 외부화하고 캡쳐해서 나중에 재사용하기 위해 사용한다. checkpoint나 undo를 구현하는데 용이하다. object의 데이터 구조나 구현은 object내에 숨겨져 있고 보이지 않고 object 외부에서 접근할수도 없다. 문제점은 internal state를 object의 외부로스터 접근 없이 외부로 저장시키는 것이다. memento pattern은 internal state의 스냅샷을 저장해서 나중에 state를 재사용 할수 있도록 한다. 동시에 캡슐화는 위배하지 않는다. * internal state : 모든 내부 데이터와 변수들 Solution Originator는 Memento의 현재 상태를 가지고 있는 객체이.. 2022. 5. 21. Mediator Pattern Mediator Pattern 서로를 참조하고 있는 object들의 결합을 끊고 상호작용 할 수 있게 만드는 방법이다. 서로를 참조하고 있는 object들의 결합을 끊을 수 있을까? 어떻게 독립적으로 이러한 object들을 사호작용 할 수 있도록 할까? 이렇게 서로를 참조하여 강한 결합이 있는 obejct들은 독립적으로 수정, 구현, 재사용이 어렵다. solution Colleague1이 변할 때마다 자기 자신(this)를 Mediator1에 넘겨준다. public class Colleague1 implements Colleague { private Mediator mediate; private String state; public Colleague1(Mediator mediate) { this.medi.. 2022. 5. 15. Iterator Pattern Iterator Pattern aggregate object의 내부를 알지 못해도 요소들을 접근 하는 방법이다. 어떻게 object의 내부를 표출시키지 않고 요소들을 접근할 수 있을까? aggregate object란 array, set같은 object를 이야기 한다. 이러한 object의 요소들을 접근하기 위해 내부를 노출시킨다면 캡슐화에 위배된다. solution Aggregate와 Iterator를 분리하여 Aggregate내에 Iterator를 내부 클래스로 구현한다. 왜 분리해서 구현해야 할까? aggregate object와 독립적으로 새로운 operation을 추가하기가 어렵다. 또, 요소를 탐색하는데 여러가지 iterator를 둘 수 없다. public class Aggregate1 imp.. 2022. 5. 15. Interpreter Pattern Interpreter Pattern 자연어에서 문법에 따라 문장을 해석하는 역할을 한다. 문법은 언어의 구조를 표현하는 방법을 의미한다. Interpreter는 언어의 문장을 통역하는 규칙의 리스트를 가지고 있다. 언어의 모든 문장은 abstract syntax tree(AST)를 통해 class들을 instance하고 있다. Solution 1. Expression class hierarchy구조로 문법을 정의한다. - expression을 해석할 수 있는 interface를 정의한다. public abstract class AbstractExpression { private String name; public AbstractExpression(String name) { this.name = name;.. 2022. 5. 14. Command Pattern Command Pattern request를 object로 캡슐화 한 다음 캡슐화된 object를 다른 요청이나 큐 등 다른 operation의 파라미터로 사용하게 하는 것이다. request의 요청자와 요청간의 결합을 어떻게 회피 할 수 있을까? 어떻게 특정 object를 request의 환경이 될 수 있도록 할까? compile-time에 request의 요청자와 특정 request를 결합시켜버린다면 run-time에는 request를 명시하는 것이 어렵다. 특정 operation을 명시하고 싶을 때 하드코딩을 회피해야 한다. 그렇게 한다면 compile-time과 run-time에 요청 처리에 대한 변화를 주기 쉬워진다. Solution Receiver1은 Reuqest, Command는 Recie.. 2022. 5. 12. Chain Of Responsibility Pattern Chain Of Responsibility Pattern request가 있을 때 request를 다룰 수 있는 기회를 특정 object에게 준다. 이러한 object가 연결되어 있는 chain에 reuqest를 넣고, object들이 이 request를 다룰 때마다 chain을 통과하는 방식을 사용한다. 어떻게 request의 송신자와 수신자가 서로 의존하게 되는 것을 막을 수 있을까? 어떻게 request를 여러 object를 통하여 처리 할 수 있을까? 위 그림처럼 Receiver object들을 여러개 묶어 chain으로 만들어 놓고 요청에 대해서 각 처리하도록 한다. Solution Handler를 다룰 수 있도록 상속받은 Receiver class를 만든다. public class Receiv.. 2022. 5. 10. Flyweight Pattern Flyweight Pattern Flyweight Pattern은 미세하게 나누어진 매우 많은 object들을 효율적으로 공유하는 것이다. 미세하게 많은 양으로 나누어진 object를 어떻게 효율적으로 지원할까? 어떻게 많은 양의 object를 생성하는 것을 회피하면서 공유 할 수 있을까? 어떻게 object들을 물리적으로 적은 양으로 생성하면서 논리적으로 문제가 없게 object들을 표현할 수 있을까? 많은 object들을 다루기 위한 기본적인 방법은 필요한 만큼 object들을 생성하는 것이다. 이러한 방법은 많은 메모리를 차지하고 overhead를 일으킬 수 있다. 이것들을 해결하기 위해서는 물리적으로 생성되는 object의 수를 줄여야 하면서 논리적으로도 모든 객체가 존재해야만 한다. soluti.. 2022. 5. 9. Facade Pattern Facade Pattern subsystem에 있는 여러 interface를 통합으로 묶어 줄 수 있는 interface를 정의하는 것이다. Facade는 subsystem을 쉽게 할 수 있도록 도와주는 higher-level interface를 정의하는 것이다. 복잡한 subsystem에 단순한 interface를 어떻게 제공 할 수 있을까? subsystem에 client와 object사이에 응집도를 어떻게 줄일 수 있을까? subsystem의 Client는 오직 Facade interface에 대해서만 알고 참조해야만 한다. 또, subsystem에 있는 서로 다른 interface에 대해서 독립적이게 하여 의존을 줄이고 client가 쉽게 구현, 수정, 테스트 그리고 재사용 하기 쉽게 한다. 왼쪽.. 2022. 5. 9. Decorator Pattern Decorator Pattern 추가적인 기능을 object에 유동적으로 부여하는 것이다. subclassing이나 상속의 유동적 대안책으로 사용된다. 어떻게 object에 추가적인 책임을 유동적으로 부여 할 수 있을까? 어떻게 object의 기능을 run-time에 확장시킬 수 있을까? 이 Pattern은 상속처럼 compile-time에 클래스의 기능을 확장하는 것을 회피하기 위해서 사용된다. 예를 들어 web application에서는 버튼이나 스크롤의 이벤트에 따라 기능이 추가될 수도 있다. 이럴 경우 run-time에 기능을 추가하는 것이 필요한데 Decorator Pattern으로 해결이 가능하다. Solution Component에 기본적인 함수를 정의를 해놓고 Component를 상속받는다.. 2022. 5. 8. Composite Pattern Composite Pattern Composite는 Object를 tree구조로 표현하기 위해 사용한다. Composite는 Client가 object와 object의 구성품을 균일하게 다루게 한다. 어떻게 object를 tree구조로 구성할까? Composite object를 가지고 어떻게 subtree를 구성할까? Tree구조는 OOP syetem에서 매우 자주 사용하기 때문에 composite는 패턴은 유용하다. Tree구조는 Leaf Object와 Composite object로 구성되어 있다. Leaf Object는 Leaf Node와 같은 역할을 한다고 보면되고 Composite object는 중간 Node이라고 보면 된다. 그래서 Composite object는 children을 가질 수 있.. 2022. 5. 5. Proxy Pattern Proxy Pattern Proxy Pattern은 다른 object를 통제 할 수 있는 대리자를 만드는 것이다. 어떻게 object에 접근하는 것을 통제 할까? object에 접근할 때, 추가적인 기능을 어떻게 제공할까? Solution Proxy는 RealSubject를 생성자로 받아 operation하게 한다. public class Proxy extends Subject{ private RealSubject realSubject; public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public String operation() { return "Hello World from proxy and " + .. 2022. 4. 24. Bridge Pattern Bridge Pattern abstraction과 implementation을 분리하여 이 2가지를 독립적으로 다양하게 할 수 있다. abtraction과 implementation을 어떻게 독립적으로 다양하게 할 것인가? 어떻게 implementation을 run-time에 선택하거나 교환할 수 있을까? 서버를 사용할 때, 여러개의 다른 하드웨어를 가진 환경이 존재 할 수 있다. application을 서로 다른 하드웨어 환경에 적절히 동작하기 위해서 필요 할 수 있다. Solution Abstraction을 통해 Implementor의 구현부를 사용 한다. 따라서, run-time에 implementor를 상속받은 클래스만 바꿔주면 되기 때문에 Abstraction과 implementation이 독립.. 2022. 4. 24. Prototype Pattern Prototype Pattern Prototypical instance를 사용하여 create할 object의 종류를 명시해주고, prototype 복사하여 새로운 object를 생성한다. run-time에 생성할 어떤 object를 명시하여 생성할 것인가? 클래스를 어떻게 동적으로 instantiation할 것인가? prototype으로써 역할을 하기 위해서 object는 스스로를 copy할 수 있는 Prototype interface를 구현해야한다. Solution Prototype interface clone메서드를 정의하고 Product1에서 사용한다. Product1에서 clone을 통해 새로운 객체를 반환하도록 한다. public class Product1 implements Prototype.. 2022. 4. 23. 이전 1 2 다음