设计模式 - 工厂模式和抽象工厂模式

本贴最后更新于 1461 天前,其中的信息可能已经事过境迁

设计模式详解。

《Design Patterns: Elements of Reusable Object-Oriented Software》(即《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)"。

设计模式不是一种规定,也不需要为了设计模式而设计,错误的应用设计模式反而会让代码变得复杂,难以维护。相反,正确的应用设计模式,可以让我们的代码更加健壮,扩展性更好,逻辑也更加清晰。

设计模式一书针对不同的应用场景,列出了各自的最佳实践。随着技术的不断发展,可能一些范例已经不再适用,但是这本书带给我们思想上的指引,意义是非常重大的。设计模式一书,根据应用场景,大致分为建造型、结构型、行为型,总共 23 种,并提出了 6 大原则。

本文开始,试着详细介绍每种设计模式,有解释不到位,理解存在偏差的地方,请提出宝贵意见。

建造型-工厂模式

工厂模式 Factory Method,定义了一个创建对象的接口,由子类决定实现是哪个,工厂方法让类实例推迟到子类。工厂模式隐藏创建对象的过程,是创建复杂对象的最佳实践。

应用场景:生产一种产品,这种产品有多种实现。

关键点:实现一个工厂接口

优点:隐藏了创建过程,创建一个产品只需要传入一个名字即可,需要增加一种产品实现,只需要实现工厂接口即可,扩展性非常好。

缺点:每次增加一种产品的实现,需要增加一个实现类,并维护工厂实现产品的方法,增加了复杂度。

通常我们创建对象需要用到关键字 new,创建一个产品直接 new 不好吗,为啥非要增加一个工厂呢?这不是多此一举吗?下面举一个简单的例子说明一下

这里是一个手机店,手机店里出售小米手机和华为手机。代码如下

非工厂模式

public abstract class Phone {
    String name;

    public abstract void init();

    public Phone() {
        init();
    }

    public void test(){
        System.out.println("i was " + name + " phone!");
        System.out.println("testing....");
    }

    public void packingPhone(){
        System.out.println("packing....");
    }
}

小米手机

public class XiaoMiPhone extends Phone {
    @Override
    public void init() {
        name = "小米";
    }
}

华为手机

public class HuaWeiPhone extends Phone {
    @Override
    public void init() {
        name = "华为";
    }
}

每次顾客来的时候,告诉我手机的名字,我就把手机卖给他,代码如下:

public class PhoneStore {

    public Phone order(String name) {
        Phone phone = null;
        switch (name) {
            case "小米":
                phone = new XiaoMiPhone();
                break;
            case "华为":
                phone = new HuaWeiPhone();
                break;
            default:
                throw new RuntimeException("未知的手机类型");
        }
        phone.test();
        phone.packingPhone();
        return phone;
    }

    public static void main(String[] args) {
        PhoneStore store = new PhoneStore();
        Phone xiaomi = store.order("小米");
        Phone huawei = store.order("华为");
    }
}

输出:

i was 小米 phone!
testing....
packing....
i was 华为 phone!
testing....
packing....

看起来还不错,但是当我们需要增加一个三星手机的时候,我们需要在 order 方法里面,重新写一个 case,用来创建三星手机。当我们不再出售小米手机的时候,我们需要将代码删除掉。

而手机打包,测试这样的业务是不变的,只有创建产品的逻辑在变。如果将一直变的逻辑和不变的逻辑写在一起,频繁改动,违反了对修改关闭,对扩展开放原则,导致时间越久越难以维护。


简单工厂

现在尝试用简单工厂 Simple Factory Method 实现上面的逻辑,代码如下:

1.增加一个工厂接口

public interface IFactory {
    Phone create(String name);
}

2.手机工厂实现

public class PhoneFactory implements IFactory {
    private static final PhoneFactory INSTANCE = new PhoneFactory();

    public static PhoneFactory getInstance() {
        return INSTANCE;
    }

    @Override
    public Phone create(String name) {
        switch (name) {
            case "小米":
                return new XiaoMiPhone();
            case "华为":
                return new HuaWeiPhone();
            default:
                throw new RuntimeException("未知的手机类型");
        }
    }
}

调整后的手机店

public class PhoneStore {
    private final IFactory factory = PhoneFactory.getInstance();

    public Phone order(String name) {
        Phone phone = factory.create(name);
        phone.test();
        phone.packingPhone();
        return phone;
    }

    public static void main(String[] args) {
        PhoneStore store = new PhoneStore();
        Phone xiaomi = store.order("小米");
        Phone huawei = store.order("华为");
    }
}

输出:

i was 小米 phone!
testing....
packing....
i was 华为 phone!
testing....
packing....

可以看到调整后的代码,输出结果没有变化,现在手机店只需要将顾客购买的手机名字传入即可,不再需要负责生产具体手机的逻辑了,这样只要生产手机的步骤不变,那么这里就一直不会修改。

我们用一个专门用来生产手机的工厂,这里实现生产手机的具体业务。

我们不需要将生产手机具体逻辑暴露出去,只需要提供一个接口方法即可。如果我们修改出售手机的品牌,只改动这里,其它不需要修改,扩展的时候也只需要新增一个实现即可,做到了对修改关闭,对扩展开放。


假设我们的手机店,每个品牌的手机都需要自己的专卖店,那上面的代码就不能满足要求了。

下面修改一下工厂的实现:

现在我们将手机店改为抽象类,将生产手机的方法改为抽象方法。

工厂模式

public abstract class PhoneStore {

    abstract Phone create(String name);

    public Phone order(String name) {
        Phone phone = create(name);
        phone.test();
        phone.packingPhone();
        return phone;
    }

    public static void main(String[] args) {
        PhoneStore xiaomi =new XiaoMiPhoneStore();
        PhoneStore huawei =new HuaWeiPhoneStore();

        xiaomi.order("老人机");
        xiaomi.order("学生机");
        huawei.order("学生机");
        huawei.order("老人机");
    }
}

小米的手机店:

public class XiaoMiPhoneStore extends PhoneStore {

    @Override
    public Phone create(String name) {
        switch (name) {
            case "老人机":
                return new XiaoMiOldPhone();
            case "学生机":
                return new XiaoMiStudentPhone();
            default:
                throw new RuntimeException("未知的手机类型");
        }
    }
}

小米手机店出售的老人机

public class XiaoMiOldPhone extends Phone {
    @Override
    public void init() {
        name = "小米老人机";
    }
}

小米手机店出售的学生机:

public class XiaoMiStudentPhone extends Phone {
    @Override
    public void init() {
        name = "小米学生机";
    }
}

华为手机店:

public class HuaWeiPhoneStore extends PhoneStore {

    @Override
    public Phone create(String name) {
        switch (name) {
            case "老人机":
                return new HuaWeiOldPhone();
            case "学生机":
                return new HuaWeiStudentPhone();
            default:
                throw new RuntimeException("未知的手机类型");
        }
    }
}

华为老人机:

public class HuaWeiOldPhone extends Phone {
    @Override
    public void init() {
        name = "华为老人机";
    }
}

华为学生机

public class HuaWeiStudentPhone extends Phone {
    @Override
    public void init() {
        name = "华为学生机";
    }
}

执行测试代码输出:

i was 小米老人机!
testing....
packing....
i was 小米学生机!
testing....
packing....
i was 华为学生机!
testing....
packing....
i was 华为老人机!
testing....
packing....

可以看到,现在我们有两个专卖店,小米和华为,可以出售自己品牌的老人机和学生机,而我们不需要改动抽象的手机店,只要去指定的专卖店即可。

**
**

建造型-抽象工厂模式

抽象工厂模式 Abstract Factory,工厂模式隐藏创建对象的过程,只能生产一种产品,当需要一个生产一族产品的时候,就需要用到抽象工厂模式。

应用场景:生产一族产品

关键点:用一个抽象工厂,实现生产一族产品,不指定具体产品实现。

优点:具体的工厂中,可以有自己的实现,抽象工厂实现了,相同的生产不同产品,可以有自己的逻辑。

缺点:产品族扩展困难。

上面的小米手机店和华为手机店可以生产自己的老人机和学生机,由于工厂越来越大,我们无法约束自己手机店用料,为了让我们的框架更加强大,我们现在加入原料工厂,我们每个品牌生产的手机,必须使用指定的原材料。

示例代码如下:

手机类增加电池和屏幕

public abstract class Phone {
    String name;
    Battery battery;
    Screen screen;

    public abstract void init();

    public void test() {
        System.out.println("i was " + name + "!");
        System.out.println("battery is " + battery.name);
        System.out.println("screen is " + screen.name);
        System.out.println("testing....");
    }

    public void packingPhone() {
        System.out.println("packing....");
    }
}

对于上面的电池和屏幕分别加入实现:

public abstract class Battery {
    String name;

    public Battery() {
        init();
    }

    abstract void init();
}
public class PinShengBattery extends Battery {
    @Override
    void init() {
        name ="PinSheng Battery";
    }
}
public class SamsungBattery extends Battery {
    @Override
    void init() {
        name="Samsung Battery";
    }
}
public abstract class Screen {
    String name;

    public Screen() {
        init();
    }

    abstract void init();
}
public class JDFScreen extends Screen {
    @Override
    void init() {
        name ="JDF Screen";
    }
}
public class SamsungScreen extends Screen {
    @Override
    void init() {
        name ="Samsung Screen";
    }
}

原料工厂接口类及实现类

public interface PhoneMaterialFactory {
    Screen createScreen();
    Battery createBattery();
}
public class XiaoMiPhoneMaterialFactory implements PhoneMaterialFactory {
    @Override
    public Screen createScreen() {
        return new SamsungScreen();
    }

    @Override
    public Battery createBattery() {
        return new PinShengBattery();
    }
}
public class HuaWeiPhoneMaterialFactory implements PhoneMaterialFactory{
    @Override
    public Screen createScreen() {
        return new JDFScreen();
    }

    @Override
    public Battery createBattery() {
        return new SamsungBattery();
    }
}

抽象手机店类及实现修改如下:

public abstract class PhoneStore {

    abstract Phone create(String name);

    public Phone order(String name) {
        Phone phone = create(name);
        phone.init();
        phone.test();
        phone.packingPhone();
        return phone;
    }

    public static void main(String[] args) {
        PhoneStore xiaomi =new XiaoMiPhoneStore();
        PhoneStore huawei =new HuaWeiPhoneStore();

        xiaomi.order("老人机");
        xiaomi.order("学生机");
        huawei.order("学生机");
        huawei.order("老人机");
    }
}
public class XiaoMiPhoneStore extends PhoneStore {

    @Override
    public Phone create(String name) {
        PhoneMaterialFactory phoneMaterialFactory =new XiaoMiPhoneMaterialFactory();
        switch (name) {
            case "老人机":
                return new XiaoMiOldPhone(phoneMaterialFactory);
            case "学生机":
                return new XiaoMiStudentPhone(phoneMaterialFactory);
            default:
                throw new RuntimeException("未知的手机类型");
        }
    }
}
public class HuaWeiPhoneStore extends PhoneStore {

    @Override
    public Phone create(String name) {
        PhoneMaterialFactory phoneMaterialFactory = new HuaWeiPhoneMaterialFactory();
        switch (name) {
            case "老人机":
                return new HuaWeiOldPhone(phoneMaterialFactory);
            case "学生机":
                return new HuaWeiStudentPhone(phoneMaterialFactory);
            default:
                throw new RuntimeException("未知的手机类型");
        }
    }
}

测试代码输出如下:

i was 小米老人机!
battery is PinSheng Battery
screen is Samsung Screen
testing....
packing....
i was 小米学生机!
battery is PinSheng Battery
screen is Samsung Screen
testing....
packing....
i was 华为学生机!
battery is Samsung Battery
screen is JDF Screen
testing....
packing....
i was 华为老人机!
battery is Samsung Battery
screen is JDF Screen
testing....
packing....

现在的手机店可以按着我们指定的原材料进行生产手机,当我们需要增加原材料的实现时,只需要实现对应的接口即可。

以上就是抽象工厂的示例代码,抽象工厂用于定义一族产品的生产制作过程。

总结:

工厂方法和抽象工厂方法是很常用的设计模式,在一些框架中如 jdk,spring 中大量使用了工厂模式,工厂模式使用灵活,扩展方便,结构清晰,非常值得我们学习。

代码比较粗糙,有问题欢迎指正,共同学习。


设计模式不是为了生搬硬套,应该根据实际情况,灵活的运用,后面会介绍更多的设计模式和设计原则,欢迎大家一起讨论学习。

欢迎关注我们

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...