객체를 생성하는 것과 관련된 패턴으로, 객체의 생성과 변경이 전체 시스템에 미치는 영향을 최소화 하고, 코드의 유연성을 높여 준다.
종류
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 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();
}
}