はじめに
今回はFactoryパターンというデザインパターンを見ていきます.
GoFデザインパターンのオブジェクト生成に関するものとなります.
Factoryパターンとは?
Factoryパターンは,インスタンス生成の責任を専用のクラス(Factory)やメソッドに委ねるパターンとなります.
抽象インタフェースだけを利用して具体的なクラスのインスタンスを生成することを可能にするので,開発途上の具体的なクラスが不安定なときに役立ちます.
このパターンを用いることによって,具体的なクラスに依存するのを避けることができます.
(コードの依存性の削減により,柔軟性が向上します)
Factoryパターンの実装
もともと以下のような構造の作図アプリケーションがあったとします.
この図を見ると,Appが具体的なクラス(Square, Circle)のインスタンスを作成しています.
コードにすると以下のようになります.
public class App{
public void run(){
Shape shape = new Circle();
shape.draw();
}
}
この場合,AppクラスがCircleという具体的なクラスを直接指定してインスタンスを生成していることになります.
このような,具体的なクラスに依存してしまっているという問題を解決するためにFactoryパターンを用います.
それぞれのソースコードは以下のようになります.
- App.java
ユーザー入力に応じた形状描画ができます.
形状名を入力するたびに適切な形状がファクトリを通じて生成されます.
import java.util.Scanner;
public class App {
private final ShapeFactory shapeFactory;
public App(ShapeFactory shapeFactory) {
this.shapeFactory = shapeFactory;
}
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("Enter the shape to draw (square, circle) or 'exit' to quit:");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) {
System.out.println("Exiting application.");
break;
}
try {
Shape shape = shapeFactory.makeShape(input);
shape.draw();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
scanner.close();
}
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactoryImplementation();
App app = new App(factory);
app.run();
}
}
- Shape.java
Shapeのインタフェースとなっており,すべての形状の共通メソッドを定義します.
public interface Shape {
void draw();
}
- Square.java
Shapeインタフェースを実装し,Squareの描画方法を定義します.
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
- Circle.java
Shapeインタフェースを実装し,Circleの描画方法を定義します.
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
- ShapeFactory.java
ファクトリの役割を果たし,形状を生成するメソッドを定義します.
インスタンス生成の責任を果たします.
public interface ShapeFactory {
Shape makeSquare();
Shape makeCircle();
}
- ShapeFactoryImplementation.java
ShapeFactoryを実装し,具体的な形状を生成します.
public class ShapeFactoryImplementation implements ShapeFactory {
@Override
public Shape makeShape(String shapeName) {
if (shapeName.equals("Square")) {
return new Square();
} else if (shapeName.equals("Circle")) {
return new Circle();
}
throw new IllegalArgumentException("Unknown shape name: " + shapeName);
}
}
このようにFactoryパターンを使うことで,
Appは具体的なクラスに依存することがなくなりました.
これにより,Appは何の工場かを指定するだけで形状を生成できますし,何の教材が生成されるかを意識する必要もありません.
そして,新たに'Triangle'の図形を追加したいとなったときには,Appを変更することなくソースコードを追加することができます.
// App.java
import java.util.Scanner;
public class App {
private final ShapeFactory shapeFactory;
public App(ShapeFactory shapeFactory) {
this.shapeFactory = shapeFactory;
}
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("Enter the shape to draw (square, circle, triangle) or 'exit' to quit:");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) {
System.out.println("Exiting application.");
break;
}
try {
Shape shape = shapeFactory.makeShape(input);
shape.draw();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
scanner.close();
}
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactoryImplementation();
App app = new App(factory);
app.run();
}
}
// Shape.java
public interface Shape {
void draw();
}
// Square.java
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
// Circle.java
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
// Triangle.java
public class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a triangle");
}
}
// ShapeFactory.java
public interface ShapeFactory {
Shape makeShape(String shapeName);
}
// ShapeFactoryImplementation.java
public class ShapeFactoryImplementation implements ShapeFactory {
@Override
public Shape makeShape(String shapeName) {
if ("square".equalsIgnoreCase(shapeName)) {
return new Square();
} else if ("circle".equalsIgnoreCase(shapeName)) {
return new Circle();
} else if ("triangle".equalsIgnoreCase(shapeName)) {
return new Triangle();
}
throw new IllegalArgumentException("Unknown shape name: " + shapeName);
}
}
Factoryパターンのデメリット
Factoryパターンの実装などを見てきましたが,もちろんデメリットもあります.
それは,設計の拡張の困難さや,ソースコードを書く量が多くなってしまうことです.
新しいクラスを1つ作るのに,新しいクラスを4つ作らなければならないこともありえます.
その内訳は,新しいクラスを表現するインタフェースとそのFactoryを表現するインタフェース,さらにその2つのインタフェースを実装する具体的なクラスの2つとなります.
クラスが増えればもちろん,その分ソースコードを書く量も多くなります.
Factoryパターンのデメリットはこのような点が考えられます.
最後に
Factoryパターンを見てみましたが,メインのファイルが具体的なクラスに依存することなく
設計をすることの重要性がわかったと思います.
また,抽象クラスの役割などの理解も深めることができました.
参考