工厂模式

2021-07-17  本文已影响0人  修伊Dal

概念

工厂模式是非常常用的一种设计模式,属于创建型设计模式一种。它主要解决代码中条件的判断吧和对象创建以及对象使用高度耦合问题。一旦模块功能频繁的往代码里扩展,高度耦合会增加代码的复杂度,系统会变导致的结构不清晰,难以维护,也不利于扩展。

工厂模式分为三类:

其中简单工厂模式在GoF的《设计模式》一书中归为工厂模式中的一种特例,与工厂模式是一类的。但看到更多的分类是把简单工厂模式分为工厂模式中的一种。

简单工厂模式

简单工厂模式又称静态工厂方法模式,因为其中创建对象的方法是静态的。

简单工厂模式应用非常广泛,我们拿电视机举例:

电视机的平牌有很多,比如华为,小米,索尼等,他们都可以播放电视台的节目。

/**
 * 电视机基类
 */
public abstract class Tv {
    /**
     * 播放电视台节目
     * @param tvStationId 电视台id
     */
    public abstract void play(int tvStationId);
}

/**
 * 华为电视机
 */
public class HuaweiTv extends Tv {
    @Override
    public void play(int tvStationId) {
        System.out.println("华为电视机播放电视台" + tvStationId + "频道的节目");
    }
}

/**
 * 小米电视机
 */
public class XiaomiTv extends Tv {
    @Override
    public void play(int tvStationId) {
        System.out.println("小米电视机播放电视台" + tvStationId + "频道的节目");
    }
}

/**
 * sony电视机
 */
public class SonyTv extends Tv {
    @Override
    public void play(int tvStationId) {
        System.out.println("索尼电视机播放电视台" + tvStationId + "频道的节目");
    }
}

因为一开始项目中的对象只有一个,但是随着需求的增加,最后结构可能会变成这样:

public void playTvStation(int tvStationId, String brand) {
    Tv tv = null;
    switch (brand) {
      case "Huawei":
        tv = new HuaweiTv();
        break;
      case "Xiaomi":
        tv = new XiaomiTv();
        break;
      case "Sony":
        tv = new SonyTv();
        break;
      default:
        throw new RuntimeException("不支持的品牌");
    }
    tv.play(tvStationId);
}

如果碰到像举例一样比较简单,并且不会再频繁扩展新的功能了,那么我觉得向上面的代码一样,用if...else或者switch...case就可以了。

但是要是频繁增加新功能,我们可以再创建一个工厂类,把条件的判断和对象的创建放到这个工厂类里面。

public class TvFactory {

    public static Tv getTv(String brand) {
        Tv result = null;
        switch (brand) {
            case "Huawei":
                result = new HuaweiTv();
                break;
            case "Xiaomi":
                result = new XiaomiTv();
                break;
            case "Sony":
                result = new SonyTv();
                break;
            default:
                throw new RuntimeException("不支持的品牌");
        }
        return result;
    }
}

有了工厂类后,我们的主程序就只有如下两句代码,并且就算扩展新的产品,也不会修改原有代码:

public void playTvStation(int tvStationId, String brand) {
    Tv tv = TvFactory.getTv(brand);
    tv.play(tvStationId);
}

如果不想用switch...case或if...else,可以采用第二种形式,由Map进行管理子类,不过这样获取到的对象是单例

public class TvFactory {

    private static final Map<String, Tv> TV_MAP = new HashMap<>();

    static {
        TV_MAP.put("Huawei", new HuaweiTv());
        TV_MAP.put("Xiaomi", new XiaomiTv());
        TV_MAP.put("Sony", new SonyTv());
    }

    public static Tv getTv(String brand) {
        if (ObjectUtils.isEmpty(brand)) {
            return null;
        }
        return TV_MAP.get(brand);
    }
}

优点

  1. 调用者不需要知道是哪个类,具体的实现,只要知道接口就行,而接口一般都是通用的。
  2. 把条件判断和对象创建以及对象使用解耦,增加了代码的可读性,也易于扩展。

缺点

当我们扩展新产品时,虽不用修改playTvStation客户端主程序接口的代码,但是我们需要修改工厂类代码,一定程度违反了开闭原则。

工厂方法模式

工厂方法模式在一定程度上解决了简单工厂不符合开闭原则问题。

工厂方法模式利用多态的思想,把创建对象的任务放到了子类工厂。

public interface ITvFactory {
    Tv getTv();
}

public class HuaweiTvFactory implements ITvFactory {
    @Override
    public Tv getTv() {
        return new HuaweiTv();
    }
}

public class XiaomiTvFactory implements ITvFactory {
    @Override
    public Tv getTv() {
        return new XiaomiTv();
    }
}

public class SonyTvFactory implements ITvFactory {
    @Override
    public Tv getTv() {
        return new SonyTv();
    }
}

我们再为多个工厂子类创建一个简单工厂类,来管理工厂类的创建。

public class TvFactoryMap {
    private static final Map<String, ITvFactory> TV_FACTORIES = new HashMap<>();

    static {
        TV_FACTORIES.put("Huawei", new HuaweiTvFactory());
        TV_FACTORIES.put("Xiaomi", new XiaomiTvFactory());
        TV_FACTORIES.put("Sony", new SonyTvFactory());
    }

    public static ITvFactory getTvFactory(String brand) {
        if (ObjectUtils.isEmpty(brand)) {
            return null;
        }
        return TV_FACTORIES.get(brand);
    }
}

优点

  1. 调用者不需要知道是哪个类,具体的实现,只要知道接口就行,而接口一般都是通用的。
  2. 扩展不再需要修改工厂类,一定程度解决了简单工厂模式不符合开闭原则的问题。

缺点

  1. 虽然扩展不需要修改工厂类,但是需要修改管理工厂子类的简单工厂类。看到很多文章说是符合开闭原则,我并不觉得这是完全符合的。
  2. 当想增加新的产品时,需要多增加一个工厂类,但频繁扩展新功能时,类会成倍增加,系统会变得复杂,维护起来会变得麻烦。

简单工厂模式与工厂方法模式适用场合

简单工厂模式是把条件判断和创建对象写在一个方法里的,所以:

抽象工厂模式

抽象工厂模式一定程度上解决了工厂方法模式增加新产品时类成倍增加的问题,但是抽象工厂模式应用比较特殊,不像前两者这么宽泛。

抽象工厂模式应用于系统中有多个产品族,但是每次只使用一个情况。

这里有个新的概念:产品族。

产品组指位于不同产品等级结构中,功能相关的产品组成的家族。

比如以屏幕分类,3个品牌的电视分别都有LCD和OLED屏幕的产品,那么不同品牌的LCD屏幕电视机分为一个产品族,不同品牌的OLED屏幕电视机也分为一个产品族。

按照这个想法划分就会有下面的6个类。

// LCD屏幕电视机:
interface ILcdTv;
class HuaweiLcdTv;
class XiaomiLcdTv;
class SonyLcdTv;

// OLED屏幕电视机:
interface IOledTv;
class HuaweiOledTv;
class XiaomiOledTv;
class SonyOledTv;

如果按照工厂方法的思路,我们给每个类增加一个工厂类。如果想增加一个品牌的电视机,比如LG电视机,那么要继续增加两个工厂类,因为LG也大概率生产LCD和OLED屏幕的电视机。这也是上面工厂方法模式的缺点,增加过多的类就会增加系统复杂度,维护的时候也会需要耗更多时间。

因此我们按照抽象工厂模式的思路,让一个工厂类创建多个对象,这样几个工厂类可以合一,类的数量就会减少。·

public interface ITvFactory {
  ILcdTv createLcdTv();
  IOledTv createOledTv();
}

public class HuaweiTvFactory {
  @Override
  public ILcdTv createLcdTv() {
    return new HuaweiLcdTv();
  }
  
  @Override
  public IOledTv createOledTv() {
    return new HuaweiOledTv();
  }
}

// ...省略XiaomiTvFactory和SonyTvFactory

当我们想增加一个新品牌的时候只要增加一个工厂就行了,维护起来也会更加的方便。

优点

  1. 同样调用者不需要知道具体类的实现,只知道接口就行
  2. 一定程度解决上解决了工厂方法模式新增产品导致类过度增加的问题。

缺点

  1. 因为有新的产品族概念,类的结构要比工厂方法模式复杂得多。
  2. 虽然解决了工厂方法模式新增产品导致类过度增加的问题。但是抽象工厂模式如果新增一个产品族,比如LED屏幕电视,这样的话新写一个接口ILedTv ,然后新写三个他的子类HuaweiLedTv,XiaomiLedTv,SonyLedTv,并且需要在接口ITvFactory中扩展一个新的方法createLedTv() ,工厂子类也需要增加相应的实现,这实在不能说是方便。

用简单工厂改进抽象工厂

上面的抽象工厂的缺点指出增加产品族时并不方便,所有的工厂子类都要增加相应实现,这点我们可以依靠简单工厂来解决。

public class TvFactory {

    public static ILcdTv createLcdTv(String brand) {
        ILcdTv result = null;
        switch (brand) {
            case "Huawei":
                result = new HuaweiLcdTv();
                break;
            case "Xiaomi":
                result = new XiaomiLcdTv();
                break;
            case "Sony":
                result = new SonyLcdTv();
                break;
            default:
                throw new RuntimeException("不支持的品牌");
        }
        return result;
    }
  
  public static IOledTv createOledTv(String brand) {
        IOledTv result = null;
        switch (brand) {
            case "Huawei":
                result = new HuaweiOledTv();
                break;
            case "Xiaomi":
                result = new XiaomiOledTv();
                break;
            case "Sony":
                result = new SonyOledTv();
                break;
            default:
                throw new RuntimeException("不支持的品牌");
        }
        return result;
    }
}

这样当我们增加新的产品族时只需要工厂扩展一个新的方法就行。

但是既然回到了简单工厂模式,那么简单工厂模式关于开闭原则问题也来了。

所以三个工厂模式并不都是完美的,各有各的优缺点,我们也不必特别在意开闭原则,选择合适的就行。

关于开闭原则

在工厂方法模式中我认为工厂方法模式一定程度解决了简单工厂不符合开闭原则的问题,但没有完全解决,实际上抽象工厂我也觉得不完全符合开闭原则,因为只要有new来创建对象的存在,那么扩展势必会修改创建对象的代码。如果想完全解决,我觉得要依靠反射来取代new创建,这样扩展的时候只要修改配置文件,不用动原来代码。

上一篇下一篇

猜你喜欢

热点阅读