23种设计模式之工厂方法模式、抽象工厂(Factory)模式

834 阅读9分钟

1、简单工厂模式(静态工厂方法模式)

1.1 定义

定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。23种设计模式并不包括简单工厂模式,它更像一种编程习惯。

1.2 模式结构

简单工厂模式由三部分组成:

  • Creator(工厂类):担任这个角色的是简单工厂模式的核心,含有与应用紧密相关的商业逻辑。工厂类在客户端的直接调用下创建产品对象。
  • AbstractProduct(抽象产品):担任这个角色的类是由简单工厂模式所创建的对象的父类,或它们共同拥有的接口。
  • ConcreteProduct(具体产品):简单工厂模式所创建的任何对象都是这个角色的实例。
1.3 实例

1.3.1 电视机父类TV(AbstractProduct)

public abstract class TV {

    abstract void openTV();
}

1.3.2 海尔电视机和长虹电视机(ConcreteProduct)

public class HaierTV extends TV {

    public void openTV() {
        System.out.println("open haier TV");
    }
}
public class ChanghongTV extends TV {

    public void openTV() {
        System.out.println("open changhong TV");
    }
}

1.3.3 工厂类(Creator)

public class Factory {

    public static TV watchTV(String type) throws Exception {
        if (type.equals("Haier")) {
            return new HaierTV();
        } else if (type.equals("Changhong")) {
            return new ChanghongTV();
        } else {
            throw new IllegalArgumentException("no correspond TV");
        }
    }
}

1.3.4 客户端调用

public class Client {
    
    public static void main(String[] args) throws Exception {
        TV tv = Factory.watchTV("Haier");
        tv.openTV();
    }
}
1.4 适用场景
  • 工厂类负责创建的对象比较小。
  • 客户端只关心传入工厂类的参数,不关心对象的创建过程。
1.5 在JDK中的应用

Calendar.class中的部分源码:

private static Calendar createCalendar(TimeZone zone,
                                       Locale aLocale)
{
    CalendarProvider provider =
        LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                             .getCalendarProvider();
    if (provider != null) {
        try {
            return provider.getInstance(zone, aLocale);
        } catch (IllegalArgumentException iae) {
            // fall back to the default instantiation
        }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            switch (caltype) {
            case "buddhist":
            cal = new BuddhistCalendar(zone, aLocale);
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, aLocale);
                break;
            case "gregory":
                cal = new GregorianCalendar(zone, aLocale);
                break;
            }
        }
    }
    if (cal == null) {
        // If no known calendar type is explicitly specified,
        // perform the traditional way to create a Calendar:
        // create a BuddhistCalendar for th_TH locale,
        // a JapaneseImperialCalendar for ja_JP_JP locale, or
        // a GregorianCalendar for any other locales.
        // NOTE: The language, country and variant strings are interned.
        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
            cal = new BuddhistCalendar(zone, aLocale);
        } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                   && aLocale.getCountry() == "JP") {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    }
    return cal;
}
1.6 优缺点

1.6.1 优点

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工具类用于创建对象。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道产品类所对应的参数即可,对应一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
  • 当需要引入新的产品是不需要修改客户端的代码,只需要添加相应的产品类并修改工厂类就可以了,所以说从产品的角度上简单工厂模式是符合“开-闭”原则的。

1.6.2 缺点

  • 由于工厂类集中了所有产品创建逻辑,工厂类一般被我们称作“全能类”或者“上帝类”,因为所有的产品创建它都完成,这看似是好事,但仔细想想是有问题的。这样一旦不能正常工作,整个系统都要受到影响。
  • 系统扩展困难,一旦添加新产品就要修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。所以说从工厂的角度来说简单工厂模式是不符合“开-闭”原则的。
  • 由于使用静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

2、工厂方法模式(虚拟构造器模式、多态工厂模式)

2.1 定义

工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

2.2 模式结构

工厂方法模式由四部分组成:

  • AbstractCreater(抽象工厂):担任这个角色的是工厂方法模式的核心,与调用者直接交互用来提供产品。任何在模式中创建对象的工厂类必须继承或者实现这个接口。
  • ConcreteCreator(具体工厂):担任这个角色的是实现了抽象工厂接口的具体实现类。具体工厂角色含有与应用密切相关的逻辑,并且受到应用程序的调用以创建产品对象。
  • AbstractProduct(抽象产品):工厂方法模式所创建的对象的超类。主要目的是定义产品的规范,所有的产品实现都必须遵循这些规范。
  • ConcreteProduct(具体产品):实现了抽象产品角色所声明的接口,工厂方法所创建的每一个对象都是某个具体产品角色的实例。
2.3 实例

2.3.1 电视机父类TV(AbstractProduct)

public abstract class TV {

    abstract void openTV();
}

2.3.2 海尔电视机和长虹电视机(ConcreteProduct)

public class HaierTV extends TV {

    public void openTV() {
        System.out.println("open haier TV");
    }
}
public class Changhong extends TV {

    public void openTV() {
        System.out.println('open changhong TV');
    }
}

2.3.3 抽象工厂类(AbstractCreater)

public abstract class Factory {

    public abstract TV watchTV();
}

2.3.4 具体工厂类(ConcreteCreator)

public class HaierFactory extends Factory {

    public TV watchTV() {
        return new HaierTV();
    }
}
public class ChanghongFactory extends Factory {

    public TV watchTV() {
        return new ChanghongTV();
    }
}

2.3.5 客户端调用

public class Client {

    public static void main(String[] args) throws Exception {
        Factory factory = new HaierFactory();
        TV tv = factory.watchTV();
        tv.openTV();
    }
}
2.4 适用场景
  • 客户端不需要知道它所场景的对象的类,只需要知道创建具体产品的工厂类。
  • 客户端可以通过子类来指定创建对应的对象。
2.5 优缺点
  • 工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。
  • 在系统加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品。这样系统的可扩展性变得良好,完全符合“开-闭“原则。

3、抽象工厂模式(Kit模式)

3.1 定义

提供一个创建一系列相关或相互依赖对象的接口,而无须指明它们具体的类。

3.2 产品族和等级等级结构
  • 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,取子类有海尔电视机、长虹电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构。
  • 产品族:在抽象工厂模式中,产品族是指同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

3.3 模式结构

抽象工厂模式和工厂方法模式类似都是四部分组成的,不同的是,抽象工厂模式中具体工厂不再是只创建一种产品,一个具体的工厂可以创建一个产品族的产品。

3.4 实例

3.4.1 电视机父类TV(AbstractProduct)

public abstract class TV {

    abstract void openTV();
}

3.4.2 海尔电视机和长虹电视(ConcreteProduct)

public class HaireTV extends TV {

    public void openTV() {
        System.out.println("open haire TV");
    }
}
public class ChanghongTV extends TV {

    public void openTV() {
        System.out.println('open changhong TV');
    }
}

3.4.3 冰箱父类Refrigerator(AbstractProduct)

public abstract class Refrigerator {

    abstract void openRefrigerator();
}

3.4.4 海尔冰箱和长虹冰箱(ConcreteProduct)

public class HaierRefrigerator extends Refrigerator {

    public void openRefrigerator() {
        System.out.println("open haier refrigerator");
    }
}
public class ChanghongRefrigerator extends Refrigerator {

    public void openRefrigerator() {
        System.out.println("open changhong refrigerator");
    }
}

3.4.5 抽象工厂类,定义同一族产品的两个不同等级结构的产品结构(AbstractCreater)

public abstract class Factory {

    public abstract TV watchTV();
    
    public abstract Refrigerator takeThings();
}

3.4.6 具体工厂类(ConcreteCreator)

public class HaierFactory extends Factory {

    public TV watchTV() {
        return new HaierTV();
    }

    public Refrigerator takeThings() {
        return new HaierRefrigerator();
    }
}
public class ChanghongFactory extends Factory {

    public TV watchTV() {
        return new ChanghongTV();
    }
    
    public Refrigerator takeThings() {
        return new ChanghongRefrigerator();
    }
}

3.4.7 客户端调用

public class Client {

    public static void main(String[] args) throws Exception {
        Factory factory = new HaierFactory();
        TV tv = factory.watchTV();
        tv.openTV();
        Refrigerator refrigerator = factory.takeThings();
        refrigerator.openRefrigerator();
    }
}
3.5 适用场景
  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
  • 系统的产品有多于一个的产品族,而系统只消费群体某一族的产品。
  • 同属于同一产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。 *系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖与实现。
3.6 优缺点

3.6.1 优点

  • 抽象工厂模式隔离了具体类的生成,使得客户不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程序上改变整个系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常使用的设计模式。
  • 增加新的具体工厂和产品族很方便,无须改动其他,符合“开闭”原则。

3.6.2 缺点

  • 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被场景的产品集合,要支持新种类的产品就意味着要对接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然带来较大的不便。
  • 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。

源代码:github.com/freedom9/de…