Java工厂方法

4741 字
12 分钟

每日一言

没有什么是完美的,这个世界并不完美,所以才显得美丽。——《钢之炼金术师》

什么是工厂方法(Factory Method)

Java中的工厂方法是一种创建对象的设计模式,属于创建模式之一。工厂方法通过定义一个创建对象的接口,让子类决定实例化哪一个类。这样做的好处是将对象的创建过程封装起来,使得客户端代码不需要知道具体的类,而是通过工厂方法来获取所需的对象。

主要特点:

  • 定义接口:工厂方法定义了创建对象的接口,但将实际创建对象的工作交给子类去完成。
  • 子类决定对象:通过继承工厂类并实现工厂方法,子类可以决定具体创建哪个类的实例。
  • 解耦对象创建:客户端不需要关心具体的类,只需要通过工厂方法来创建对象,从而实现了代码的解耦和扩展性。

如何写好一个工厂方法

第一步:定义产品的抽象接口或抽象类

产品是由工厂创建的对象(实例),并且他们都实现了同一个抽象接口或父类。这个抽象类中应该包含所有产品至少应该实现的接口。

📌 举个例子:

假设你有一个发送消息的系统,支持多种通知方式:

通知方式类名属于“产品”吗?
发送邮件EmailNotification✅ 是产品
发送短信SMSNotification✅ 是产品
发送微信WeChatNotification✅ 是产品

它们都有一个共同的“父类”或“接口”:

interface Notification {
    void send(String message);
}

第二步:创建多个具体的产品实现类

这些类是实际的产品。也就是说我们需要实现我们所需要的每个产品的具体实现。

class EmailNotification implements Notification {
    public void send(String message) {
        System.out.println("发送邮件: " + message);
    }
}

class SMSNotification implements Notification {
    public void send(String message) {
        System.out.println("发送短信: " + message);
    }
}

第三步:定义一个抽象工厂或接口

抽象工厂描述了创建产品的方法:

abstract class NotificationFactory {
    public abstract Notification createNotification();
}

也可以是接口:

interface NotificationFactory {
    Notification createNotification();
}

第四步:实现具体的工厂子类

每个子类负责创建一个具体的产品:

class EmailFactory extends NotificationFactory {
    public Notification createNotification() {
        return new EmailNotification();
    }
}

class SMSFactory extends NotificationFactory {
    public Notification createNotification() {
        return new SMSNotification();
    }
}

第五步:添加一个工厂选择器(用于根据不同类型返回不同工厂)

NotificationFactoryProvider {
    public static NotificationFactory getFactory(String type) {
        switch (type.toLowerCase()) {
            case "email": return new EmailFactory();
            case "sms": return new SMSFactory();
            default: throw new IllegalArgumentException("未知通知类型");
        }
    }
}

第六步:客户端使用工厂选择器获取对象

public class Main {
    public static void main(String[] args) {
        //客户端不关心具体的实现类,只关心工厂和接口
        NotificationFactory factory = NotificationFactoryProvider.getFactory("email");
        Notification notification = factory.createNotification();
        notification.send("Hello, this is a test email!"); 
    }
}

充分运用了多态的特性,当我们提前不知道需要那种通知方式时,通过使用工厂方法,在运行环境中动态调节我们生产的产品。

易扩展性

接下来我们继续通过这个例子体现工厂方法的易扩展性:

新增微信通知

工厂方法的生产方式在增加类型时,只动类,不懂客户端。即不需要修改Main函数中的内容。

我们先新增微信通知这个产品:

class WeChatNotification implements Notification {
    public void send(String message) {
        System.out.println("发送微信: " + message);
    }
}

然后增加微信类的创建工厂:

class WeChatFactory extends NotificationFactory {
    public Notification createNotification() {
        return new WechatNotification();
    }
}

然后我们在工厂选择器中增加微信选项:

NotificationFactoryProvider {
    public static NotificationFactory getFactory(String type) {
        switch (type.toLowerCase()) {
            case "email": return new EmailFactory();
            case "sms": return new SMSFactory();
	    case "wechat": return new WeChatFactory;	//只需增加这一行
            default: throw new IllegalArgumentException("未知通知类型");
        }
    }
}

这样当客户端接收到字段wechat时,就会生成微信工厂,并生成微信产品。

简单工厂

对于上面的例子,似乎并不能很好的利用到工厂方法的特性,反而让程序过度封装显得冗杂。因此我们引入简单工厂,作为在相对简单的情况中使用:

interface Notification {
    void send(String message);
}
class EmailNotification implements Notification {
    public void send(String message) {
        System.out.println("发送邮件: " + message);
    }
}

class SMSNotification implements Notification {
    public void send(String message) {
        System.out.println("发送短信: " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        //客户端不关心具体的实现类,只关心工厂和接口
        String type = "email"; // 可以是 "email" 或 "sms"
        Notification notification = createNotification(type);
        notification.send("Hello, this is a test message!");
    }
    public static Notification createNotification(String type) {
        switch(type) {
            case "email":
                return new EmailNotification();
            case "sms":
                return new SMSNotification();
            default:
                throw new IllegalArgumentException("Unknown notification type: " + type);
        }
    }
}

省去了给每个商品创建工厂的步骤,使用一个大工厂直接生成对应商品。

抽象工厂(Abstract Factroy)

抽象工厂是工厂方法的升级版,使用于一组产品的创建。

跨平台的GUI框架

如果我们想要开发一个跨平台的GUI框架,可以生成:

  • 按钮(Button)
  • 输入框(Textbox)

由于Windows系统和Mac系统UI风格不一致,你希望:

  • 在Window系统下使用Windows风格组件
  • 在Mac系统下使用Mac风格的组件

第一步:抽象产品族

我们给每一个产品创建一个接口:

// 抽象产品A:按钮
interface Button {
    void render();
}

// 抽象产品B:文本框
interface Textbox {
    void render();
}

第二步:具体产品实现(Mac和Windows两种风格)

class WindowsButton implements Button {
    public void render() {
        System.out.println("渲染 Windows 风格按钮");
    }
}

class WindowsTextbox implements Textbox {
    public void render() {
        System.out.println("渲染 Windows 风格文本框");
    }
}

class MacButton implements Button {
    public void render() {
        System.out.println("渲染 Mac 风格按钮");
    }
}

class MacTextbox implements Textbox {
    public void render() {
        System.out.println("渲染 Mac 风格文本框");
    }
}

第三步:抽象工厂接口

interface GUIFactory {
    Button createButton();
    Textbox createTextbox();
}

第四步:具体工厂类

class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }

    public Textbox createTextbox() {
        return new WindowsTextbox();
    }
}

class MacFactory implements GUIFactory {
    public Button createButton() {
        return new MacButton();
    }

    public Textbox createTextbox() {
        return new MacTextbox();
    }
}

第五步:客户端使用(不关心具体产品是哪一套)

public class Main {
    public static void main(String[] args) {
        GUIFactory factory = getFactory("mac");  // 或 "windows"
      
        Button button = factory.createButton();
        Textbox textbox = factory.createTextbox();

        button.render();
        textbox.render();
    }

    public static GUIFactory getFactory(String type) {
        if (type.equalsIgnoreCase("windows")) {
            return new WindowsFactory();
        } else if (type.equalsIgnoreCase("mac")) {
            return new MacFactory();
        } else {
            throw new IllegalArgumentException("不支持的类型");
        }
    }
}