Factory Parttern이란?
Factory Pattern은 디자인 패턴의 종류 중 생성 디자인 패턴에 속한다.
객체 생성 로직을 추상화해서 하위 클래스에서 객체 생성에 대해 구체적인 내용을 결정하는 디자인 패턴이다.
특징:
객체를 직접 생성하는 대신, 팩토리 클래스를 통해 객체를 생성함으로써 객체 생성 과정에서 발생할 수 있는 의존성을 줄이고, 코드의 유지보수성을 높일 수 있음.
Factory Pattern을 쓰는 이유(==기존 개발 형태에 대한 문제점)?
왜 쓰는지에 대한 관점을 Factory Pattern의 효과 및 장점이 아닌
Factory Pattern을 쓰지 않았던 기존 개발 방식을 사용한다면 어떤 문제점(혹은 불편함)이 발생하는지 예시로 보겠다.
<기존 개발의 문제점>
1. 객체 생성 코드의 중복
2. 코드 확장성 부족
3. 의존성 증가
shape라는 인터페이스가 존재하고 해당 인터페이스를 통해 원형 클래스(Circle)과 사각형(Squre) 클래스를 구현한다고 해보자.
// Shape 인터페이스
public interface Shape {
void draw();
}
// Circle 클래스
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
// Square 클래스
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Square");
}
}
1. 객체 생성 코드의 중복
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
circle.draw();
Shape square = new Square();
square.draw();
}
}
기존 개발 방식에서 객체를 생성할 때마다 생성로직을 반복하게 되는데 이것은 코드 중복이고 유지보수가 어려워진다.
객체 생성 로직이 여러 곳에 분산되어있으면, 객체 생성 방법이 변경될때마다 모든 곳을 수정해야한다.
예를들어 크기가 동일한 원형, 사각형을 만들고 싶어서 생성할때 넓이 값을 변수로 받는 생성 방법으로 변경한다고 생각해보자. 그럼 우리는 생성 로직이 있는 곳마다 변경해야한다.
2. 코드 확장성 부족
우리가 유동적으로 Shape 인터페이스로 먼저 생성해놓고 유동적으로 String으로 모양이름을 받은 뒤 생성하는 코드를 만들자고 해보자.
기존 원형과 사각형 클래스 단 2개만 존재할 때 다음과 같을 것이다.
public class Main {
public static void main(String[] args) {
Shape shape;
String shapeType = "CIRCLE";
if (shapeType.equals("CIRCLE")) {
shape = new Circle();
} else if (shapeType.equals("SQUARE")) {
shape = new Square();
} else {
shape = null;
}
if (shape != null) {
shape.draw();
}
}
}
여기에 삼각형 클래스를 추가 구현해놨다고 생각해보자.
public class Main {
public static void main(String[] args) {
Shape shape;
String shapeType = "CIRCLE";
if (shapeType.equals("CIRCLE")) {
shape = new Circle();
} else if (shapeType.equals("SQUARE")) {
shape = new Square();
} else if (shapeType.equals("TRIANGLE")) {
shape = new Triangle();
} else {
shape = null;
}
if (shape != null) {
shape.draw();
}
}
}
만일 다른 모양들이 생겨나면? 계속 else if 로 추가해야하는 것이다.
이 점은 SOLID 5대 설계 법칙 중 OCP에도 해당되는 데 확장에 전혀 열려있지 않고 확장하려면 계속 코드를 수정 해야한다.
3. 의존성 증가
객체를 직접 생성하면 클래스 간의 의존성이 증가한다.
이것은 코드의 결합도를 높이고 SOLID 5대 설계 원칙 중 DIP을 위배한다.
public class DrawingApp {
private Circle circle;
private Square square;
public DrawingApp() {
this.circle = new Circle();
this.square = new Square();
}
public void drawShapes() {
circle.draw();
square.draw();
}
public static void main(String[] args) {
DrawingApp app = new DrawingApp();
app.drawShapes();
}
}
너무나 구체적인 클래스인 Circle과 Square을 직접적으로 의존하고있다.
아까처럼 삼각형을 추가하려면? 우리는 DrawingApp 클래스를 수정해야한다.
Factory Pattern의 효과 및 장점의 관점으로 Factory Pattern을 쓰는 이유는 다음과 같다.
<Factory Pattern 사용의 효과>
1. 객체 생성 코드의 캡슐화
2. 코드의 유연성과 확장성
3. 의존성 감소
1. 객체 생성 코드의 캡슐화
객체 생성 로직을 한 곳에 모아서 관리할 수 있어. 이를 통해 코드의 가독성과 유지보수성이 향상된다.
2. 코드의 유연성과 확장성
새로운 객체 타입을 추가할 때 기존 코드를 수정하지 않고 확장할 수 있다.
3. 의존성 감소
객체 생성과 관련된 의존성을 줄일 수 있어, 특히 인터페이스를 통해 객체를 생성하면 코드의 결합도가 낮아진다.
그럼 아까 봤던 문제되는 상황을 팩토리 패턴으로 수정해보자.
// ShapeFactory 클래스
public class ShapeFactory {
public Shape createShape(String type) {
if (type == null) {
return null;
}
if (type.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (type.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
// DrawingApp 클래스
public class DrawingApp {
private ShapeFactory shapeFactory;
public DrawingApp(ShapeFactory shapeFactory) {
this.shapeFactory = shapeFactory;
}
public void drawShapes() {
Shape circle = shapeFactory.createShape("CIRCLE");
if (circle != null) {
circle.draw();
}
Shape square = shapeFactory.createShape("SQUARE");
if (square != null) {
square.draw();
}
}
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
DrawingApp app = new DrawingApp(shapeFactory);
app.drawShapes();
}
}
ShapeFactory 클래스에 캡슐화되어 중복이 제거되고, DrawingApp 클래스는 ShapeFactory를 통해 객체를 생성한다.
Shape를 추가할 때 ShapeFactory 클래스만 수정하면 되고 DrawingApp 클래스는 수정하지 않아도 된다.
DrawingApp 클래스는 ShapeFactory를 통해 객체를 생성하므로, 특정 Shape 클래스에 대한 의존성이 줄어들어 결합도가 낮아진다.
Factory Pattern(팩토리 패턴) 구현 방법
1. Simple Factory
2. Factory Method
3. Abstract Factory
1. Simple Factory
// Shape 인터페이스
public interface Shape {
void draw();
}
// Circle 클래스
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
// Square 클래스
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Square");
}
}
// ShapeFactory 클래스
public class ShapeFactory {
public Shape createShape(String type) {
if (type == null) {
return null;
}
if (type.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (type.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.createShape("CIRCLE");
shape1.draw();
Shape shape2 = shapeFactory.createShape("SQUARE");
shape2.draw();
}
}
사실 Simple Factory는 패턴이라기보단 관용구에 가깝다.
단순히 생성하는 부분을 하나의 클래스(Simple Factory)에 모아놓은 것이다.
2. Factory Method (추상 메서드 방법)
// Shape 인터페이스
public interface Shape {
void draw();
}
// Circle 클래스
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
// Square 클래스
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Square");
}
}
// ShapeFactory 추상 클래스
public abstract class ShapeFactory {
public abstract Shape createShape();
public void drawShape() {
Shape shape = createShape();
shape.draw();
}
}
// CircleFactory 클래스
public class CircleFactory extends ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
// SquareFactory 클래스
public class SquareFactory extends ShapeFactory {
@Override
public Shape createShape() {
return new Square();
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
ShapeFactory circleFactory = new CircleFactory();
circleFactory.drawShape();
ShapeFactory squareFactory = new SquareFactory();
squareFactory.drawShape();
}
}
구체적인 클래스의 인스턴스를 생성하기 위해 서브클래스를 통해 팩토리 메서드를 구현하는 방법.
위 코드에서는 CircleFactory와 SquareFactory가 ShapeFactory를 상속받아 createShape 메서드를 구현했다.
3. Abstract Factory (추상 팩토리 방법)
// Shape 인터페이스
public interface Shape {
void draw();
}
// Circle 클래스
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle");
}
}
// Square 클래스
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Square");
}
}
public class RoundedRectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rounded Rectangle");
}
}
public class RoundedSquare implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rounded Square");
}
}
public interface AbstractFactory {
Shape createShape(String shapeType);
}
public class ShapeFactory implements AbstractFactory {
@Override
public Shape createShape(String shapeType) {
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
public class FactoryProducer {
public static AbstractFactory getFactory(boolean rounded) {
if (rounded) {
return new RoundedShapeFactory();
} else {
return new ShapeFactory();
}
}
}
public class Main {
public static void main(String[] args) {
// 일반 Shape 팩토리
AbstractFactory shapeFactory = FactoryProducer.getFactory(false);
Shape shape1 = shapeFactory.createShape("CIRCLE");
shape1.draw(); // Drawing a Circle
Shape shape2 = shapeFactory.createShape("SQUARE");
shape2.draw(); // Drawing a Square
// Rounded Shape 팩토리
AbstractFactory roundedShapeFactory = FactoryProducer.getFactory(true);
Shape shape3 = roundedShapeFactory.createShape("RECTANGLE");
shape3.draw(); // Drawing a Rounded Rectangle
Shape shape4 = roundedShapeFactory.createShape("SQUARE");
shape4.draw(); // Drawing a Rounded Square
}
}
관련된 객체 군을 생성하는 팩토리를 제공하는 방법
위 코드에서는 ShapeFactory와 RoundedShapeFactory가 각각 AbstractFactory를 구현하여 여러 객체를 생성한다.
FactoryProducer 클래스가 AbstractFactory 를 구현한 구체적인 팩토리를 생성하는 역할을 한다.
Factory Pattern의 장단점
장점
객체 생성 로직의 캡슐화: 객체 생성 로직을 별도의 팩토리 클래스에 캡슐화하여 코드의 가독성과 유지보수성을 향상시킴.
확장성: 새로운 객체 타입을 쉽게 추가할 수 있어. 기존 코드를 수정하지 않고 새로운 팩토리 클래스를 추가하면 됨.
의존성 감소: 객체 생성과 관련된 의존성을 줄일 수 있어, 특히 인터페이스를 통해 객체를 생성하면 코드의 결합도가 낮아짐.
단점
클래스의 수 증가: 팩토리 클래스와 객체 클래스가 별도로 존재하므로, 클래스의 수가 증가할 수 있음.
복잡성 증가: 객체 생성 로직이 단순한 경우, 팩토리 패턴을 적용하면 오히려 코드의 복잡성이 증가할 수 있음.
'CS > 디자인패턴' 카테고리의 다른 글
[Design Pattern] Proxy Pattern(프록시 패턴) (0) | 2024.06.03 |
---|---|
[Design Pattern] Strategy Pattern(전략 패턴) (0) | 2024.06.02 |
[Design Pattern] Singleton Pattern(싱글톤 패턴) (0) | 2024.05.31 |
[Design Pattern] Builder Pattern(빌더 패턴) (1) | 2024.02.07 |
[Design Pattern] Observer pattern(옵저버 패턴) (0) | 2024.01.24 |