•
전략패턴
•
옵저버 패턴
•
데코레이터 패턴
•
팩토리 패턴
•
싱글톤 패턴
•
커맨드 패턴
•
어댑터 패턴
•
파사드 패턴
•
템플릿 메소드 패턴
•
반복자 패턴
•
컴포지트 패턴
•
상태 패턴
•
프록시 패턴
•
복합 패턴 - MVC
SOLID
단일책임
public class UserController {
//Store used by controller
private Store store = new Store();
//Create a new user
public String createUser(String userJson) throws IOException {
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(userJson, User.class);
if(!isValidUser(user)) {
return "ERROR";
}
store.store(user);
return "SUCCESS";
}
//Validates the user object
private boolean isValidUser(User user) {
if(!isPresent(user.getName())) {
return false;
}
user.setName(user.getName().trim());
if(!isValidAlphaNumeric(user.getName())) {
return false;
}
if(user.getEmail() == null || user.getEmail().trim().length() == 0) {
return false;
}
user.setEmail(user.getEmail().trim());
if(!isValidEmail(user.getEmail())) {
return false;
}
return true;
}
//Simply checks if value is null or empty..
private boolean isPresent(String value) {
return value != null && value.trim().length() > 0;
}
//check string for special characters
private boolean isValidAlphaNumeric(String value) {
Pattern pattern = Pattern.compile("[^A-Za-z0-9]");
Matcher matcher = pattern.matcher(value);
return !matcher.find();
}
//check string for valid email address - this is not for prod.
//Just for demo. This fails for lots of valid emails.
private boolean isValidEmail(String value) {
Pattern pattern = Pattern.compile("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$");
Matcher matcher = pattern.matcher(value);
return matcher.find();
}
}
Java
복사
컨트롤러 클래스는 기본적으로 요청을 수신하고 반환한다.
컨트롤러 자체에는 비즈니스 로직이 없어야 하지만 현재에는 유저를 생성하고, 검증하고, 저장하는 많은 비즈니스 로직을 포함하고 있습니다.
public class UserController {
//Store used by controller
private UserPersistenceService persistenceService = new UserPersistenceService();
//Create a new user
public String createUser(String userJson) throws IOException {
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(userJson, User.class);
UserValidator validator = new UserValidator();
boolean valid = validator.validateUser(user);
if(!valid) {
return "ERROR";
}
persistenceService.saveUser(user);
return "SUCCESS";
}
}
Java
복사
public class UserValidator {
public boolean validateUser(User user) {
return isValidUser(user);
}
private boolean isValidUser(User user) {
if(!isPresent(user.getName())) {
return false;
}
user.setName(user.getName().trim());
if(!isValidAlphaNumeric(user.getName())) {
return false;
}
if(user.getEmail() == null || user.getEmail().trim().length() == 0) {
return false;
}
user.setEmail(user.getEmail().trim());
if(!isValidEmail(user.getEmail())) {
return false;
}
return true;
}
//Simply checks if value is null or empty..
private boolean isPresent(String value) {
return value != null && value.trim().length() > 0;
}
//check string for special characters
private boolean isValidAlphaNumeric(String value) {
Pattern pattern = Pattern.compile("[^A-Za-z0-9]");
Matcher matcher = pattern.matcher(value);
return !matcher.find();
}
//check string for valid email address - this is not for prod.
//Just for demo. This fails for lots of valid emails.
private boolean isValidEmail(String value) {
Pattern pattern = Pattern.compile("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$");
Matcher matcher = pattern.matcher(value);
return matcher.find();
}
}
Java
복사
public class UserPersistenceService {
private Store store = new Store();
public void saveUser(User user) {
store.store(user);
}
}
Java
복사
OCP(개방 폐쇄)
확장을 위해 열려 있어야 하지만 수정에는 닫혀야 있어야 한다.
•
확장: 행위가 늘어난다.
•
수정에 닫혀있다: 기존 코드를 바꿔서는 안된다.
@Getter
@Setter
public class PhoneSubscriber {
private Long subscriberId;
private String address;
private Long phoneNumber;
private int baseRate;
public double calculateBill() {
List<CallHistory.Call> sessions = CallHistory.getCurrentCalls(subscriberId);
long totalDuration = sessions.stream().mapToLong(CallHistory.Call::getDuration).sum();
// 계산 결과
return (double) (totalDuration * baseRate) /100;
}
}
Java
복사
public class ISPSubscriber {
private Long subscriberId;
private String address;
private Long phoneNumber;
private int baseRate;
private long freeUsage; // 추가 속성
public ISPSubscriber() {
}
public double calculateBill() {
List<InternetSessionHistory.InternetSession> sessions = InternetSessionHistory.getCurrentSessions(subscriberId);
long totalData = sessions.stream().mapToLong(InternetSessionHistory.InternetSession::getDataUsed).sum();
long chargeableData = totalData - freeUsage; // 추가 연산
return chargeableData*baseRate/100;
}
}
Java
복사
public class CallHistory {
@Getter
public static class Call {
private LocalDateTime begin;
private long duration;
private Long subscriberId;
public Call(Long subscriberId, LocalDateTime begin, long duration) {
this.begin = begin;
this.duration = duration;
this.subscriberId = subscriberId;
}
}
private static final Map<Long, List<Call>> CALLS = new HashMap<>();
public synchronized static List<Call> getCurrentCalls(Long subscriberId) {
if(!CALLS.containsKey(subscriberId)) {
return Collections.emptyList();
}
return CALLS.get(subscriberId);
}
public synchronized static void addSession(Long subscriberId, LocalDateTime begin, long duration) {
List<Call> calls;
if(!CALLS.containsKey(subscriberId)) {
calls = new LinkedList<>();
CALLS.put(subscriberId, calls);
} else {
calls = CALLS.get(subscriberId);
}
calls.add(new Call(subscriberId, begin, duration));
}
}
Java
복사
public class CallHistory {
@Getter
public static class Call {
private LocalDateTime begin;
private long duration;
private Long subscriberId;
public Call(Long subscriberId, LocalDateTime begin, long duration) {
this.begin = begin;
this.duration = duration;
this.subscriberId = subscriberId;
}
}
private static final Map<Long, List<Call>> CALLS = new HashMap<>();
public synchronized static List<Call> getCurrentCalls(Long subscriberId) {
if(!CALLS.containsKey(subscriberId)) {
return Collections.emptyList();
}
return CALLS.get(subscriberId);
}
public synchronized static void addSession(Long subscriberId, LocalDateTime begin, long duration) {
List<Call> calls;
if(!CALLS.containsKey(subscriberId)) {
calls = new LinkedList<>();
CALLS.put(subscriberId, calls);
} else {
calls = CALLS.get(subscriberId);
}
calls.add(new Call(subscriberId, begin, duration));
}
}
Java
복사
public class InternetSessionHistory {
@Getter
public static class InternetSession {
private LocalDateTime begin;
private Long subscriberId;
private Long dataUsed;
public InternetSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
this.begin = begin;
this.dataUsed = dataUsed;
this.subscriberId = subscriberId;
}
}
private static final Map<Long, List<InternetSession>> SESSIONS = new HashMap<>();
public synchronized static List<InternetSession> getCurrentSessions(Long subscriberId) {
if(!SESSIONS.containsKey(subscriberId)) {
return Collections.emptyList();
}
return SESSIONS.get(subscriberId);
}
public synchronized static void addSession(Long subscriberId, LocalDateTime begin, long dataUsed) {
List<InternetSession> sessions;
if(!SESSIONS.containsKey(subscriberId)) {
sessions = new LinkedList<>();
SESSIONS.put(subscriberId, sessions);
} else {
sessions = SESSIONS.get(subscriberId);
}
sessions.add(new InternetSession(subscriberId, begin, dataUsed));
}
}
Java
복사
@Getter
@Setter
public abstract class Subscriber {
protected Long subscriberId;
protected String address;
protected Long phoneNumber;
protected int baseRate;
public abstract double calculateBill(); // 확장 가능
}
Java
복사
@Getter
@Setter
public class PhoneSubscriber extends Subscriber{
// 언제든 수정이 가능하다.
@Override
public double calculateBill() {
List<CallHistory.Call> sessions = CallHistory.getCurrentCalls(subscriberId);
long totalDuration = sessions.stream().mapToLong(CallHistory.Call::getDuration).sum();
// 계산 결과
return (double) (totalDuration * baseRate) /100;
}
}
Java
복사
public class ISPSubscriber extends Subscriber {
private long freeUsage; // 추가 속성
@Override
public double calculateBill() {
List<InternetSessionHistory.InternetSession> sessions = InternetSessionHistory.getCurrentSessions(subscriberId);
long totalData = sessions.stream().mapToLong(InternetSessionHistory.InternetSession::getDataUsed).sum();
long chargeableData = totalData - freeUsage; // 추가 연산
return (double) (chargeableData * baseRate) /100;
}
}
Java
복사
리스코프
최상위 클래스 객체가 예상되는 곳에서 서브 클래스와 클래스 객체를 사용할 수 있어야 한다.
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(10, 20);
System.out.println(rectangle.computeArea());
Square square = new Square(10);
System.out.println(square.computeArea());
useRectangle(rectangle);
useRectangle(square);
}
private static void useRectangle(Rectangle rectangle) {
rectangle.setHeight(20);
rectangle.setWidth(30);
assert rectangle.getHeight() == 20 : "Height Not equal to 20";
assert rectangle.getWidth() == 30 : "Width Not equal to 30";
}
}
Java
복사
예외발생 ⇒ Square객체는 set을 할경우 높이/너비가 둘다 변한다.
@Getter
@Setter
public class Rectangle {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int computeArea() {
return width * height;
}
}
Java
복사
사각형
public class Square extends Rectangle {
public Square(int side) {
super(side, side);
}
@Override
public void setWidth(int width) {
setSide(width);
}
@Override
public void setHeight(int height) {
setSide(height);
}
public void setSide(int side) {
super.setWidth(side);
super.setHeight(side);
}
}
Java
복사
정사각형
public class Main {
public static void main(String[] args) {
Rectangle rectangle = new Rectangle(10, 20);
System.out.println(rectangle.computeArea());
Square square = new Square(10);
System.out.println(square.computeArea());
useRectangle(rectangle);
}
private static void useRectangle(Rectangle rectangle) {
rectangle.setHeight(20);
rectangle.setWidth(30);
assert rectangle.getHeight() == 20 : "Height Not equal to 20";
assert rectangle.getWidth() == 30 : "Width Not equal to 30";
}
}
Java
복사
public interface Shape {
public int computeArea();
}
Java
복사
@Getter
@Setter
public class Square implements Shape {
private int side;
public Square(int side) {
this.side = side;
}
@Override
public int computeArea() {
return side * side;
}
}
Java
복사
인터페이스 분리
Interface Pollution
•
거대한 인터페이스
•
관련없는 메소드 ⇒ 구현만하고 아무것도 안하는 메서드를 만들게됨
public interface PersistenceService<T extends Entity> {
public void save(T entity);
public void delete(T entity);
public T findById(Long id);
public List<T> findByName(String name);
}
Java
복사
public class UserPersistenceService implements PersistenceService<User>{
private static final Map<Long, User> USERS = new HashMap<>();
@Override
public void save(User entity) {
synchronized (USERS) {
USERS.put(entity.getId(), entity);
}
}
@Override
public void delete(User entity) {
synchronized (USERS) {
USERS.remove(entity.getId());
}
}
@Override
public User findById(Long id) {
synchronized (USERS) {
return USERS.get(id);
}
}
@Override
public List<User> findByName(String name) {
synchronized (USERS) {
return USERS.values().stream().filter(u->u.getName().equalsIgnoreCase(name)).collect(Collectors.toList());
}
}
}
Java
복사
public interface PersistenceService<T extends Entity> {
public void save(T entity);
public void delete(T entity);
public T findById(Long id);
}
Java
복사
public class UserPersistenceService implements PersistenceService<User>{
private static final Map<Long, User> USERS = new HashMap<>();
@Override
public void save(User entity) {
synchronized (USERS) {
USERS.put(entity.getId(), entity);
}
}
@Override
public void delete(User entity) {
synchronized (USERS) {
USERS.remove(entity.getId());
}
}
@Override
public User findById(Long id) {
synchronized (USERS) {
return USERS.get(id);
}
}
}
Java
복사
public class OrderPersistenceService implements PersistenceService<Order> {
private static final Map<Long, Order> ORDERS = new HashMap<>();
@Override
public void save(Order entity) {
synchronized (ORDERS) {
ORDERS.put(entity.getId(), entity);
}
}
@Override
public void delete(Order entity) {
synchronized (ORDERS) {
ORDERS.remove(entity.getId());
}
}
@Override
public Order findById(Long id) {
synchronized (ORDERS) {
return ORDERS.get(id);
}
}
}
Java
복사
의존성 역전 원칙
1.
높은 수준의 모듈은 저 수준의 모듈에 의존하면 안된다. (특정 클래스와 긴밀하게 연결되어서는 안된다. 추상화에 의존해야 한다.S)
2.
추상화된 것이 세부사항애 의존해서는 안된다.
의존성
•
디자인 패턴
빌더
•
불변 클래스 - 생성이후 변경할 수 없다.
•
불변 클래스는 생성자에게 모든 변수를 초기화 해주어야 한다.
•
하지만 인자에 따른 생성자를 모두 만들어주기에는 코드가 너무 길어진다.
public class Client {
public static void main(String[] args) {
// 유저 생성 & 빌더 생성
User user = createUser();
UserDTOBuilder builder = new UserWebDTOBuilder();
// DTO를 빌더로 생성
UserDTO dto = directBuild(builder, user);
System.out.println("dto = " + dto);
}
// 빌더 사용
private static UserDTO directBuild(UserDTOBuilder builder, User user) {
return builder.withFirstName(user.getFirstName())
.withLastName(user.getLastName())
.withBirthday(user.getBirthday())
.withAddress(user.getAddress())
.build();
}
/**
* Returns a sample user.
*/
public static User createUser() {
User user = new User();
user.setBirthday(LocalDate.of(1960, 5, 6));
user.setFirstName("Ron");
user.setLastName("Swanson");
Address address = new Address();
address.setHouseNumber("100");
address.setStreet("State Street");
address.setCity("Pawnee");
address.setState("Indiana");
address.setZipcode("47998");
user.setAddress(address);
return user;
}
}
Java
복사
@Getter @Setter
public class User {
private String firstName;
private String lastName;
private LocalDate birthday;
private Address address;
}
Java
복사
@Getter @Setter
public class Address {
private String houseNumber;
private String street;
private String city;
private String zipcode;
private String state;
}
Java
복사
@Getter
public class UserWebDTO implements UserDTO {
private String name;
private String address;
private String age;
public UserWebDTO(String name, String address, String age) {
this.name = name;
this.address = address;
this.age = age;
}
@Override
public String toString() {
return "name=" + name + "\nage=" + age + "\naddress=" + address ;
}
}
Java
복사
setter가 없는 불변객체
public class UserWebDTOBuilder implements UserDTOBuilder {
private String firstName;
private String lastName;
private String age;
private String address;
private UserWebDTO dto;
@Override
public UserDTOBuilder withFirstName(String fname) {
firstName = fname;
return this;
}
@Override
public UserDTOBuilder withLastName(String lname) {
lastName = lname;
return this;
}
@Override
public UserDTOBuilder withBirthday(LocalDate date) {
Period ageInYears = Period.between(date, LocalDate.now());
age = Integer.toString(ageInYears.getYears());
return this;
}
@Override
public UserDTOBuilder withAddress(Address address) {
this.address = address.getHouseNumber() + ", " + address.getStreet() + "\n " + address.getCity() + "\n " + address.getState() + " " + address.getZipcode();
return this;
}
@Override
public UserDTO build() {
dto = new UserWebDTO(firstName + " " + lastName, address, age);
return dto;
}
@Override
public UserDTO getUserDTO() {
return null;
}
}
Java
복사
public class Client {
public static void main(String[] args) {
User user = createUser();
// Client has to provide director with concrete builder
UserDTO dto = directBuild(UserDTO.getBuilder(), user);
System.out.println(dto);
}
/**
* This method serves the role of director in builder pattern.
*/
private static UserDTO directBuild(UserDTOBuilder builder, User user) {
return builder
.withFirstName(user.getFirstName())
.withLastName(user.getLastName())
.withBirthday(user.getBirthday())
.withAddress(user.getAddress())
.build();
}
/**
* Returns a sample user.
*/
public static User createUser() {
User user = new User();
user.setBirthday(LocalDate.of(1960, 5, 6));
user.setFirstName("Ron");
user.setLastName("Swanson");
Address address = new Address();
address.setHouseNumber("100");
address.setStreet("State Street");
address.setCity("Pawnee");
address.setState("Indiana");
address.setZipcode("47998");
user.setAddress(address);
return user;
}
}
Java
복사
@Getter @Setter
public class UserDTO {
private String name;
private String address;
private String age;
@Override
public String toString() {
return "name=" + name + "\nage=" + age + "\naddress=" + address ;
}
//Get builder instance
public static UserDTOBuilder getBuilder() {
return new UserDTOBuilder();
}
//Builder
public static class UserDTOBuilder {
private String firstName;
private String lastName;
private String age;
private String address;
private UserDTO userDTO;
public UserDTOBuilder withFirstName(String fname) {
this.firstName = fname;
return this;
}
public UserDTOBuilder withLastName(String lname) {
this.lastName = lname;
return this;
}
public UserDTOBuilder withBirthday(LocalDate date) {
age = Integer.toString(Period.between(date, LocalDate.now()).getYears());
return this;
}
public UserDTOBuilder withAddress(Address address) {
this.address = address.getHouseNumber() + " " +address.getStreet()
+ "\n"+address.getCity()+", "+address.getState()+" "+address.getZipcode();
return this;
}
public UserDTO build() {
this.userDTO = new UserDTO();
userDTO.setName(firstName+" " + lastName);
userDTO.setAddress(address);
userDTO.setAge(age);
return this.userDTO;
}
}
}
Java
복사
빌더 패턴의 주의점
•
빌더 패턴은 손쉽게 불변 객체를 만들 수 있으며, 불필요한 생성자를 방지할 수 있습니다.
•
불변성이 필요하지 않아도 객체 생성에 직관적인 코드가 만들어집니다.
•
객체 생성과 초기화는 같이 이루어져야 한다,
•
추상 빌더는 크게 의미가 없으나, 여러가지 클래스에대한 공통 빌더를 만들 수는 있다. (클레스 계층구조 잘보기)
•
생성자의 인수가 너무 많을때 고려한다.
실제 예시
•
java.lang.StringBuilder 클래스와 java.nio 패키지의 ByteBuffer, CharBuffer 클래스들이 빌더 패턴의 예시로 자주 언급됩니다.
•
제 의견으로는, 이 클래스들이 빌더 패턴의 예로 제시될 수 있지만, 완벽하게 맞지는 않습니다. 이 클래스들은 여러 단계로 객체를 만들 수 있게 해주지만, 최종 객체의 일부분만을 한 번에 만듭니다. 이런 점에서 빌더 패턴을 어느 정도 구현했다고 볼 수 있습니다.
•
StringBuilder는 빌더 패턴의 의도와 목적을 충족시킵니다. 하지만 StringBuilder의 구조를 자세히 보면, 문제가 있습니다. 빌더 패턴의 정의에 따르면, 빌더는 동일한 단계를 통해 다양한 형태의 객체를 만들 수 있어야 합니다.
빌더와 프로토타입 차이
빌더 (Builder)
// 피자 클래스
public class Pizza {
private String dough;
private String sauce;
private String topping;
public static class Builder {
private String dough;
private String sauce;
private String topping;
public Builder dough(String dough) {
this.dough = dough;
return this;
}
public Builder sauce(String sauce) {
this.sauce = sauce;
return this;
}
public Builder topping(String topping) {
this.topping = topping;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
private Pizza(Builder builder) {
this.dough = builder.dough;
this.sauce = builder.sauce;
this.topping = builder.topping;
}
@Override
public String toString() {
return "Pizza with " + dough + ", " + sauce + ", " + topping;
}
public static void main(String[] args) {
Pizza pizza = new Pizza.Builder()
.dough("cross")
.sauce("tomato")
.topping("mozzarella")
.build();
System.out.println(pizza);
}
}
Java
복사
•
복잡한 생성자가 있을 때, 빌더는 이를 다루는 방법을 제공합니다.
•
우리는 빌더를 별도의 클래스로 만들어 기존 코드(레거시 코드)와 함께 사용할 수 있습니다.
프로토타입 (Prototype)
프로토타입 패턴은 객체를 복제하여 새로운 객체를 생성합니다. 여기서는 클론 가능한 직원을 생성하는 예제를 보겠습니다.
// 직원 클래스
public class Employee implements Cloneable {
private String name;
private String position;
public Employee(String name, String position) {
this.name = name;
this.position = position;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Employee{name='" + name + "', position='" + position + "'}";
}
public static void main(String[] args) {
try {
Employee original = new Employee("John Doe", "Manager");
Employee clone = (Employee) original.clone();
clone.setName("Jane Doe");
clone.setPosition("Developer");
System.out.println("Original: " + original);
System.out.println("Clone: " + clone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
Java
복사
•
프로토타입은 생성자를 완전히 생략할 수 있게 합니다.
•
자바에서는 이 패턴이 clone 메서드를 사용하여 작동하며, 기존 코드를 수정해야 하기 때문에 레거시 코드와 함께 사용할 수 없을 수도 있습니다.
Pitfalls (함정)
•
초보자에게 복잡할 수 있음: 주로 '메서드 체이닝' 사용으로 인해 빌더 메서드가 빌더 객체 자체를 반환하기 때문에 초보자에게 다소 복잡할 수 있습니다.
•
부분적으로 초기화된 객체의 가능성: 사용자 코드가 withXXX 메서드를 사용하여 일부 속성만 설정하거나 전혀 설정하지 않고 build()를 호출할 수 있습니다. 필요한 속성이 누락된 경우, build 메서드는 적절한 기본값을 제공하거나 예외를 던져야 합니다.
심플 팩토리
Simple Factory란?
•
정의: 간단한 팩토리 패턴에서는 객체 생성 로직을 별도의 클래스로 이동시키고, 이 클래스의 정적 메서드로 객체를 생성합니다.
•
디자인 패턴 여부: 일부 사람들은 간단한 팩토리를 디자인 패턴으로 보지 않습니다. 이는 단순히 객체 생성을 캡슐화하는 메서드이기 때문입니다. 복잡한 작업이 필요하지 않습니다.
•
사용 이유: 객체를 인스턴스화할 때 여러 옵션이 있는 경우, 올바른 클래스를 선택하는 간단한 로직을 사용합니다.
UML 설명
•
SimpleFactory 클래스: 정적 메서드를 제공하여 하위 클래스의 인스턴스를 얻습니다.
•
Product 클래스: 이 클래스와 그 하위 클래스들의 객체가 필요합니다.
•
Client: SimpleFactory를 사용하여 필요한 객체를 생성합니다.
간단한 팩토리 구현
1.
별도의 클래스 생성: SimpleFactory라는 클래스를 만들고, 필요한 객체를 반환하는 메서드를 추가합니다.
2.
정적 메서드 추가: 이 메서드는 객체를 인스턴스화할 클래스를 결정하는 인자를 받습니다.
3.
추가 인자 제공: 객체를 인스턴스화할 때 사용할 추가 인자를 제공할 수 있습니다.
구현 고려사항 (Implementation Considerations)
•
단순 팩토리는 기존 클래스의 메서드로도 구현 가능: 별도의 클래스를 추가하면 다른 코드에서도 간단한 팩토리를 쉽게 사용할 수 있습니다.
•
상태 추적이 필요하지 않음: 단순 팩토리는 상태를 추적할 필요가 없기 때문에 정적 메서드로 유지하는 것이 좋습니다.
디자인 고려사항 (Design Considerations)
•
다른 디자인 패턴과 함께 사용: 간단한 팩토리는 객체를 생성하기 위해 빌더 패턴과 같은 다른 디자인 패턴을 사용할 수 있습니다.
•
특정 하위 클래스에서 팩토리를 특수화해야 하는 경우: 이 경우 팩토리 메서드 디자인 패턴이 필요합니다.
단순 팩토리 예제 (Example of a Simple Factory)
자바의 java.text.NumberFormat 클래스는 getInstance 메서드를 가지고 있으며, 이는 단순 팩토리의 예입니다.
java코드 복사
private static NumberFormat getInstance(LocaleProviderAdapter adapter, Locale locale, int choice) {
NumberFormatProvider provider = adapter.getNumberFormatProvider();
NumberFormat numberFormat = null;
switch (choice) {
case NUMBERSTYLE:
numberFormat = provider.getNumberInstance(locale);
break;
case PERCENTSTYLE:
numberFormat = provider.getPercentInstance(locale);
break;
case CURRENCYSTYLE:
numberFormat = provider.getCurrencyInstance(locale);
break;
case INTEGERSTYLE:
numberFormat = provider.getIntegerInstance(locale);
break;
}
return numberFormat;
}
Java
복사
비교 및 대조: 팩토리 메서드 패턴 (Compare & Contrast with Factory Method Pattern)
•
단순 팩토리(Simple Factory):
◦
인스턴스화 로직을 클라이언트 코드에서 분리하여 보통 정적 메서드로 구현.
◦
생성할 수 있는 모든 클래스에 대해 알고 있음.
•
팩토리 메서드(Factory Method):
◦
객체 생성을 하위 클래스에 위임할 때 유용.
◦
생성할 모든 제품 하위 클래스에 대해 미리 알 필요가 없음.
단점 (Pitfalls)
•
복잡해질 수 있는 인스턴스화 기준: 시간이 지나면서 인스턴스화할 객체를 결정하는 기준이 복잡해질 수 있음. 이런 상황이 발생하면 팩토리 메서드 디자인 패턴을 사용하는 것이 좋습니다.
팩토리 메소드
UML 설명
역할(Role)
•
Product: 팩토리 메서드가 생성하는 제품의 인터페이스 또는 기본 클래스입니다.
•
Concrete Product: Product 인터페이스나 클래스를 구현합니다.
•
Creator: 추상 팩토리 메서드를 선언합니다. 이 클래스는 팩토리 메서드를 사용하여 제품을 생성합니다.
•
Concrete Creator: 팩토리 메서드를 구현하고, 구체적인 제품 인스턴스를 반환합니다.
팩토리 메서드 구현 (Implement a Factory Method)
1.
Creator 클래스 생성: 구체적인 객체를 제공할 수 있는 경우, Creator 클래스는 구체적일 수 있으며, 기본 객체를 제공할 수 있는 경우 추상적일 수도 있습니다.
2.
구현 클래스에서 메서드 재정의: 구현 클래스는 메서드를 재정의하여 객체를 반환합니다.
구현 고려사항 (Implementation Considerations)
•
구체 클래스인 Creator: Creator는 구체 클래스가 될 수 있으며, 기본 객체를 제공하는 팩토리 메서드의 기본 구현을 제공할 수 있습니다. 이러한 경우 기본 Creator에서 "기본" 객체를 생성합니다.
•
추가 인자 사용: 단순 팩토리 방식으로 추가 인자를 받아서 서로 다른 객체 유형을 선택할 수 있습니다. 하위 클래스는 팩토리 메서드를 재정의하여 특정 기준에 따라 서로 다른 객체를 선택적으로 생성할 수 있습니다.
디자인 고려사항 (Design Considerations)
•
Creator 계층 구조: 팩토리 메서드 패턴에서 Creator 계층 구조는 제품 계층 구조를 반영합니다. 일반적으로 객체 유형별로 구체적인 Creator를 가지게 됩니다.
•
템플릿 메서드 패턴: 템플릿 메서드 디자인 패턴은 종종 팩토리 메서드를 사용합니다.
•
추상 팩토리 패턴: 또 다른 생성 패턴인 추상 팩토리 패턴도 팩토리 메서드 패턴을 활용합니다.
팩토리 메서드 예제 (Examples of a Factory Method)
java.util.Collection 또는 java.util.AbstractCollection 클래스는 팩토리 메서드의 예로, 추상 메서드 iterator()를 가지고 있습니다.
java코드 복사
public abstract class AbstractCollection<E> implements Collection<E> {
// 유일한 생성자
protected AbstractCollection() {}
// 쿼리 작업
public abstract Iterator<E> iterator();
}
Java
복사
단점 (Pitfalls)
•
구현이 더 복잡함: 더 많은 클래스가 필요하고 단위 테스트가 필요합니다.
•
초기부터 사용해야 함: 팩토리 메서드 디자인 패턴은 처음부터 사용해야 하며, 기존 코드를 리팩토링하기 쉽지 않습니다.
•
하위 클래스를 강제 생성: 적절한 인스턴스를 생성하기 위해 하위 클래스를 생성해야 합니다.
요약 (In-A-Hurry Summary)
팩토리 메서드 패턴은 객체 인스턴스화를 하위 클래스에 위임하려는 경우에 사용합니다. 제품 상속 계층 구조가 있고, 이를 확장할 가능성이 있을 때 유용합니다.
예제 코드
예제 UML 설명
•
Message: 메시지의 추상 클래스입니다.
•
TextMessage, JSONMessage: Message 클래스를 구현한 구체적인 클래스들입니다.
•
MessageCreator: 팩토리 메서드를 선언한 추상 클래스입니다.
•
JSONMessageCreator, TextMessageCreator: MessageCreator를 구현한 클래스들로, 구체적인 메시지 객체를 생성합니다.
Product 인터페이스 및 구현 클래스들
// Message 추상 클래스
public abstract class Message {
public abstract void send();
}
// TextMessage 클래스
public class TextMessage extends Message {
@Override
public void send() {
System.out.println("Sending Text Message");
}
}
// JSONMessage 클래스
public class JSONMessage extends Message {
@Override
public void send() {
System.out.println("Sending JSON Message");
}
}
Java
복사
Creator 클래스 및 구현 클래스들
// MessageCreator 추상 클래스
public abstract class MessageCreator {
// 팩토리 메서드
protected abstract Message createMessage();
// 메시지를 보내는 메서드
public void sendMessage() {
Message msg = createMessage();
msg.send();
}
}
// TextMessageCreator 클래스
public class TextMessageCreator extends MessageCreator {
@Override
protected Message createMessage() {
return new TextMessage();
}
}
// JSONMessageCreator 클래스
public class JSONMessageCreator extends MessageCreator {
@Override
protected Message createMessage() {
return new JSONMessage();
}
}
Java
복사
클라이언트 코드
public class Client {
public static void main(String[] args) {
MessageCreator creator;
// 텍스트 메시지를 생성하는 팩토리 메서드를 사용
creator = new TextMessageCreator();
creator.sendMessage(); // Output: Sending Text Message
// JSON 메시지를 생성하는 팩토리 메서드를 사용
creator = new JSONMessageCreator();
creator.sendMessage(); // Output: Sending JSON Message
}
}
Java
복사
이 예제는 팩토리 메서드 패턴을 사용하여 다양한 메시지 유형을 생성하고 사용하는 방법을 보여줍니다. MessageCreator 클래스는 팩토리 메서드를 정의하고, TextMessageCreator와 JSONMessageCreator 클래스는 이 메서드를 구현하여 구체적인 메시지 객체를 생성합니다.
추상 팩토리
UML 설명
역할(Role)
•
Abstract Factory: 제품을 생성하는 연산을 정의하는 인터페이스입니다.
•
Concrete Factory: 특정 제품군을 생성하는 팩토리를 구현합니다.
•
Abstract Product: 특정 제품 유형을 위한 인터페이스입니다.
•
Concrete Product: 제품 인터페이스나 클래스를 구현합니다.
•
Client: 추상 팩토리와 추상 제품을 사용합니다.
추상 팩토리 구현 (Implement Abstract Factory)
1.
제품 "세트" 연구: 제품군을 식별합니다.
2.
추상 팩토리 생성: 추상 클래스나 인터페이스로 정의합니다.
3.
구체 팩토리 구현: 각 제품군에 대한 구체 팩토리를 제공합니다.
4.
팩토리 메서드 패턴 사용: 추상 팩토리는 여러 팩토리 메서드를 가진 객체로 생각할 수 있습니다.
예제 코드
Abstract Product 및 Concrete Product
java코드 복사
// AbstractProductA 인터페이스
public interface AbstractProductA {
void performActionA();
}
// ConcreteProductA1 클래스
public class ConcreteProductA1 implements AbstractProductA {
@Override
public void performActionA() {
System.out.println("Action A1 performed.");
}
}
// ConcreteProductA2 클래스
public class ConcreteProductA2 implements AbstractProductA {
@Override
public void performActionA() {
System.out.println("Action A2 performed.");
}
}
// AbstractProductB 인터페이스
public interface AbstractProductB {
void performActionB();
}
// ConcreteProductB1 클래스
public class ConcreteProductB1 implements AbstractProductB {
@Override
public void performActionB() {
System.out.println("Action B1 performed.");
}
}
// ConcreteProductB2 클래스
public class ConcreteProductB2 implements AbstractProductB {
@Override
public void performActionB() {
System.out.println("Action B2 performed.");
}
}
Java
복사
Abstract Factory 및 Concrete Factory
java코드 복사
// AbstractFactory 인터페이스
public interface AbstractFactory {
AbstractProductA createProductA();
AbstractProductB createProductB();
}
// ConcreteFactory1 클래스
public class ConcreteFactory1 implements AbstractFactory {
@Override
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
}
// ConcreteFactory2 클래스
public class ConcreteFactory2 implements AbstractFactory {
@Override
public AbstractProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public AbstractProductB createProductB() {
return new ConcreteProductB2();
}
}
Java
복사
클라이언트 코드
java코드 복사
public class Client {
private AbstractProductA productA;
private AbstractProductB productB;
public Client(AbstractFactory factory) {
productA = factory.createProductA();
productB = factory.createProductB();
}
public void performActions() {
productA.performActionA();
productB.performActionB();
}
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
Client client1 = new Client(factory1);
client1.performActions(); // Output: Action A1 performed. Action B1 performed.
AbstractFactory factory2 = new ConcreteFactory2();
Client client2 = new Client(factory2);
client2.performActions(); // Output: Action A2 performed. Action B2 performed.
}
}
Java
복사
이 예제는 추상 팩토리 패턴을 사용하여 두 개의 서로 다른 제품군을 생성하고 사용하는 방법을 보여줍니다. AbstractFactory 인터페이스는 제품을 생성하는 메서드를 정의하고, ConcreteFactory1과 ConcreteFactory2 클래스는 각기 다른 제품군을 생성하는 구현을 제공합니다. 클라이언트 코드는 팩토리를 사용하여 제품을 생성하고, 각 제품의 동작을 실행합니다.
개념 설명:
•
추상 팩토리 패턴(Abstract Factory Pattern)은 관련된 객체들의 그룹을 생성하는 인터페이스를 제공하는 패턴입니다. 이 패턴을 사용하면 클라이언트가 구체적인 클래스들을 지정하지 않고도 객체를 생성할 수 있습니다.
구성 요소:
1.
추상 팩토리(AbstractFactory): 제품을 생성하기 위한 인터페이스를 정의합니다.
2.
구체 팩토리(ConcreteFactory): 추상 팩토리를 구현하여 구체적인 제품을 생성합니다.
3.
추상 제품(AbstractProduct): 제품 객체에 대한 인터페이스를 정의합니다.
4.
구체 제품(ConcreteProduct): 추상 제품 인터페이스를 구현한 클래스들입니다.
5.
클라이언트(Client): 추상 팩토리 및 추상 제품을 사용하여 작업을 수행합니다.
UML 다이어그램 설명:
•
AbstractFactory: 추상 팩토리 클래스는 createProductA(), createProductB() 등의 메서드를 정의합니다.
•
ConcreteFactoryA, ConcreteFactoryB: 각 팩토리는 추상 팩토리 메서드를 구현하여 ProductAOne, ProductATwo, ProductBOne, ProductBTwo 등의 객체를 생성합니다.
•
AbstractProductA, AbstractProductB: 각 제품군의 인터페이스를 정의합니다.
•
ConcreteProductAOne, ConcreteProductATwo, ConcreteProductBOne, ConcreteProductBTwo: 실제 제품 클래스로, 추상 제품 인터페이스를 구현합니다.
•
Client: 추상 팩토리와 추상 제품을 사용하여 객체를 생성하고 사용합니다.
구현 고려 사항:
1.
기본 객체 제공: 기본 구현을 제공할 수 있는 구체 클래스를 사용하거나, 추상 클래스로 정의하여 필요에 따라 서브클래스에서 구현하도록 합니다.
2.
추가 인자 사용: 팩토리 메서드에서 추가 인자를 받아 다양한 객체를 선택적으로 생성할 수 있습니다.
디자인 고려 사항:
1.
제품 계층 구조 반영: 팩토리 메서드 패턴의 생성자 계층 구조가 제품 계층 구조를 반영합니다.
2.
템플릿 메서드 패턴 활용: 템플릿 메서드 디자인 패턴을 자주 사용하여 팩토리 메서드를 구성할 수 있습니다.
3.
추상 팩토리와의 연계: 추상 팩토리 디자인 패턴은 팩토리 메서드 패턴을 활용하여 다수의 팩토리 메서드를 포함할 수 있습니다.
예제 코드:
java코드 복사
// AbstractProductA
interface AbstractProductA {
void use();
}
// ConcreteProductA1
class ProductAOne implements AbstractProductA {
public void use() {
System.out.println("Using Product A1");
}
}
// ConcreteProductA2
class ProductATwo implements AbstractProductA {
public void use() {
System.out.println("Using Product A2");
}
}
// AbstractProductB
interface AbstractProductB {
void consume();
}
// ConcreteProductB1
class ProductBOne implements AbstractProductB {
public void consume() {
System.out.println("Consuming Product B1");
}
}
// ConcreteProductB2
class ProductBTwo implements AbstractProductB {
public void consume() {
System.out.println("Consuming Product B2");
}
}
// AbstractFactory
interface AbstractFactory {
AbstractProductA createProductA();
AbstractProductB createProductB();
}
// ConcreteFactory1
class ConcreteFactoryA implements AbstractFactory {
public AbstractProductA createProductA() {
return new ProductAOne();
}
public AbstractProductB createProductB() {
return new ProductBOne();
}
}
// ConcreteFactory2
class ConcreteFactoryB implements AbstractFactory {
public AbstractProductA createProductA() {
return new ProductATwo();
}
public AbstractProductB createProductB() {
return new ProductBTwo();
}
}
// Client
public class Client {
public static void main(String[] args) {
AbstractFactory factoryA = new ConcreteFactoryA();
AbstractProductA productA1 = factoryA.createProductA();
AbstractProductB productB1 = factoryA.createProductB();
productA1.use();
productB1.consume();
AbstractFactory factoryB = new ConcreteFactoryB();
AbstractProductA productA2 = factoryB.createProductA();
AbstractProductB productB2 = factoryB.createProductB();
productA2.use();
productB2.consume();
}
}
Java
복사
추가 설명:
•
추상 팩토리 패턴은 서로 관련이 있거나 의존성이 있는 객체들을 생성하는데 유용합니다.
•
팩토리 메서드 패턴을 사용하여 구체적인 객체 생성을 서브클래스에 위임할 수 있으며, 이는 코드의 유연성과 확장성을 높여줍니다.
•
추상 팩토리 패턴을 적용하면 클라이언트 코드는 구체적인 클래스를 알 필요가 없으며, 이는 유지보수성과 코드의 가독성을 향상시킵니다.
개체 풀
오브젝트 풀(Object Pool)이란 무엇인가?
정의: 오브젝트 풀은 클래스 인스턴스를 생성하는 비용이 높고, 짧은 시간 동안 많은 수의 객체가 필요한 경우에 사용됩니다.
사용법: 객체를 미리 생성하거나 인메모리 캐시에 수집해 둡니다. 코드에서 객체가 필요할 때, 이 캐시에서 객체를 제공합니다.
복잡성: 효율적으로 결함 없이 구현하기 가장 복잡한 패턴 중 하나입니다.
UML 다이어그램 설명
•
ObjectPool: 재사용 가능한 객체 인스턴스를 캐시하고, 재사용 가능한 객체를 얻고 반납하는 메서드를 제공합니다.
•
Client: 객체 풀을 사용하여 재사용 가능한 객체 인스턴스를 얻습니다.
•
AbstractReusable: 재사용 가능한 객체의 작업을 정의합니다.
•
ConcreteReusable: 상태, 작업 및 초기화 메서드를 구현하여 재사용 가능한 객체를 구현합니다.
구현 단계
오브젝트 풀 클래스 생성:
1.
객체의 스레드 안전 캐싱을 구현합니다.
2.
객체를 획득하고 반납하는 메서드를 제공합니다.
3.
객체를 풀에 제공하기 전에 캐시된 객체를 초기화합니다.
재사용 가능한 객체:
•
코드에서 반납할 때 객체의 상태를 초기화하는 메서드를 제공해야 합니다.
결정 사항:
•
풀에 객체가 없을 때 새 객체를 생성할지 아니면 객체가 사용 가능해질 때까지 대기할지를 결정합니다.
•
이 선택은 객체가 고정된 수의 외부 자원에 연결되어 있는지 여부에 따라 달라집니다.
예제 코드
다음은 Java로 구현한 예제입니다:
java코드 복사
import java.util.concurrent.ConcurrentLinkedQueue;
abstract class AbstractReusable {
public abstract void operation();
public abstract void reset();
}
class ConcreteReusable extends AbstractReusable {
private String state;
public void operation() {
System.out.println("Performing operation with state: " + state);
}
public void reset() {
state = null;
}
public void setState(String state) {
this.state = state;
}
}
class ObjectPool {
private ConcurrentLinkedQueue<ConcreteReusable> pool = new ConcurrentLinkedQueue<>();
public ObjectPool(int initialSize) {
for (int i = 0; i < initialSize; i++) {
pool.add(new ConcreteReusable());
}
}
public ConcreteReusable getReusable() {
ConcreteReusable reusable = pool.poll();
if (reusable == null) {
reusable = new ConcreteReusable();
}
return reusable;
}
public void releaseReusable(ConcreteReusable reusable) {
reusable.reset();
pool.add(reusable);
}
}
public class Client {
public static void main(String[] args) {
ObjectPool pool = new ObjectPool(2);
ConcreteReusable reusable1 = pool.getReusable();
reusable1.setState("First Use");
reusable1.operation();
pool.releaseReusable(reusable1);
ConcreteReusable reusable2 = pool.getReusable();
reusable2.setState("Second Use");
reusable2.operation();
pool.releaseReusable(reusable2);
}
}
Java
복사
요약
오브젝트 풀: 객체 생성을 최소화하기 위해 재사용 가능한 객체 집합을 효율적으로 관리합니다.
디자인 고려사항:
•
스레드 안전성: 풀을 스레드 안전하게 만들어 동시성 문제를 피해야 합니다.
•
객체 상태 초기화: 재사용 전에 객체의 상태를 초기화하여 부작용을 방지해야 합니다.
•
객체 수명 주기 관리: 풀에 객체가 없을 때 새 객체를 생성할지, 아니면 사용할 수 있을 때까지 대기할지 결정해야 합니다.
Object Pool이란?
Object Pool(객체 풀)은 객체 생성 비용이 높고, 짧은 시간 동안 많은 객체가 필요할 때 사용하는 디자인 패턴입니다.
•
정의: 객체 풀은 생성 비용이 높은 객체를 관리하고 재사용할 수 있도록 합니다.
•
용도: 객체를 미리 생성하거나 메모리 캐시에 수집합니다. 코드에서 객체가 필요할 때, 이 캐시에서 제공됩니다.
•
복잡성: 효율적으로 구현하고 결함 없이 사용하기 어려운 패턴 중 하나입니다.
UML 다이어그램 설명
•
ObjectPool: 재사용 가능한 객체 인스턴스를 캐시하고 이를 가져오고 반환하는 메서드를 제공합니다.
•
Client: 객체 풀을 사용하여 재사용 가능한 객체 인스턴스를 가져옵니다.
•
AbstractReusable: 재사용 가능한 객체의 작업을 정의합니다.
•
ConcreteReusable: 상태, 작업 및 리셋 메서드를 가진 재사용 가능한 객체를 구현합니다.
구현 단계
1.
Object Pool 클래스 생성:
•
객체의 스레드 안전한 캐싱을 구현합니다.
•
객체를 획득하고 반환하는 메서드를 제공합니다.
•
캐시된 객체를 제공하기 전에 리셋합니다.
2.
재사용 가능한 객체:
•
코드에 의해 반환될 때 상태를 리셋하는 메서드를 제공해야 합니다.
3.
결정 포인트:
•
풀이 비었을 때 새 객체를 생성할지 아니면 객체가 사용 가능해질 때까지 기다릴지 결정합니다.
•
이 선택은 객체가 고정된 수의 외부 리소스에 연결되어 있는지에 따라 달라집니다.
Java 예제 코드
java코드 복사
import java.util.concurrent.ConcurrentLinkedQueue;
abstract class AbstractReusable {
public abstract void operation();
public abstract void reset();
}
class ConcreteReusable extends AbstractReusable {
private String state;
public void operation() {
System.out.println("Performing operation with state: " + state);
}
public void reset() {
state = null;
}
public void setState(String state) {
this.state = state;
}
}
class ObjectPool {
private ConcurrentLinkedQueue<ConcreteReusable> pool = new ConcurrentLinkedQueue<>();
public ObjectPool(int initialSize) {
for (int i = 0; i < initialSize; i++) {
pool.add(new ConcreteReusable());
}
}
public ConcreteReusable getReusable() {
ConcreteReusable reusable = pool.poll();
if (reusable == null) {
reusable = new ConcreteReusable();
}
return reusable;
}
public void releaseReusable(ConcreteReusable reusable) {
reusable.reset();
pool.add(reusable);
}
}
public class Client {
public static void main(String[] args) {
ObjectPool pool = new ObjectPool(2);
ConcreteReusable reusable1 = pool.getReusable();
reusable1.setState("First Use");
reusable1.operation();
pool.releaseReusable(reusable1);
ConcreteReusable reusable2 = pool.getReusable();
reusable2.setState("Second Use");
reusable2.operation();
pool.releaseReusable(reusable2);
}
}
Java
복사
요약
•
Object Pool: 객체 생성을 최소화하여 재사용 가능한 객체를 효율적으로 관리합니다.
•
디자인 고려사항:
◦
스레드 안전성: 풀이 스레드 안전하도록 하여 동시성 문제를 방지합니다.
◦
객체 상태 리셋: 객체를 재사용하기 전에 상태를 리셋하여 부작용을 방지합니다.
◦
객체 수명 주기 관리: 풀이 비었을 때 새 객체를 생성할지 또는 사용 가능한 객체를 기다릴지 결정합니다.
구현 고려사항
1.
객체 상태 리셋: 리셋 작업이 비용이 많이 들지 않아야 성능 절감을 유지할 수 있습니다.
2.
객체 미리 캐싱: 객체를 미리 생성하면 사용 시점에 속도를 높일 수 있지만, 시작 시간과 메모리 소비가 증가할 수 있습니다.
3.
동기화: 객체 풀의 동기화는 리셋 시간과 동기화된 컨텍스트에서 리셋을 피해야 할 필요성을 고려해야 합니다.
디자인 고려사항
1.
매개변수화된 풀: 풀을 매개변수화하여 여러 객체를 캐싱하고 반환하는 방법을 제공할 수 있습니다.
2.
객체 풀링의 장점: 외부 리소스 초기화 비용이 높은 경우에만 유용합니다.
3.
장기 객체 풀링: 장기적으로 사용되는 객체를 풀링하지 않거나, 자주 객체를 생성하는 것을 피하기 위해 풀링하지 않아야 합니다.
Object Pool의 예
1.
Thread Pool Executor: java.util.concurrent.ThreadPoolExecutor는 스레드를 풀링합니다. 주로 ExecutorService를 통해 사용됩니다.
2.
데이터베이스 연결 풀링: Apache Commons DBCP 라이브러리는 데이터베이스 연결 풀링에 사용되며, 일반적으로 JNDI를 통해 생성되고 노출됩니다.
Prototype 패턴과의 비교
•
Object Pool:
◦
프로그램 실행 동안 자주 사용되는 캐시된 객체를 가집니다.
◦
객체를 명시적으로 풀로 반환해야 합니다. 반환하지 않으면 메모리/리소스 누수가 발생할 수 있습니다.
•
Prototype:
◦
필요할 때 객체를 생성하며, 캐싱은 없습니다.
◦
객체가 클론되면 특별한 처리가 필요 없으며, 일반 객체처럼 사용할 수 있습니다.
단점
1.
올바른 사용: 클라이언트 코드가 객체를 풀로 반환하는 것이 중요합니다.
2.
상태 리셋: 재사용 가능한 객체는 상태를 효율적으로 리셋할 수 있어야 합니다.
3.
레거시 코드: 기존 코드를 리팩토링하여 객체 풀을 사용하는 것은 어려울 수 있습니다.
4.
풀 사이즈 관리: 풀이 비었을 때 새 객체를 생성할지 또는 객체가 사용 가능해질 때까지 기다릴지 결정해야 합니다.