设计模式:用实际案例讲解工厂模式

工厂模式有啥用啊,我的项目没使用工厂模式也照样运行

这是我听过最令人哭笑不得的吐槽,这个程序猿的头发不知道有没有被自己薅秃

的确,项目中不使用工厂模式并不会影响项目的运行

但是,当项目后期需要二次开发时,代码的维护和修改的复杂度,绝对能让你恨不得把自己头发都薅秃

下面我们就来盘一盘工厂模式能解决哪些问题

简单工厂模式

实际案例

假如客户有这样一个需求,做一个用户订购手机来玩游戏的项目

项目中可以生产华为和小米的手机,生产的手机只能用来玩游戏,用户可以通过京东和淘宝来订购手机

需求中的一个前置条件是手机只能用来玩游戏,不能做别的事情。这就类似于一个规范,所有的手机都要遵守这个规范。

制定规范是java中接口的特性,所以我们要定义一个接口基类,叫做Phone,里面有一个玩游戏的方法play()

还要有华为手机和小米手机的类,分别叫做HuweiXiaomi,这两个手机类要遵循手机只能用来玩游戏这个规范,所以它们要实现Phone类,并完成play()方法

用java代码实现分别如下

phone基类

public interface Phone {
    void play();
}

华为手机类Huawei

public class Huawei implements Phone {
    @Override
    public void play() {
        System.out.println("华为手机玩游戏");
    }
}

小米手机类Xiaomi

public class Xiaomi implements Phone {
    @Override
    public void play() {
        System.out.println("小米手机玩游戏");
    }
}

用户可以通过京东和淘宝来订购手机,所以还要有京东和淘宝的类,分别叫做JingdongTaobao。类里面各有一个订购方法叫做order(),可以根据用户的喜好来订购不同的手机

如果用户喜欢华为则订购华为手机来玩游戏,如果用户喜欢小米则订购小米手机来玩游戏,其他的用户就不能玩游戏

因为京东类和淘宝类的代码逻辑一摸一样,这里只贴一下京东类的代码

public class Jingdong {
    public void order(String type) {
        Phone phone = null;
        if ("huawei".equals(type)) {
            phone = new Huawei();
            phone.play();
        } else if ("xiaomi".equals(type)) {
            phone = new Xiaomi();
            phone.play();
        } else {
            System.out.println("暂不支持");
        }
    }
}

这样我们就实现了客户的需求,而且没有使用任何设计模式。

项目虽然可以完美的运行,但是有一个问题值得我们思考,假如又增加了苹果手机,这时候我们的代码该怎么修改

首先,我们肯定是要增加苹果手机类IPhone,并且实现Phone基类

京东类中订购方法的逻辑需要做出相应的修改

淘宝类中订购方法的逻辑也需要做出相应的修改,修改的地方和京东类一模一样。这时我们就发现同样的代码需要修改两次。

如果订购类有很多,除了京东、淘宝,还有拼多多、微信商城等等。那就意味着同样的代码不止要修改两次,有多少个订购类就需要修改多少次

这个工作量是很大的,而且极其容易出错,就问你头秃不头秃

这时候就需要使用简单工厂模式来优化我们的代码

简单工厂模式就是创建一个工厂类,由这个类来封装实例化对象的行为

套用到这个例子中就是:创建一个工厂类,由工厂类来封装创建手机的逻辑。订购类从工厂类中获取手机对象直接使用,不再关心手机创建的过程

工厂类PhoneFactory用代码实现就是这样

public class PhoneFactory {
    public static Phone createPhone(String type) {
        Phone phone = null;
        if ("huawei".equals(type)) {
            phone = new Huawei();
        } else if ("xiaomi".equals(type)) {
            phone = new Xiaomi();
        } else if ("apple".equals(type)) {
            phone = new IPhone();
        } else {
            System.out.println("暂不支持");
        }
        return phone;
    }
}

京东、淘宝等订购类从工厂类里面获取手机对象后直接使用,不再关心手机的创建过程(京东和淘宝类的代码一样,这里只贴京东类的代码)

public class Jingdong {
    public void order(String type) {
        Phone phone = PhoneFactory.createPhone(type);
        if (phone != null) {
            phone.play();
        }
    }
}

这就实现了简单工厂模式,以后再需要增加手机型号时只需要修改工厂类就行了,其他代码不用修改

简单工厂模式总结

简单工厂模式就是创建一个工厂类,根据传入的参数类型来创建具体的产品对象,并返回产品对象的实例

主要适用于调用者不知道应该创建哪个具体的对象,只能根据传入的条件返回相应对象的场景

比如案例中,订购类是不知道要创建哪个手机对象的,只能根据用户提供的条件才能知道需要哪个手机对象

简单工厂模式的好处在于将对象的创建过程和使用过程进行解耦,减少新增具体产品时修改代码的复杂度

就像上面的例子一样,对象的创建过程由工厂类负责,订购类不需要关心对象是怎么创建的,只需要从工厂类获取对象来使用即可

当需要增加手机对象时,只需要修改工厂类,而不需要对每一个订购类进行修改

简单工厂模式的缺点在于每次新增具体产品时,都需要修改工厂类,这违背了设计模式中的开闭原则。而且当具体的产品比较多时,工厂类的if-else判断就会比较多,代码不美观

工厂方法模式

实际案例

基于刚才用户订购手机玩游戏的需求,我们稍微改动一下。

为了实现精准营销,京东、淘宝等商城分别上线了华为专卖店、小米专卖店和苹果专卖店

当用户进入华为专卖店,就默认用户要订购华为手机;当用户进入小米专卖店,就默认用户要订购小米手机;当用户进入苹果专卖店,就默认用户要订购苹果手机

这个需求用刚才我们讲的简单工厂模式也可以实现

但是简单工厂的缺点也很明显,当新增粉丝类型时需要修改工厂类,当粉丝类型过多时工厂类的逻辑就会比较繁杂

比如新增了vivo粉丝,工厂类就需要新增判断条件去创建vivo手机对象;新增了oppo手机,工厂类就要新增判断条件去创建oppo手机对象。一直不断的新增下去的话,就会导致工厂类的中的判断过多,代码很长,后期不容易维护

而且,简单工厂模式是适用于调用者不知道应该创建哪种对象的场景。

在这个需求中,京东等订购类中为不同的粉丝提供了专卖店,假如专卖店是订购类中的一个方法的话,在专卖店这个方法中是知道应该创建什么样的对象的。比如,在华为手机的订购方法中,是知道要创建华为手机对象的

所以,这个需求可以用工厂方法模式来实现

工厂方法模式和简单工厂模式相似,也需要有一个工厂类。不过在工厂方法模式中,工厂类不再负责创建对象。因为在每个订购方法中已经知道应该创建哪个手机对象,所以创建对象的逻辑下沉到订购类的方法中

工厂类只负责制定规范,来约束每个产品的具体行为,所以工厂类是一个接口基类。每个对应的产品需要有一个自己的工厂,来继承这个基类,达到约束自身行为的目的

这个需求中工厂基类规定每个工厂只能有一个方法,这个方法的作用就是创建手机对象

工厂基类PhoneFactory用代码实现

public interface PhoneFactory {
   Phone createPhone();
}

华为工厂类用代码实现

public class HuaweiFactory implements PhoneFactory {
   @Override
   public Phone createPhone() {
      return new Huawei();
   }
}

小米工厂类用代码实现

public class XiaomiFactory implements PhoneFactory {
   @Override
   public Phone createPhone() {
      return new Xiaomi();
   }
}

在订购类中,不同的订购方法调用不同的工厂获取对象。比如京东订购类的代码如下(淘宝订购类处理逻辑类似,这里不再贴淘宝类的代码)

public class Jingdong {
    // 华为粉丝订购华为手机
    public void orderHuawei() {
        PhoneFactory phoneFactory = new HuaweiFactory();
        Phone phone = phoneFactory.createPhone();
        phone.play();
    }
    // 小米粉丝订购小米手机
    public void orderXiaomi() {
        PhoneFactory phoneFactory = new XiaomiFactory();
        Phone phone = phoneFactory.createPhone();
        phone.play();
    }
}

这样我们就用工厂方法模式实现了为不同粉丝订购不同手机的需求

工厂方法模式总结

工厂方法模式是定义一个工厂接口基类,基类中定义一个创建产品的抽象方法。每个产品需要有自己的工厂来实现这个基类,并完成创建对应产品实例的方法,由具体的产品调用该方法来创建对象

它主要适用于调用者已经明确知道需要创建哪一个具体产品的场景

比如,针对华为的粉丝,已经明确知道要创建华为的手机产品

工厂方法模式的优势在于完全符合了开闭原则,在新增产品时不需要再改动已存在的代码,使工厂类和产品类的代码完全解耦,更利于程序的扩展

他的缺点也很明显,当新增产品时,需要同时新增产品类和工厂类,导致系统中的类是成对增加,增加了系统的复杂度

抽象工厂模式

实际案例

基于工厂方法模式的案例,我们再进一步扩展

用户不单单想订购手机来玩游戏,还想订购ipad和电脑

可以用刚才讲的工厂方法模式来实现:我们不仅需要提供手机工厂的基类,还需要提供ipad工厂基类和电脑工厂基类,并且为每个工厂基类提供具体的工厂实现类

订购类方法中,根据不同的需求来创建不同的产品供用户使用

这样实现的代码没有问题,但是不符合我们真实开发中的业务场景

在实际业务场景中,京东商城的华为专卖店想要订购手机不需要到华为公司的手机部门去订购吧?想要订购ipad不需要到华为公司的ipad部门订购吧?想要订购电脑也不需要到华为公司的电脑部门订购吧?

京东商城的华为专卖店应该只负责和华为公司对接,和具体的业务部门没关系。专卖店想要订购某个产品去告诉华为公司,由公司去给具体的业务部门沟通

所以,从实际的使用场景出发,我们的代码应该这样设计

不再使用单独的手机工厂、ipad工厂和PC工厂,而是把同一个厂家作为工厂,由工厂分别创建不同的产品

Factory基类实现代码如下

public interface Factory {
    Phone createPhone();
    IPad createIPad();
    PC createPC();
}

华为工厂类实现代码如下

public class HuaweiFactory implements Factory {
    @Override
    public Phone createPhone() {
        return new HuaweiPhone();
    }
    @Override
    public IPad createIPad() {
        return new HuaweiIPad();
    }
    @Override
    public PC createPC() {
        return new HuaweiPC();
    }
}

小米工厂实现代码如下

public class XiaomiFactory implements Factory {
    @Override
    public Phone createPhone() {
        return new XiaomiPhone();
    }
    @Override
    public IPad createIPad() {
        return new XiaomiIPad();
    }
    @Override
    public PC createPC() {
        return new XiaomiPC();
    }
}

在京东订购类中,我们只需要创建对应的工厂对象,由工厂对象创建不同的产品

public class Jingdong {
    // 华为粉丝订购手机、ipad、电脑
    public void orderHuawei() {
        Factory factory = new HuaweiFactory();
        Phone phone = factory.createPhone();
        phone.play();
        IPad ipad = factory.createIPad();
        ipad.play();
        PC pc = factory.createPC();
        pc.play();
    }
    // 小米粉丝订购手机、ipad、电脑
    public void orderXiaomi() {
        Factory factory = new XiaomiFactory();
        Phone phone = factory.createPhone();
        phone.play();
        IPad ipad = factory.createIPad();
        ipad.play();
        PC pc = factory.createPC();
        pc.play();
    }
}

这样我们就用抽象工厂模式实现了用户订购手机、ipad和电脑的需求

抽象工厂模式总结

抽象工厂模式是将具有一定共性的产品封装到一块,由工厂类分别为这些产品提供创建对象的方法,调用者可以根据不同的需求调用工厂类的具体方法来获得产品实例

比如案例中华为的手机、ipad和电脑都属于华为公司产品,所以可以由华为工厂类来负责分别创建不同的对象

它的优势在于将具有一定共性的产品集合封装到一起,在实际开发中更符合具体的业务场景

他的缺点就是降低了系统的扩展性,当新增产品时需要修改工厂类,在工厂类的基类和实现类中都需要增加对应的方法

比如说,用户也想订购VR眼镜来玩游戏。那么工厂基类中需要增加创建VR眼镜的方法,所有的工厂实现类中都需要增加对该方法的实现,系统扩展性比较差

— 文章来自赫连小伍公众号