모르지 않다는 것은 아는것과 다르다.

객체지향

생성 패턴 (디자인 패턴)

채마스 2022. 5. 15. 01:29

생성패턴

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