设计模式六:工厂模式

解决什么问题

在某些情况下,创建对象可能涉及一系列复杂的初始化操作,例如查找配置文件、查询数据库表、初始化成员对象等。如果将这些逻辑放在构造函数中,会极大地影响代码的可读性。为了提高代码的可维护性,我们可以采用工厂模式。

工厂模式通过定义一个专门负责对象创建的类,即工厂类,来解决这个问题。工厂类负责执行复杂的初始化操作,然后返回已经初始化好的对象。通过使用工厂模式,在任何需要生成复杂对象的地方,我们都可以使用工厂类来简化对象的创建过程。

使用工厂模式可以将对象的创建逻辑与客户端代码分离,提高代码的可读性和可维护性。同时,工厂模式还可以灵活地适应不同的对象创建需求,使系统更具扩展性和可定制性。

总结:工厂模式时用来创建对象的,一般这些对象在初始化时,有这些特征

  1. 复杂对象的创建:当对象的创建涉及一系列复杂的初始化步骤、依赖关系或条件判断时,工厂模式可以将这些复杂的创建逻辑封装在工厂类中,使客户端代码更简洁清晰。
  2. 对象创建的可定制性:如果你希望客户端能够根据不同的需求定制对象的创建过程,工厂模式可以提供灵活的创建方式。通过工厂类,你可以根据参数、配置或运行时环境等信息来动态决定创建何种对象。

简单工厂模式

示例:

public interface Keyboard {
    void print();
    void input(Context context);
}
class HPKeyboard implements Keyboard {

    @Override
    public void print() {
        //...输出逻辑;
    }

    @Override
    public void input(Context context) {
        //...输入逻辑;
    }

}

class DellKeyboard implements Keyboard {

    @Override
    public void print() {
        //...输出逻辑;
    }

    @Override
    public void input(Context context) {
        //...输入逻辑;
    }

}

class LenovoKeyboard implements Keyboard {

    @Override
    public void print() {
        //...输出逻辑;
    }

    @Override
    public void input(Context context) {
        //...输入逻辑;
    }

}

/**
 * 工厂
 */
public class KeyboardFactory {
    public Keyboard getInstance(int brand) {
        if(BrandEnum.HP.getCode() == brand){
            return new HPKeyboard();
        } else if(BrandEnum.LENOVO.getCode() == brand){
            return new LenovoKeyboard();
        } else if(BrandEnum.DELL.getCode() == brand){
            return new DellKeyboard();
        }
        return null;
    }

    public static void main(String[] args) {
        KeyboardFactory keyboardFactory = new KeyboardFactory();
        Keyboard lenovoKeyboard = KeyboardFactory.getInstance(BrandEnum.LENOVO.getCode());
        //...
    }
}

缺点:上面的工厂实现是一个具体的类KeyboardFactory,而非接口或者抽象类,getInstance()方法利用if-else创建并返回具体的键盘实例,如果增加新的键盘子类,键盘工厂的创建方法中就要增加新的if-else。这种做法扩展性差,违背了开闭原则,也影响了可读性。所以,这种方式使用在业务较简单,工厂类不会经常更改的情况。

工厂方法

为了解决上面提到的"增加if-else"的问题,可以为每一个键盘子类建立一个对应的工厂子类,这些工厂子类实现同一个抽象工厂接口。这样,创建不同品牌的键盘,只需要实现不同的工厂子类。当有新品牌加入时,新建具体工厂继承抽象工厂,而不用修改任何一个类。 示例:

public interface IKeyboardFactory {
    Keyboard getInstance();
}

public class HPKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance(){
        return new HPKeyboard();
    }
}

public class LenovoFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance(){
        return new LenovoKeyboard();
    }
}

public class DellKeyboardFactory implements IKeyboardFactory {
    @Override
    public Keyboard getInstance(){
        return new DellKeyboard();
    }
}

缺点: 每一种品牌对应一个工厂子类,在创建具体键盘对象时,实例化不同的工厂子类。但是,如果业务涉及的子类越来越多,难道每一个子类都要对应一个工厂类吗?这样会使得系统中类的个数成倍增加,增加了代码的复杂度。

抽象工厂

为了缩减工厂实现子类的数量,不必给每一个产品分配一个工厂类,可以将产品进行分组,每组中的不同产品由同一个工厂类的不同方法来创建。

例如,键盘、主机这2种产品可以分到同一个分组——电脑,而不同品牌的电脑由不同的制造商工厂来创建。

示例:

public interface Keyboard {
   void print();
}
public class DellKeyboard implements Keyboard {
    @Override
    public void print() {
        //...dell...dell;
    }
}
public class HPKeyboard implements Keyboard {
    @Override
    public void print() {
        //...HP...HP;
    }
}
public interface Monitor {
   void play();
}
public class DellMonitor implements Monitor {
    @Override
    public void play() {
        //...dell...dell;
    }
}
public class HPMonitor implements Monitor {
    @Override
    public void play() {
        //...HP...HP;
    }
}
public interface MainFrame {
   void run();
}
public class DellMainFrame implements MainFrame {
    @Override
    public void run() {
        //...dell...dell;
    }
}
public class HPMainFrame implements MainFrame {
    @Override
    public void run() {
        //...HP...HP;
    }
}
//工厂类。工厂分为Dell工厂和HP工厂,各自负责品牌内产品的创建
public interface IFactory {
    MainFrame createMainFrame();
    Monitor createMainFrame();
    Keyboard createKeyboard();
}
public class DellFactory implements IFactory {
      @Override
      public MainFrame createMainFrame(){
                MainFrame mainFrame = new DellMainFrame();
             //...造一个Dell主机;
             return mainFrame;
      }

      @Override
      public Monitor createMonitor(){
                Monitor monitor = new DellMonitor();
             //...造一个Dell显示器;
             return monitor;
      }

      @Override
      public Keyboard createKeyboard(){
                Keyboard keyboard = new DellKeyboard();
             //...造一个Dell键盘;
             return Keyboard;
      }
}
public class HPFactory implements IFactory {
      @Override
      public MainFrame createMainFrame(){
                MainFrame mainFrame = new HPMainFrame();
             //...造一个HP主机;
             return mainFrame;
      }

      @Override
      public Monitor createMonitor(){
                Monitor monitor = new HPMonitor();
             //...造一个HP显示器;
             return monitor;
      }

      @Override
      public Keyboard createKeyboard(){
                Keyboard keyboard = new HPKeyboard();
             //...造一个HP键盘;
             return Keyboard;
      }
}
//客户端代码。实例化不同的工厂子类,可以通过不同的创建方法创建不同的产品
public class Main {
    public static void main(String[] args) {
        IFactory dellFactory = new DellFactory();
        IFactory HPFactory = new HPFactory();
        //创建戴尔键盘
        Keyboard dellKeyboard = dellFactory.createKeyboard();
        //...
    }
}

优缺点: 增加分组非常简单,例如要增加Lenovo分组,只需创建Lenovo工厂和具体的产品实现类。分组中的产品扩展非常困难,要增加一个鼠标Mouse,既要创建抽象的Mouse接口, 又要增加具体的实现:DellMouse、HPMouse, 还要再每个Factory中定义创建鼠标的方法实现。

使用工厂模式的java框架和库

  1. 工厂方法:Mybatis的DataSourceFactory;
  2. 简单工厂模式:Logger框架:许多日志记录框架,如Log4j、SLF4J使用简单工厂模式来创建和获取日志记录器的实例。

总结

三种模式的区别:

  • 简单工厂:唯一工厂类,一个产品抽象类,工厂类的创建方法依据入参判断并创建具体产品对象。
  • 工厂方法:多个工厂类,一个产品抽象类,利用多态创建不同的产品对象,避免了大量的if-else判断。
  • 抽象工厂:多个工厂类,多个产品抽象类,产品子类分组,同一个工厂实现类创建同组中的不同产品,减少了工厂子类的数量。

在下述情况下可以考虑使用工厂模式:

  1. 在编码时不能预见需要创建哪种类的实例。
  2. 系统不应依赖于产品类实例如何被创建、组合和表达的细节。

总之,工厂模式就是为了方便创建同一接口定义的具有复杂参数和初始化步骤的不同对象。工厂模式一般用来创建复杂对象。只需用new就可以创建成功的简单对象,无需使用工厂模式,否则会增加系统的复杂度。

此外,如果对象的参数是不固定的,推荐使用生成器(Builder)模式。

ps: 本文从淘宝技术中知乎回答整理而来


https://www.zhihu.com/question/27125796/answer/1615074467

CONTENTS