본문 바로가기
DesignPattern

[Design Pattern] abstract factory pattern

by 봄석 2019. 9. 17.

[Design Pattern] abstract factory pattern 

 

 

abstract factory pattern(추상팩토리패턴)

추상팩토리 패턴은 많은 수의 연관된 서브 클래스를 특정 그룹으로 묶어 한번에 교체할 수 있도록 만든 디자인패턴입니다.

 

 

추상화(Abstact) + 팩토리(Factory)를 합친 단어입니다.

추상화란 구체화의 반대말로 생각하면 편할것입니다.

실제 코드 부분이 작성되지 않고 어떻게 사용할지 명세(인터페이스라고 한다)만 정의하는 것입니다.



추상팩토리 패턴은 다양한 구성 요소 별로 "객체의  집합"을 생성해야 할 때 유용한 패턴입니다.

다시 말해서, 서로의 다른 객체들을 하나의 팩토리에서 생성과 관리를 한다고 보면됩니다.

하나의 인터페이스에서 객체의 생성을 처리하고, 다양한 성격의 객체를 하나의 군으로 형성해 그것을 객체 단위로 취급하여 생성해야할 때 유용한 패턴입니다.

 

 

추상 팩토리 패턴은 아래와 같은 특징, 장단점이 존재합니다.

 

 

특징 

  1. Templeate Method Pattern을 사용
  2. Factory 클래스를 이용하여 객체 생성
  3. 추상적인 클래스( interface, abstract )를 통해 실제 구현대상인 Concrete(구상클래스,구체클래스)와 Client 간의 결합도를 낮춥니다.
  4. 인자에따라 생성되는 팩토리의 종류가 결정됩니다.
  5. 객체 구성을 활용(제품군)
    1. 객체가 객체를 생성하는 형태
  6. 구상 클래스에 의존하지 않고 관련된 객체들로 이루어진 제품군을만듬
    1. 다양한 구성요소별로  객체의 집합을 생성할때 사용되는 형태

 

 

장점

  1. 객체의 자료형이 하위클래스 의해서 결정됨 -> 확장에 용이함
  2. 동일한 형태로 프로그래밍 가능함
  3. 확장성있는 전체 프로젝트 구성가능함
  4. 구상클래스에 의존하지않고 추상화된 것에 의존한다(디자인원칙 준수)
  5. 팩토리 메소드 패턴의 if-else 로직에서 벗어날 수 있게 해줍니다.

단점

  1. 객체가 늘어날때마다 하위 클래스 재정의로 인한 불필요한 많은 클래스 생성가능성이 있음

 

 

 

휴대폰공장 예로 한번 살펴보도록 하겠습니다.

먼저 안좋은 예입니다.

 

 

 

 

안좋은예( abstract Factory Pattern을 적용하지 않았을 때)

 

 

1. AP

public abstract class AP {
    private Battery battery;

    public AP(Battery battery) {
        this.battery = battery;
    }

    public void process() throws Exception {
        if (battery.getPower() > 0)
            active();
        else
            throw new Exception("battry is out");
    }

    protected abstract void active();
}
public class APFactory {
    static AP createAP(Vendor vendorId, Battery battery) throws Exception {
        if (vendorId == Vendor.Apple) return new IPhone(battery);
        else if (vendorId == Vendor.Samsung) return new GalaxyNote(battery);
        else if (vendorId == Vendor.LG) return new LGV40(battery);
        else throw new Exception("Not Support Venders AP");
    }
}
public class GaluxyNote extends AP {

    public GaluxyNote(Battery battery) {
        super(battery);
    }

    @Override
    protected void active() {
        System.out.println("GaluxyNote Phone Active");
    }
}
public class IPhone extends AP {

    public IPhone(Battery battery) {
        super(battery);
    }

    @Override
    protected void active() {
        System.out.println("IPhone Active");

    }
}
public class LGV40 extends AP {

    public LGV40(Battery battery) {
        super(battery);
    }

    @Override
    protected void active() {
        System.out.println("LG Phone Active");
    }
}

APFactory 클래스에서는 venderID 와 Battery를 받아 AP(GaluxyNote,IPhone,LGV40) 을 리턴하고 있습니다.

여기서  if와  else  구문을 사용하여 식별합니다 .

 

2 Battery

public abstract class Battery {
    protected abstract int getPower();
}
public class BatteryFactory {
    static Battery createBattery(Vendor vendorId) throws Exception {
        if (vendorId == Vendor.Samsung) return new SamsungBattery();
        else if (vendorId == Vendor.LG) return new LGBattery();
        else if (vendorId == Vendor.Apple) return new AppleBattery();
        else throw new Exception("Not Support Vendoers Battery");
    }
}
public class SamsungBattery extends Battery {
    @Override
    protected int getPower() {
        System.out.println("spent energy of samsung battery");
        return 5;
    }
}
public class LGBattery extends Battery {
    @Override
    protected int getPower() {
        System.out.println("spent energy of lg battery");
        return 10;
    }
}
public class AppleBattery extends Battery {
    @Override
    protected int getPower() {
        System.out.println("spent energy of apple battery");
        return 8;
    }
}

BatteryFactory 클래스에서도 마찬가지로 

여기서  if와  else  구문을 사용하여 식별하고 있는 것을 볼 수있습니다.

 

위에서의 factory클래스의 문제점은 무엇인지 생각해 보겠습니다.

좋은 코드는 변화에 유연합니다. 유연하다는 것은 최소한의 수정으로 요구사항을 반영할 수 있어야 한다는 것을 의미합니다.

만약 새로운 종류의 스마트폰이 Huawei가 추가되었다고 가정해 보겠습니다.

그렇다면 어떻게 해야할까요 ?

그렇다면 위의 facotry클래스의 createAP, createBattery 함수는 새로운 스마트폰 종류에 맞춰 분기를 추가해야 할 것입니다.

 

즉 , 문제는 인스턴스를 타입에 따라 생성해야 하기 때문에 

분기문이 들어가고 의존성이 생기게 된다는 것입니다.

 

하나만 추가하는 것은 어렵지 않습니다. 중요한 것은 커피의 종류가 얼마나 많이 추가될지 모른다는 것입니다.

그때마다 if문을 추가해준다면 createAP(), createBattery() 함수는 매우 비대해질 것입니다,

 

 


 

그렇다면 해결방안은 ??

그렇다면 인스턴스를 바깥에서 만든다면? 

만든다는 행위만 정의 한다면 분기문을 제거할 수 있지 않을까?

 행위를 정의하는 생각이 바로 추상화입니다.

 


 

 

 

좋은예( abstract Factory Pattern을 적용하였을때)

 

사실 아이폰, 갤럭시 또는 다른 스마트폰의 조립 과정이나 필요한 재료들은 일률적이며, 이들의 조립과정도 모두 같습니다.

각각의 클라이언트는 각각의 재료에 대한 팩토리가 아닌 완성되는 제품에 대한 재료 팩토리를 가지면 되는 것 아닐까요?

아래와 같은 코드처럼 말입니다.

 

 

1. AP

public interface AP {
    public void process() throws Exception;
    public void active();
}
public class GaluxyNote implements AP {
    private Battery battery;

    public GaluxyNote(Battery battery) {
        this.battery = battery;
    }

    @Override
    public void process() throws Exception {
        if (battery.getPower() > 0)
            active();
        else
            throw new Exception("battery is out");
    }

    @Override
    public void active() {
        System.out.println("GaluxyNote Phone Active");
    }
}
public class IPhone implements AP {
    private Battery battery;

    public IPhone(Battery battery) {
        this.battery = battery;
    }

    @Override
    public void process() throws Exception {
        if (battery.getPower() > 0)
            active();
        else
            throw new Exception("battery is out");
    }

    @Override
    public void active() {
        System.out.println("IPhone Active");

    }
}
public class LGV40 implements AP {
    private Battery battery;

    public LGV40(Battery battery) {
        this.battery = battery;
    }

    @Override
    public void process() throws Exception {
        if (battery.getPower() > 0)
            active();
        else
            throw new Exception("battery is out");
    }

    @Override
    public void active() {
        System.out.println("LG Phone Active");
    }
}

 

 

2. Battery

public interface Battery {
    public int getPower();
}
public class SamsungBattery implements Battery {
    @Override
    public int getPower() {
        System.out.println("spent energy of samsung battery");
        return 10;
    }
}
public class AppleBattery implements Battery {
    @Override
    public int getPower() {
        return 8;
    }
}
public class LGBattery implements Battery {
    @Override
    public int getPower() {
        System.out.println("spent energy of lg battery");
        return 10;
    }
}

 

3. Factory

SmartPhoneFactory인스턴스를 만드는 행위만 정의(추상화) 해놓고, 행위에 대한 구현은 세부적인 팩토리들을 만들어서

createAp(),createBattery()라는 공통적인 메서드를 이용하여 생성하도록 정의합니다.

정리하면 추상 팩토리는 인스턴스의 생성을 서브클래스에게 위임함으로써 의존성을 낮춘다. 

public interface SmartPhoneFactory {
    public AP createAP(Battery battery);
    public Battery createBattery();
}
public class GaluxyFactory implements SmartPhoneFactory {
    @Override
    public AP createAP(Battery battery) {
        return new GaluxyNote(battery);
    }

    @Override
    public Battery createBattery() {
        return new SamsungBattery();
    }
}
public class IPhoneFactory implements SmartPhoneFactory {
    @Override
    public AP createAP(Battery battery) {
        return new IPhone(battery);
    }

    @Override
    public Battery createBattery() {
        return new AppleBattery();
    }
}
public class V40Factory implements SmartPhoneFactory {
    @Override
    public AP createAP(Battery battery) {
        return new LGV40(battery);
    }

    @Override
    public Battery createBattery() {
        return new LGBattery();
    }
}

 

 

4. Client

또한 클라이언트에서 해당 팩토리의 인스턴스를 외부에서 주입하도록 설계한다면,

의존성을 클라이언트와 구현팩토리간의 직접의존성을 완전히 끊을 수 있게 됩니다. 

 

이렇게 외부에서 의존성을 넘겨주는 방법을 의존성 주입(Dependency Injection)라 부릅니다.

public class ClientFactory {
    private SmartPhoneFactory factory;

    public ClientFactory(SmartPhoneFactory factory) {
        this.factory = factory;
    }

    public void createPhone() throws Exception {
        factory.createAP(factory.createBattery()).process();
    }
}
public class Client {
    public static void main(String[] args) throws Exception {
        ClientFactory client1 =new ClientFactory(new GalaxyFactory());
        client1.createPhone();

        ClientFactory client2 =new ClientFactory(new IPhoneFactory());
        client2.createPhone();

        ClientFactory client3 =new ClientFactory(new V40Factory());
        client3.createPhone();
    }
}

 




전체 sample code 보러가기

https://github.com/qjatjr1108/DesignPattern

 

qjatjr1108/DesignPattern

DesignPattern Sample. Contribute to qjatjr1108/DesignPattern development by creating an account on GitHub.

github.com

 

 

 

용어정리

· AbstractFactory : 개념적 제품에 대한 객체를 생성하는 연산으로 인터페이스를 정의함.

· ConcreteFactory : 구체적인 제품에 대한 객체를 생성하는 연산을 구현함.

· AbstractProduct : 개념적 제품 객체에 대한 인터페이스를 정의함.

· ConcreteProduct : 구체적으로 팩토리가 생성할 객체를 정의하고, AbstractProduct가 정의하는 인터페이스를 구현함.

· Client : AbsctractFactory와 AbstractProduct 클래스에 선언된 인터페이스를 사용함.

 

댓글