생성패턴
- 객체를 생성하는 것과 관련된 패턴으로, 객체의 생성과 변경이 전체 시스템에 미치는 영향을 최소화 하고, 코드의 유연성을 높여 준다.
- 종류
- Factory Method
- Singleton
- Prototype
- Builder
- Abstract Factory
- Chaining
Factory Method Pattern
- 인스턴스 생성과 관련된 패턴이다.
- 상위 클래스는 객체를 생성하기 위한 인터페이스를 정의한다.
- 하위 클래스에서는 어떤 클래스의 인스턴스를 생성할지 결정한다.
- 생성과 관련된 동일한 메서드는 상위 클래스에서 처리한다.
- 결론적으로 상황에 따라 다양한 인스턴스를 생성할 수 있게 도와주는 패턴이다.
class diagram
- Product: 팩토리 메소드가 생성하는 객체의 인터페이스를 정의한다.
- ConcreteProduct: Product 클래스에 정의된 인터페이스를 실제로 구현한다.
- Creator
- Product 타입의 객체를 반환하는 팩토리 메소드를 선언한다.
- Creator 클래스는 팩토리 메소드를 기본적으로 구현하는데, 이 구현에서는 ConcreateProduct 객체를 반환한다.
- Product 객체의 생성을 위해 팩토리 메소드를 호출한다.
- ConcreteCreator
- ConcreteProduct 의 인스턴스를 반환하기 위해 팩토리 메소드를 재정의 한다.
예제
public abstract class Car {
String carType;
public String toString() {
return carType;
}
}
- class diagram 에서 Product 에 해당된다.
public abstract class CarFactory {
public abstract Car createCar(String name); // add - on
public abstract Car returnMyCar(String name);
}
- class diagram 에서 Creator 에 해당된다.
public class HyundaiFactory extends CarFactory{
HashMap<String, Car> carMap = new HashMap<String, Car>();
@Override
public Car createCar(String name) {
Car car = null;
if (name == "sonata") {
car = new Sonata();
}
else if( name == "santafe") {
car = new Santafe();
}
return car;
}
@Override
public Car returnMyCar(String name) {
// Jame는 Sonata, Tomas는 Santafe 인 경우
Car myCar = carMap.get(name);
if(myCar == null) {
if(name.equals("James")){
myCar = new Sonata();
}
else if(name.equals("Tomas") ){
myCar = new Santafe();
}
carMap.put(name, myCar);
}
return myCar;
}
}
- class diagram 에서 ConcreteCreator 에 해당된다.
- returnCar 메서드 처럼 인스턴스를 생성하는 것 뿐 아니라 관리해 줄 수도 있다.
- 값이 있다면 그 값을 Map 에서 찾아서 반환하고, 없는 경우 새로 생성해서 Map 에 등록한다.
public class Sonata extends Car{
Sonata(){
carType = "Sonata";
}
}
public class Santafe extends Car{
Santafe(){
carType = "Santafe";
}
}
- class diagram 에서 ConcreteProduct 에 해당된다.
public class CarTest {
public static void main(String[] args) {
CarFactory factory = new HyundaiFactory();
Car newCar = factory.createCar("sonata");
System.out.println(newCar);
Car myCar = factory.returnMyCar("Tomas");
Car hisCar = factory.returnMyCar("Tomas");
System.out.println(myCar == hisCar);
}
}
실글톤 패턴
Singleton Pattern 이란?
- 클래스의 인스턴스는 오직 하나임을 보장하며 이 인스턴스에 접근할 수 있는 방법을 제공하는 패턴이다.
- 어떤 클래스 경우에는 정확히 하나의 인스턴스만을 갖도록 하는 것이 중요하다.
- 결국은 인스턴스가 여러개 있는 경우에 문제가 있는 경우 많이 사용한다.
- 한 회사에는 하나의 회계 시스템만이 운영하는 경우
- DataBase와 연결하는 connection은 여러개일 수 있지만, connection pool은 한 개인 경우
- 자바에는 전역 변수가 존재하지 않으므로 인스턴스가 하나만 존재하도록 설계해야 하고 이에 접근 하는 방법을 제공한다.
- 유일하게 존재하는 인스턴스로의 접근을 통제할 수 있다.
- 전역 변수를 사용함으로써 발생할 수 있는 오류를 (C++ 의 경우) 줄 일수 있다.
class diagram
- private 한 인스턴스를 속성으로 갖는다.
- 다이어 그램을 보면, 생성자가 private 인 것을 확인할 수 있다.
- 또한 접근할 수 있도록 public 한 메서드를 제공한다.
예제
public class ConnectionPool {
private static ConnectionPool instance = new ConnectionPool();
private ConnectionPool() {}
public static ConnectionPool getInstance() {
if(instance == null) {
instance = new ConnectionPool();
}
return instance;
}
}
- 먼저 private static 으로 반환될 인스턴스를 정의한다.
- 인스턴스 호출시 위에서 선언한 인스턴가 항상 반환된다.
- private 한 생성자를 만들어 생성자를 다른 곳에서 함부로 만들지 못하도록 제한한다.
- public한 getInstance 를 통해서 외부에서 인스턴스를 반환할 수 있도록 한다.
Prototype Pattern
Prototype Pattern 이란?
- 복제해서 인스턴스를 만드는 패턴이다.
- 클래스의 인스턴스가 생성과정이 복잡하거나 여러 조합에 의해 생성되어야 하는경우 하나의 견본(prototype)을 만들어
초기화 해두고 이를 복제해서 객체를 생성하는 방법이다. - 프로토타입 속성값을 활용하여 다양한 객체를 생성할 수 있다.
- 서브클래스의 수를 줄일 수 있다.
- 자바에서는 clone() 메서드를 재정의하여 구현한다.
Class diagram
- 다이어그램을 보면, 복제에 필요한 Prototype 이라는 인터페이스가 있다.
- 그 다음 인터페이스를 구현하는 ConcretePrototype 있는 것을 확인할 수 있다.
- 정리하면, 복제하는데 필요한 인터페이스를 정의하고, 인터페이스를 구현해서 구체적인 클래스를 구현한다.
- 인터페이스라는 틀을 이용해서 여러 구체적인 클래스를 복제하는 방식이다.
예제
class BookShelf implements Cloneable{
private ArrayList<Book> shelf;
public BookShelf() {
shelf = new ArrayList<Book>();
}
public void addBook(Book book) {
shelf.add(book);
}
@Override
protected Object clone() throws CloneNotSupportedException {
BookShelf another = new BookShelf();
for(Book book : shelf) {
another.addBook(new Book(book.getAuthor(), book.getTitle()));
}
return another;
}
public ArrayList<Book> getShelf() {
return shelf;
}
public void setShelf(ArrayList<Book> shelf) {
this.shelf = shelf;
}
public String toString() {
return shelf.toString();
}
}
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
BookShelf bookShelf = new BookShelf();
bookShelf.addBook(new Book("orange", "Tomas"));
bookShelf.addBook(new Book("apple", "James"));
bookShelf.addBook(new Book("grape", "Edward"));
BookShelf another = (BookShelf)bookShelf.clone();
bookShelf.getShelf().get(0).setAuthor("Mango");
bookShelf.getShelf().get(0).setTitle("Jane");
}
}
- clone() 메서드를 재정의하기 위해 Cloneable 인터페이스를 구현한다.
- 구현한 clone() 메서드는 깊은 복사(deep copy)를 하도록 구현한 메서드이다. (기본 구현은 얕은 복사이다.)
- 어떤 경우 이 패턴이 유요할까?
- database 에서 데이터를 여러번 가져와서 변경할때, 위의 패턴을 사용하지 않으면 매번 database 에서 똑같은 값을 가져와야한다.
- prototype pattern 을 사용하면 위의 경우를 해결할 수 있다.
Abstract Factory Pattern
Abstract Factory Pattern 이란?
- 여러 제품군을 한꺼번에 생성하는 패턴이다.
- 구체적인 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공하는 패턴이다.
- 정리하면, 인스턴스를 생성하고 싶은 Factory 만 선택하면, 그 팩토리에 걸맞는 인스턴스가 생성된다.
- 상황에 따라 적절한 형태의 인스턴스를 제공하고자 할때 유용한 패턴이다.
- 데이터베이스에 따라 DAO클래스가 달라져야 한다고 할 때, 현재 사용해야 하는 DB의 종류에 따른 DAO 인스턴스를 한꺼번에 생성하도록 하고자 한다.
- 위젯을 생성하여 보여줄 때 선택한 옵션에 따라 위젯의 set이 달라질 수 있다.
구체적인 클래스를 생성하지 않고
도 서로 관련성이 있거나 독립적인 여러 객체의 군을 생성하기 위한 인터페이스를 제공한다.- 추상화된 인터페이스 팩토리를 제공하고 상황에 따라 그에 맞는 인스턴스들이 생성되도록 한다.
- 생성되고 구성되고 표현되는 방식과 무관하게 시스템을 독립적으로 만들고자 할 때 사용하면 좋다.
- 하나 이상의 제품군들 중 하나를 선택하여 시스템을 설정해야 하고 한번 구성한 제품을 다른 것으로 대체할 수 있을 때 사용하면 좋다.
Class diagram
- 다이어그램을 보면, AbstractFactory 인터페이스가 있고, AbstractFactory를 상속받은 2개의 팩토리가 있다.
- 예를들어 ConcreteFactory1 이 mssql 버전, ConcreteFactory2 가 오라클 버전이라고 했을때, 팩토리를 적절하게 선택해서 product 객체들을 다른 버전으로 생성할 수 있다.
- AbstractFactory
- 개념적 제품에 대한 객체를 생성하는 오퍼레이션 인터페이스를 정의한다.
- ConcreteFactory
- 구체적인 제품에 대한 객체를 생성하는 오퍼레이션을 구현한다.
- AbstractProduct
- 개념적 제품 객체에 대한 인터페이스를 정의한다.
- ConcreteProduct
- 구체적으로 팩토리가 생성할 객체를 정의하고, AbstractProduct 가 정의하고 있는 인터페이스를 구현한다.
- Client
- AbstractFactory 와 AbstractProduct 클래스에 선언된 인터페이스를 사용한다.
정리
- 일반적으로 ConcreteFactory 클래스의 인스턴스는 실행 할 때 만들어진다.
- 구체적 팩토리는 어떤 특정 구현을 갖는 제품 객체를 생성한다. 서로 다른 제품 객체를 생성하기 위해서 사용자는 서로 다른 ConcretetFactory 를 사용한다.
- AbstractFactory 는 ConcreteFactory 서브클래스를 통해 필요한 제품 객체를 생성하는 책임을 위임한다.
예제
@Getter
@Setter
public class UserInfo {
private String userId;
private String password;
private String userName;
}
@Getter
@Setter
public class Product {
private String productId;
private String productName;
}
public interface ProductDao {
void insertProduct(Product product);
void updateProduct(Product product);
void deleteProduct(Product product);
}
public interface UserInfoDao {
void insertProduct(UserInfo userInfo);
void updateProduct(UserInfo userInfo);
void deleteProduct(UserInfo userInfo);
}
public class ProductMySqlDao implements ProductDao {
@Override
public void insertProduct(Product product) {
System.out.println("insert into MySql DB productId = " + product.getProductId());
}
@Override
public void updateProduct(Product product) {
System.out.println("update into MySql DB productId = " + product.getProductId());
}
@Override
public void deleteProduct(Product product) {
System.out.println("delete into MySql DB productId = " + product.getProductId());
}
}
public class ProductOracleDao implements ProductDao {
@Override
public void insertProduct(Product product) {
System.out.println("insert into Oracle DB productId = " + product.getProductId());
}
@Override
public void updateProduct(Product product) {
System.out.println("update into Oracle DB productId = " + product.getProductId());
}
@Override
public void deleteProduct(Product product) {
System.out.println("delete into Oracle DB productId = " + product.getProductId());
}
}
public class UserInfoMySqlDao implements UserInfoDao {
@Override
public void insertUserInfo(UserInfo userInfo) {
System.out.println("insert into MySql DB userId = " + userInfo.getUserId());
}
@Override
public void updateUserInfo(UserInfo userInfo) {
System.out.println("update into MySql DB userId = " + userInfo.getUserId());
}
@Override
public void deleteUserInfo(UserInfo userInfo) {
System.out.println("delete into MySql DB userId = " + userInfo.getUserId());
}
}
public class UserInfoOracleDao implements UserInfoDao {
@Override
public void insertUserInfo(UserInfo userInfo) {
System.out.println("insert into Oracle DB userId = " + userInfo.getUserId());
}
@Override
public void updateUserInfo(UserInfo userInfo) {
System.out.println("update into Oracle DB userId = " + userInfo.getUserId());
}
@Override
public void deleteUserInfo(UserInfo userInfo) {
System.out.println("delete into Oracle DB userId = " + userInfo.getUserId());
}
}
public interface DaoFactory {
public UserInfoDao createUserInfoDao();
public ProductDao createProductDao();
}
public class MySqlDaoFactory implements DaoFactory {
@Override
public UserInfoDao createUserInfoDao() {
return new UserInfoMySqlDao();
}
@Override
public ProductDao createProductDao() {
return new ProductMySqlDao();
}
}
public class OracleDaoFactory implements DaoFactory {
@Override
public UserInfoDao createUserInfoDao() {
return new UserInfoOracleDao();
}
@Override
public ProductDao createProductDao() {
return new ProductOracleDao();
}
}
public class WebClient {
public static void main(String[] args) {
FileInputStream fis = new FileInputStream("db.properties");
Properties prop = new Properties();
prop.load(fis);
String dbType = prop.getProperty("DBTYPE");
UserInfo userInfo = new UserInfo();
userInfo.setUserId("11111");
userInfo.setPassword("22222");
userInfo.setUserName("채마스");
Product product = new Product();
product.setProductId("001123231");
product.setProductName("컴퓨터");
DaoFactory daoFactory = null;
if (dbType.equals("MYSQL"))
daoFactory = new MySqlDaoFactory();
else if (dbType.equals("ORACLE"))
daoFactory = new OracleDaoFactory();
else
System.out.println("지원하지 않는 DB");
UserInfoDao userInfoDao = daoFactory.createUserInfoDao();
System.out.println("===UserInfo Transaction===");
userInfoDao.insertUserInfo(userInfo);
userInfoDao.updateUserInfo(userInfo);
userInfoDao.deleteUserInfo(userInfo);
ProductDao productDao = daoFactory.createProductDao();
System.out.println("===Product Transaction===");
productDao.insertProduct(product);
productDao.updateProduct(product);
productDao.deleteProduct(product);
}
}
- 위의 코드를 보면 UserInfoDao, ProductDao 와 같은 구체적인 클래스에 의존하는 것이아니라 DaoFactory 라는 추상 클래스에 의존하는 것을 확인할 수 있다.
Builder Pattern
Builder Pattern 이란?
- Effective Java 에서 사용하는 Builder Pattern 과는 다른 개념이다.
- 메서드의 조합으로 결과물을 생성하는 방법이라고 할 수 있다.
- 생성에 대한 과정과 각 결과물을 표현하는 방법을 분리하여 동일한 생성 과정에 서로 다른 여러 결과물이 나올 수 있도록 한다.
- 클라이언트 코드는 Builder가 제공하는 메서드를 기반으로 원하는 결과물을 얻을 수 있다.
- 단계별 생성에 중점을 두는 패턴이다.
- 새로운 결과물이 필요한 경우에도 동일한 과정으로 생성을 할 수 있다.
Class diagram
- Director 클래스는 Builder 변수를 가지고 있고 Builder 를 호출한다.
- Builder 는 추상 클래스나 인터페이스로 구성된다.
- Builder
- Product의 각 요소들을 생성하는데 필요한 추상 메서드가 선언된 클래스나 인터페이스다.
- ConcreteBuilder
- Builder에 선언된 메서드를 구현한 클래스다.
- Director
- Builder 인터페이스를 사용하여 Product를 생성한다.
- Product
- 원하는 결과물이다.
정리
생성과정과 구현을 분리
된다.- 제품의
다양한 구현이 가능
하다. - 제품의 생산 과정을 더 세분화 할 수 있다.
클라이언트는 구체적인 사항을 알 필요가 없다.
예시
public interface MakeReport {
public void MakeHeader();
public void MakeBody();
public void MakeFooter();
public String getReport();
}
- 위 인터페이스를 구현하여 TextReport, HTMLReport 등을 생성할 수 있다.
- 해더와 바디와 푸터를 조합하여 생성한다.
예제
- 여러가지 피자를 만드는 방법을 빌더 패턴을 적용하면 아래와 같다.
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE};
final Set<Topping> toppings;
abstract static class Builder {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public Builder addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
public Builder sauceInside() {
return self();
}
abstract Pizza build();
protected abstract Builder self();
}
Pizza(Builder builder){
toppings = builder.toppings.clone();
}
public String toString() {
return toppings.toString();
}
}
- 생성자 대신 위에서 구현한 Builder 라는 inner 클래스를 사용해서 인스턴스를 생성한다.
- Builder 는 원하는 토핑을 추가해서 인스턴스를 생성할 수 있다.
- abstract 메서드로 build() 를 정의함으로써 하위 클래스에게 build() 의 구현을 위임한다.
public class NyPizza extends Pizza{
public enum Size { SMALL, MEDIUM, LARGE};
private final Size size;
public static class Builder extends Pizza.Builder {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
public NyPizza build() {
return new NyPizza(this);
}
protected Builder self() {return this;}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
- 이전에 구현한 Pizza.Builder 를 상속받아 Builder 클래스를 구현한다.
- abstract 메서드인 build() 메서드를 정의한다.
public class Calzone extends Pizza{
private final boolean sauceInside;
public static class Builder extends Pizza.Builder{
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
public Calzone build() {
return new Calzone(this);
}
protected Builder self() {return this;}
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
public String toString() {
return toppings.toString() + " sauceInside: " + sauceInside;
}
}
public class PizzaTest {
public static void main(String[] args) {
Pizza nyPizza = new NyPizza.Builder(Size.SMALL).addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION).build();
Pizza calzone = new Calzone.Builder().addTopping(Topping.HAM).addTopping(Topping.PEPPER)
.sauceInside().build();
}
}
REFERENCES
- 박은종님의 디자인 패턴
'객체지향' 카테고리의 다른 글
프록시 적용하기 (0) | 2022.08.06 |
---|---|
프록시 패턴과 데코레이터 패턴 (0) | 2022.08.06 |
행위 패턴 (디자인 패턴) (0) | 2022.05.15 |
구조패턴 (디자인 패턴) (0) | 2022.05.15 |
클래스 다이어그램 (0) | 2022.04.23 |