Factory
Factory 패턴이란?
Factory 패턴은 객체 생성의 책임을 별도의 클래스로 분리하여, 객체 생성 로직을 캡슐화하고 유연성을 높이는 디자인 패턴입니다. 객체를 생성할 때 구체적인 클래스에 의존하지 않고, 인터페이스나 추상 클래스를 통해 객체를 생성함으로써 결합도를 낮추고 유지보수를 용이하게 합니다.
Factory 패턴의 특징
- 객체 생성의 캡슐화: 객체 생성 로직을 분리하여, 클라이언트가 구체적인 클래스에 의존하지 않고 필요한 객체를 생성할 수 있습니다.
- 유연성 증가: 다양한 객체 생성 요구 사항에 대응할 수 있으며, 객체 생성 방식이 변경되더라도 클라이언트 코드에 영향을 미치지 않습니다.
- 확장성: 새로운 객체 타입을 추가하거나 생성 방식에 변화를 줄 때, 기존 코드를 수정하지 않고 팩토리 클래스를 확장함으로써 유연하게 대처할 수 있습니다.
Factory 패턴 구조
- Client → Creator
- 클라이언트는
Creator
의CreateProduct()
메서드를 호출하여 제품 생성을 요청.
- 클라이언트는
- Creator → ConcreteProduct
Creator
는 요청에 따라 구체적인 제품(ConcreteProductA
또는ConcreteProductB
)을 생성.
- Creator → Client
- 생성된 제품을
Creator
가 클라이언트에게 반환.
- 생성된 제품을
Factory 패턴 적용
Factory 패턴의 필요성
객체 생성 과정이 복잡하거나, 특정 객체가 자주 생성되고 그 과정이 반복되는 경우, 객체 생성을 관리하는 코드가 복잡해질 수 있습니다. 이때, Factory 패턴
을 적용하면 객체 생성 로직을 한곳에 집중시켜 관리할 수 있으며, 객체 생성과 관련된 코드를 수정할 필요 없이 새로운 객체를 쉽게 생성할 수 있습니다.
잘못된 처리
public class CarService
{
public Car CreateCar(string type)
{
if (type == "SUV")
return new SUV();
else if (type == "Sedan")
return new Sedan();
else
throw new ArgumentException("Invalid car type");
}
}
- 유연성 부족: 새로운 자동차 타입이 추가될 때마다 기존 코드를 수정해야 합니다. 이는 코드의 유지보수성을 떨어뜨립니다.
- 결합도 증가: 클래스가 구체적인 객체 타입에 직접 의존하므로, 객체 생성 방식이 변경되면 관련된 모든 코드에 영향을 미칩니다.
Factory 패턴 적용 예시
public interface ICar
{
void Drive();
}
public class SUV : ICar
{
public void Drive() => Console.WriteLine("Driving an SUV");
}
public class Sedan : ICar
{
public void Drive() => Console.WriteLine("Driving a Sedan");
}
public class CarFactory
{
public ICar CreateCar(string type)
{
if (type == "SUV")
return new SUV();
else if (type == "Sedan")
return new Sedan();
else
throw new ArgumentException("Invalid car type");
}
}
- 객체 생성 로직의 캡슐화: 객체 생성 로직을
CarFactory
클래스로 분리하여, 클라이언트 코드에서는 구체적인 객체 타입에 의존하지 않고도 객체를 생성할 수 있습니다. - 유연성 및 확장성 향상: 새로운 자동차 타입이 추가될 때에도
CarFactory
클래스만 수정하면 되므로, 기존 코드에 영향을 미치지 않고 확장할 수 있습니다.
Factory 패턴 구성 요소
- 추상화된 인터페이스 (ICar): 구체적인 객체(SUV, Sedan)들이 구현하는 인터페이스로, 객체 생성 후 클라이언트가 사용하는 일관된 인터페이스입니다.
- 팩토리 클래스 (CarFactory): 객체 생성 로직을 담당하며, 요청에 따라 적절한 구체 클래스를 반환합니다. 팩토리 클래스는 객체 생성의 복잡성을 감추고, 클라이언트에게는 객체 타입에 상관없이 일관된 인터페이스만 제공합니다.
Factory 패턴의 장단점
장점
- 유연성: 클라이언트는 객체의 구체적인 클래스를 알 필요 없이, 팩토리를 통해 필요한 객체를 생성할 수 있습니다. 새로운 객체 타입이 추가되더라도 클라이언트 코드를 수정할 필요가 없습니다.
- 결합도 감소: 객체 생성 로직을 분리함으로써, 객체를 사용하는 코드와 생성하는 코드 간의 결합도를 줄입니다.
- 유지보수성: 객체 생성 방식이 변경되더라도, 해당 팩토리 클래스만 수정하면 되기 때문에 유지보수성이 향상됩니다.
단점
- 복잡성 증가: 단순한 객체 생성에는 불필요하게 복잡해질 수 있으며, 추가적인 클래스와 코드가 요구됩니다.
- 오버헤드: 객체 생성 로직이 매우 간단할 때는 팩토리 패턴을 사용하지 않고 객체를 직접 생성하는 것이 더 효율적일 수 있습니다.
맺음말
Factory 패턴
은 객체 생성의 복잡성을 줄이고, 유연성과 유지보수성을 높이는 중요한 디자인 패턴입니다. 객체 생성이 자주 일어나고 그 로직이 복잡한 애플리케이션에서 특히 유용하며, 코드의 결합도를 줄여 확장성과 재사용성을 높일 수 있습니다.
심화 학습
Factory 와 Abstract Factory, Factory Method 의 비교
Factory 패턴
은 객체 생성의 기본적인 구조를 제공하며, 이를 바탕으로 Abstract Factory
와 Factory Method
패턴은 각각의 목적에 맞춘 확장된 형태로 설계되었습니다. 이 세 패턴은 모두 객체 생성을 관리하지만, 사용 시나리오와 복잡도에 따라 차이점이 존재합니다.
Factory 패턴과 Abstract Factory 패턴의 차이
- Factory 패턴은 주로 하나의 객체를 생성하는 데 사용되며, 구체적인 클래스에 의존하지 않고 인터페이스를 통해 객체를 생성합니다.
- Abstract Factory 패턴은 연관된 객체들을 그룹으로 묶어 생성하는 패턴입니다. 예를 들어, 특정 테마의 UI 요소들을 일관되게 생성할 때 유용하며, 여러 종류의 객체가 함께 사용되는 경우에 적합합니다. Abstract Factory는 팩토리 클래스 자체를 생성하는 패턴으로, 팩토리들의 그룹(Factory of Factories)을 제공합니다.
// Factory 패턴 - 하나의 객체 생성
public class CarFactory
{
public ICar CreateCar(string type)
{
if (type == "SUV")
return new SUV();
else if (type == "Sedan")
return new Sedan();
else
throw new ArgumentException("Invalid car type");
}
}
// Abstract Factory 패턴 - 연관된 객체들을 생성
public abstract class CarFactory
{
public abstract ICar CreateCar();
public abstract IEngine CreateEngine();
}
public class SUVFactory : CarFactory
{
public override ICar CreateCar() => new SUV();
public override IEngine CreateEngine() => new SUVEngine();
}
public class SedanFactory : CarFactory
{
public override ICar CreateCar() => new Sedan();
public override IEngine CreateEngine() => new SedanEngine();
}
Factory 패턴과 Factory Method 패턴의 차이
- Factory 패턴은 팩토리 클래스가 객체 생성을 담당하며, 클라이언트는 객체 생성 방식에 관여하지 않습니다. 팩토리 메서드는 팩토리 클래스를 통해 객체가 생성되므로 구체적인 클래스에 대한 의존성을 줄입니다.
- Factory Method 패턴은 객체 생성 방식을 서브클래스가 결정하도록 만들어, 상속을 통해 객체 생성 방식을 변경하거나 확장할 수 있는 구조입니다. 즉, 서브클래스가 구체적인 객체 생성을 책임지므로, 새로운 객체를 쉽게 확장하거나 변경할 수 있습니다.
// Factory 패턴
public class SimpleCarFactory
{
public Car CreateCar(string type)
{
if (type == "Sedan")
return new Sedan();
else
return new SUV();
}
}
// Factory Method 패턴
public abstract class Car
{
public abstract void Drive();
}
public abstract class CarFactory
{
public abstract Car CreateCar();
}
public class SedanFactory : CarFactory
{
public override Car CreateCar() => new Sedan();
}
public class SUVFactory : CarFactory
{
public override Car CreateCar() => new SUV();
}
Factory와 Singleton
Singleton 패턴은 클래스의 인스턴스를 하나로 제한하고, 전역적으로 접근할 수 있도록 하는 패턴입니다. Factory 패턴
과 결합하여 하나의 Factory 인스턴스를 통해 객체를 생성하도록 하면, 여러 곳에서 동일한 Factory 인스턴스를 사용할 수 있습니다. 이는 Factory가 많은 리소스를 사용하거나, 생성 비용이 높은 경우 유용합니다.
public class SingletonCarFactory
{
private static SingletonCarFactory _instance;
private SingletonCarFactory() { }
public static SingletonCarFactory Instance
{
get
{
if (_instance == null)
{
_instance = new SingletonCarFactory();
}
return _instance;
}
}
public ICar CreateCar(string type)
{
if (type == "SUV")
return new SUV();
else if (type == "Sedan")
return new Sedan();
else
throw new ArgumentException("Invalid car type");
}
}
이렇게 하면 SingletonCarFactory
는 애플리케이션 내에서 하나만 생성되며, 모든 객체 생성을 동일한 인스턴스에서 관리할 수 있습니다.
Factory와 Prototype
Prototype 패턴은 객체를 새로 생성하는 대신, 기존 객체를 복제하여 새로운 객체를 만드는 패턴입니다. 이 패턴은 복잡한 객체를 생성하는 데 유용하며, Factory 패턴
과 결합하여 새로운 객체를 생성할 때 복제 방식을 사용할 수 있습니다.
public interface ICar : ICloneable
{
void Drive();
}
public class Sedan : ICar
{
public object Clone() => this.MemberwiseClone();
public void Drive() => Console.WriteLine("Driving a Sedan");
}
public class CarFactory
{
private readonly ICar _prototype;
public CarFactory(ICar prototype)
{
_prototype = prototype;
}
public ICar CreateCar() => (ICar)_prototype.Clone();
}
여기서 CarFactory
는 기존 객체를 복제하여 새로운 객체를 반환합니다. 이를 통해 객체 생성 비용을 줄이고, 복잡한 객체를 효율적으로 관리할 수 있습니다.
Factory와 Object Pool
Object Pool 패턴은 성능을 향상시키기 위해 미리 생성된 객체들을 풀Pool로 관리하고, 필요할 때 객체를 재사용하는 패턴입니다. Factory 패턴
과 결합하면, 객체 생성의 부담을 줄이면서 객체 재사용을 통해 성능을 최적화 할 수 있습니다.
public class ObjectPool
{
private readonly List<ICar> _availableCars = new List<ICar>();
public ICar GetCar()
{
if (_availableCars.Count > 0)
{
var car = _availableCars[0];
_availableCars.RemoveAt(0);
return car;
}
else
{
return new Sedan(); // 새로운 객체 생성
}
}
public void ReleaseCar(ICar car)
{
_availableCars.Add(car);
}
}
ObjectPool
클래스는 ICar
객체를 관리하며, 새로운 객체 생성 비용을 줄이고 이미 생성된 객체를 재사용할 수 있게 해줍니다.
Factory와 Builder
Builder 패턴은 복잡한 객체를 단계별로 생성할 수 있도록 하는 패턴입니다. Factory 패턴
과 결합하면, 객체 생성의 유연성을 제공하면서 복잡한 초기화 로직을 캡슐화할 수 있습니다.
public class CarBuilder
{
private string _engine;
private string _color;
public CarBuilder SetEngine(string engine)
{
_engine = engine;
return this;
}
public CarBuilder SetColor(string color)
{
_color = color;
return this;
}
public ICar Build()
{
return new CustomCar(_engine, _color);
}
}
public class CustomCarFactory
{
public ICar CreateCar(CarBuilder builder)
{
return builder.SetEngine("V8").SetColor("Red").Build();
}
}
여기서 CarBuilder
는 복잡한 CustomCar
객체를 단계별로 생성하며, CustomCarFactory
는 빌더를 사용해 객체 생성 과정을 유연하게 처리합니다.
Factory 와 의존성 주입
Factory 패턴
은 의존성 주입Dependency Injection, DI과 자주 결합됩니다. 의존성 주입은 객체 간의 결합도를 줄여 코드의 유연성과 테스트 가능성을 높이는 방법이며, 팩토리는 주입되는 객체를 생성하는 역할을 합니다. 이는 특히 DI 컨테이너에서 유용하게 사용됩니다. 팩토리를 주입받아 객체를 동적으로 생성할 때, 코드의 의존성을 효과적으로 관리할 수 있습니다.
public class CarService
{
private readonly CarFactory _carFactory;
public CarService(CarFactory carFactory)
{
_carFactory = carFactory;
}
public void Run()
{
var car = _carFactory.CreateCar("SUV");
car.Drive();
}
}
이 구조는 팩토리를 의존성 주입받아 CarService
클래스에서 객체 생성 방식을 유연하게 관리할 수 있도록 합니다.