《HF 设计模式》 C4 工厂模式

除了使用new操作符外还有更多制造对象的方法,实例化对象这个过程不应该总是公开地进行

工厂模式分为 简单工厂,工厂方法模式,抽象工厂模式

1 模拟披萨店(上)

1.1 new对象带来的问题

当使用new关键字时,我们创建了一个类的对象,当有一群相关的具体类时,通常会写出这样地代码:

Duck duck;

if(picnic) {
    
    
	duck = new MallardDuck();
} else if(hunting) {
    
    
	duck = new DecoyDuck;
} else if(inBathTub) {
    
    
	duck = new RubberDuck;
}

当看见这样的代码时,一旦有修改或扩展,就需要重新打开这部分代码进行修改,这段代码违背了我们的设计原则:

设计原则:
类应该对扩展开放,对修改关闭

通常上述的代码将造成系统更难维护和更新,且更容易出错

针对接口编程,可以隔离掉以后系统可能发生的一大堆改变,如果代码针对接口编写,那么通过多态,它可以与任何新类实现该接口
但是当代码出使用大量具体类时,一旦想加入新的具体类,就必须改变代码,就违反了开闭原则

1.2 增加更多的披萨

假设你有一个披萨店,制作一个披萨代码可能会这么写:

public Pizza getOnePizza() {
    
    
	Pizza pizza = new Pizza();
	
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

但是现在我们需要更多的披萨种类:

public Pizza getOnePizza(String type) {
    
    
	Pizza pizza;
	
	if(type.equals("cheese")) {
    
    
		pizza = new CheesePizza();
	} else if(type.equals("greek")) {
    
    
		pizza = new GreekPizza();	
	} else if(type.equals("pepperoni")) {
    
    
		pizza = new PepperoniPizza();
	}

	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

如果现在我们需要更多的披萨,就必须增加更多的else if来生产更多的对象,如果某个披萨卖的不好,我们需要在代码中删除它

这只是一个简单的模拟披萨的程序,想象工程中如果遇到相似的问题,类似上面生成对象的代码存在于几十个类甚至更多,修改一次代码将会心力憔悴

上述的代码违背了开闭原则,解决这个问题需要最初的设计原则:

设计原则:
找到程序中变与不变的部分,将变化的部分独立出来

对于上述生产披萨的程序,可以看到生成披萨实例的过程,那一系列else if生产具体种类的披萨是改变的,而披萨的准备,烘烤,切片,装盘等过程是不变的

1.3 简单工厂

有了变化和不变的部分后,不变的部分继续保留在getOnePizza()中,我们将变化的代码拿到另一个类中,这个类就是工厂,根据披萨的类型,返回对应的披萨对象

Pizza超类:

public class Pizza {
    
    
    protected String name;
    protected int price;

    public Pizza(String name, int price) {
    
    
        this.name = name;
        this.price = price;
    }

    public String getName() {
    
    
        return name;
    }

    public int getPrice() {
    
    
        return price;
    }

    public void prepare() {
    
    
        System.out.println("准备 " + this.name);
    }

    public void bake() {
    
    
        System.out.println("烘烤 " + this.name);
    }

    public void cut() {
    
    
        System.out.println("切片 " + this.name);
    }

    public void box() {
    
    
        System.out.println("装盒 " + this.name);
    }
}

不同种类的Pizza:


public class CheesePizza extends Pizza {
    
    

    public CheesePizza() {
    
    
        super("cheesePizza", 30);
    }

}


public class ClamPizza extends Pizza {
    
    

    public CheesePizza() {
    
    
        super("clamPizza", 20);
    }

}
...

简单Pizza工厂:

public class SimplePizzaFactory {
    
    

    public SimplePizzaFactory() {
    
    }

    public Pizza createOnePizza(String type) {
    
    
        Pizza pizza;

        if (type.equals("cheese")) {
    
    
            pizza = new CheesePizza();
        } else {
    
    
            return null;
        }
        return pizza;
    }

}

披萨店:

public class PizzaStore {
    
    

    private SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
    
    
        this.factory = factory;
    }

    public Pizza getOnePizza(String type) {
    
    
        Pizza pizza = this.factory.createOnePizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

测试:
在这里插入图片描述

通过一个工厂类,我们把生产不同种类披萨的过程与原先的代码分离,这样做看起来只是把问题放到了另一个类里,但是如果以后需要一个菜单,外卖等功能,都可以从该工厂中取得对象,而不用在每个类中都去写生产对象的逻辑过程

当以后需要增加或删除某个pizza时,只需要更改对应工厂内的代码,需要创建披萨对象的类不用做任何改动

也可以将工厂内生产对象的方法变为静态方法,这样调用时还可以跳过生产工厂对象的过程,
在这里插入图片描述

1.4 加盟披萨店

现在我们要开更多的分店,如在纽约,芝加哥,加州等地开设分店,但是考虑到不同地方的饮食习惯不同,比如纽约分店的披萨应该做成薄饼,少量调料,芝加哥分店的披萨应该做成厚饼,大量调料

这些PizzaStore需要的对象不再相同,原先的SimplePizzaFactory不能再继续使用,需要为每个地区设立一家工厂,提供给附近的披萨店

NYPizzaFactory
ChicagoPizzaFactory
CaliforniaFactory

这样每个加盟店就可以提供符合本地需求的披萨

NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.getOnePizza("cheese");

ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.getOnePizza("cheese");

这样可以保证各地区披萨原料不同,但是各地区加盟店可能会有自己的需求,对每种披萨的烘烤的方式不同,切片方式不同,装盘方式不同

为了解决这个问题,让Pizza中的
prepare(),bake(),cut(),box()都成为抽象方法
PizzaStore中的 createOnePizza()也作为抽象方法

Pizza种类:

/**
 * @author 雫
 * @date 2021/3/3 - 14:13
 * @function 所有pizza的子类
 * 所有Pizza都必须实现具体制作流程
 */
public abstract class Pizza {
    
    
    protected String name;
    protected double price;

    public Pizza(String name, double price) {
    
    
        this.name = name;
        this.price = price;
    }

    public abstract void prepare();

    public abstract void bake();

    public abstract void cut();

    public abstract void box();
}

/**
 * @author 雫
 * @date 2021/3/3 - 14:21
 * @function 纽约芝士披萨
 */
public class NYCheesePizza extends Pizza {
    
    

    public NYCheesePizza() {
    
    
        super("nyCheesePizza", 40);
    }

    @Override
    public void prepare() {
    
    
        System.out.println("准备 " + this.name);
        System.out.println("调料较少");
    }

    @Override
    public void bake() {
    
    
        System.out.println("烘烤 " + this.name);
    }

    @Override
    public void cut() {
    
    
        System.out.println("切多片");
    }

    @Override
    public void box() {
    
    
        System.out.println("用XXX厂的盒子装盘");
    }
}


/**
 * @author 雫
 * @date 2021/3/3 - 14:24
 * @function 芝加哥芝士披萨
 */
public class ChicagoCheesePizza extends Pizza {
    
    

    public ChicagoCheesePizza() {
    
    
        super("chicagoCheesePizza", 35);
    }

    @Override
    public void prepare() {
    
    
        System.out.println("准备 " + this.name);
        System.out.println("调料较多");
    }

    @Override
    public void bake() {
    
    
        System.out.println("烘烤 " + this.name);
        System.out.println("烘烤时间稍长");
    }

    @Override
    public void cut() {
    
    
        System.out.println("切少片");
    }

    @Override
    public void box() {
    
    
        System.out.println("用YYY厂的盒子装盘");
    }
}

披萨店:

/**
 * @author 雫
 * @date 2021/3/3 - 14:13
 * @function 所有store的超类
 * 都必须实现creatOnePizza方法
 */
public abstract class PizzaStore {
    
    

    public Pizza getOnePizza(String type) {
    
    
        Pizza pizza;

        pizza = creatOnePizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    protected abstract Pizza creatOnePizza(String type);
}

/**
 * @author 雫
 * @date 2021/3/3 - 14:27
 * @function 芝加哥披萨店
 */
public class ChicagoPizzaStore extends PizzaStore {
    
    

    public ChicagoPizzaStore() {
    
    
    }

    @Override
    protected Pizza creatOnePizza(String type) {
    
    
        Pizza pizza;
        if(type.equals("cheese")) {
    
    
            pizza = new ChicagoCheesePizza();
        } else {
    
    
            return null;
        }
        return pizza;
    }

}

/**
 * @author 雫
 * @date 2021/3/3 - 14:21
 * @function 纽约披萨店
 */
public class NYPizzaStore extends PizzaStore {
    
    

    public NYPizzaStore() {
    
    
    }

    @Override
    protected Pizza creatOnePizza(String type) {
    
    
        Pizza pizza;

        if(type.equals("cheese")) {
    
    
            pizza = new NYCheesePizza();
        } else {
    
    
            return null;
        }
        return pizza;
    }

}

测试:
在这里插入图片描述
上述的程序中,PizzaStore中的抽象方法creatOnePizza()被getOnePizza()调用,且在子类中被实现,这里的creatOnePizza()就是工厂方法模式

1.5 工厂方法模式

在这里插入图片描述
在PizzaStore中getOnePizza()需要用到一个Pizza对象,这个对象由createOnePizza()抽象方法完成,也就是说,对象的生成交给了子类决定
且getOnePizza()需要对Pizza对象做一系列的操作,但是getOnePizza()并不知道将来要处理哪种Pizza,这就是解耦

而createOnePizza就是一个工厂方法

abstract Pizza createOnePizza(String type);

工厂方法:
abstract Product factoryMethod(String type);
工厂方法是抽象的,依赖子类来创建产品(对象)
工厂方法必须返回一个产品(超类中通常会用到工厂方法的返回值)
工厂方法能将超类中的代码和创建产品的代码分开
工厂方法可以根据参数来创建所需产品(也可以不要参数)

所有工厂模式都用来封装对象的创建,工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的

工厂方法模式:定义了一个创建对象的接口(抽象类),但由子类决定要实例化的类是哪一个,工厂方法模式把创建对象的过程推迟到了子类

在这里插入图片描述抽象创建者中,任何其它非抽象方法,都可以用到工厂方法所创建出来的产品,但是创建产品是由子类完成的

1.6 工厂方法模式总结

1,工厂方法模式中,需要有一个抽象创建者(接口,抽象类),有一个或若干个具体创建者(实现接口,继承抽象类)

2,工厂方法模式可以帮助我们将产品的“实现”从“使用”中解耦,如果增加或改变产品的实现,那么抽象创建者不会受到影响

3,抽象创建者可以不是抽象的,做成默认生产单一对象也是可行的

工厂方法模式和简单工厂的对比
工厂方法生成对象是创建一个框架,由子类决定如何实现,简单工厂是将对象封装起来,但简单工厂不能变更正在创建的产品,没有工厂方法模式的弹性

使用工厂的优点
将创建对象的代码集中在一个类或一个方法中,可以避免代码中的重复,并方便以后的维护,实例化对象时,依赖于接口,而不是具体类

2 模拟披萨店(下)

2.1 对象间的依赖

假设我们不使用任何设计模式来模拟披萨店,可能写出下面的代码

public class PizzaStore {
    
    

	public PizzaStore() {
    
    }
	
	public Pizza getOnePizza(String style, String type) {
    
    
        Pizza pizza = null;
        if(style.equals("NY")) {
    
    
            if(type.equals("cheese")) {
    
    
                pizza = new NYCheesePizza();
            } else if(type.equals("clam")) {
    
    
                pizza = new NYClamPizza();
            }
           	...
        } else if(style.equals("Chicago")) {
    
    
            if(type.equals("cheese")) {
    
    
                pizza = new ChicagoCheesePizza();
            } else if(type.equlas("clam")) {
    
    
            	pizza = new ChicageClamPizza();
			}
            ...
        } else {
    
    
            return null;
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

}

毫无疑问,没有一点弹性,功能和实现死死捆绑,扩展和修改要大面积更改源码,这就是存在大量的对象依赖

当直接实例化一个对象时,就是在依赖它的具体类,这里的对象是PizzaStore对象,它依赖的具体类有:
在这里插入图片描述
也就是说PizzaStore对象的功能是靠这些具体类来实现的,如果有一个具体类出现了问题,那么整个PizzaStore就会工作不正常,所以降低代码内的依赖是明智之举

2.2 依赖倒置原则

设计原则:
要依赖抽象,不要依赖具体类

即不要让高层组件(PizzaStore)依赖低层组件(NYCheesePizza),而且不管高层还是低层组件,都应该依赖于“抽象”,超类和接口就是抽象

回看刚才2.1内的代码,该如何用依赖倒置原则重构代码?

2.1内代码的问题?
PizzaStore依赖各种Pizza,在自己的getOnePizza内实例化所有对象
由此造成了PizzaStore对大量具体类的依赖

解决方法?
把PizzaStore中用到的对象全部拿出去
即生成各种Pizza的过程封装到别处

拿到哪?
简单工厂,或工厂方法模式
将实例化对象的过程封装到一个类中(简单工厂)
将实例化对象的过程交给子类完成(工厂方法模式)

在这里插入图片描述
现在PizzaStore只依赖Pizza(抽象类),而所有种类的Pizza因为继承Pizza,因此也都依赖Pizza,成功符合了设计原则依赖抽象而不是具体类,PizzaStore变得干净

想遵循依赖倒置原则,工厂模式并非是唯一的技巧,但却是最有威力的技巧之一

当你的类中需要实例化多个对象时,且这些对象有相同的超类/接口,那么可以采用工厂模式来减少依赖关系,提高系统的可维护性

2.3 创建原料工厂

现在各地区的披萨店已经能正常工作了,但是还存在一个问题,各个加盟店使用的原料是自己采购的,为了避免有加盟店偷工减料,决定为每个地区再建立一个原料工厂,为附近的加盟店供应在这里插入图片描述

原料:

/**
 * @author 雫
 * @date 2021/3/3 - 17:45
 * @function 面团
 */
public class Dough {
    
    
    private String brand;
    private String place;

    public Dough(String brand, String place) {
    
    
        this.brand = brand;
        this.place = place;
    }

    public String getBrand() {
    
    
        return brand;
    }

    public String getPlace() {
    
    
        return place;
    }
}

/**
 * @author 雫
 * @date 2021/3/3 - 17:46
 * @function 酱汁
 */
public class Sauce {
    
    
    private String brand;
    private String place;

    public Sauce(String brand, String place) {
    
    
        this.brand = brand;
        this.place = place;
    }

    public String getBrand() {
    
    
        return brand;
    }

    public String getPlace() {
    
    
        return place;
    }
}

/**
 * @author 雫
 * @date 2021/3/3 - 17:47
 * @function 芝士
 */
public class Cheese {
    
    
    private String brand;
    private String place;

    public Cheese(String brand, String place) {
    
    
        this.brand = brand;
        this.place = place;
    }

    public String getBrand() {
    
    
        return brand;
    }

    public String getPlace() {
    
    
        return place;
    }
}

原料工厂接口:

/**
 * @author 雫
 * @date 2021/3/3 - 17:38
 * @function 原料工厂接口
 */
public interface IngredientFactory {
    
    
    Dough createDough();

    Sauce createSauce();

    Cheese createCheese();
}

各地区原料工厂:

/**
 * @author 雫
 * @date 2021/3/3 - 17:49
 * @function
 */
public class NyIngredientFactory implements IngredientFactory {
    
    
    @Override
    public Dough createDough() {
    
    
        Dough dough =  new Dough("xx", "NewYork");
        System.out.println("Dough: " + dough.getBrand() + ", " 
        		+ dough.getPlace());
        return dough;
    }

    @Override
    public Sauce createSauce() {
    
    
        Sauce sauce = new Sauce("yy", "NewYork");
        System.out.println("Sauce: " +sauce.getBrand() + ", " 
        		+ sauce.getPlace());
        return sauce;
    }

    @Override
    public Cheese createCheese() {
    
    
        Cheese cheese = new Cheese("zz", "NewYork");
        System.out.println("Cheese: " + cheese.getBrand() + ", " 
        		+ cheese.getPlace());
        return cheese;
    }

}

将准备原料的过程加入制作披萨的过程:

/**
 * @author 雫
 * @date 2021/3/3 - 17:41
 * @function 纽约芝士披萨
 */
public class NyCheesePizza extends Pizza {
    
    
    private IngredientFactory ingredientFactory;

    public NyCheesePizza() {
    
    
        super("nyCheesePizza", 50);
        this.ingredientFactory = new NyIngredientFactory();
    }

    @Override
    public void prepare() {
    
    
        ingredientFactory.createDough();
        ingredientFactory.createSauce();
        ingredientFactory.createCheese();
    }

    @Override
    public void bake() {
    
    
        System.out.println("烘烤 " + this.name);
    }

    @Override
    public void cut() {
    
    
        System.out.println("切多片");
    }

    @Override
    public void box() {
    
    
        System.out.println("用XXX厂的盒子装盘");
    }
}

测试:
在这里插入图片描述
上述增加原料工厂用到了组合抽象工厂模式,组合即是将原料工厂当作各种Pizza的成员

2.4 抽象工厂模式

抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类

即采用抽象工厂模式允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的产品具体是什么,这样一来,客户从具体产品中被解耦

在这里插入图片描述

2.5 工厂模式总结

1,工厂模式将对象的创建封装起来,使程序解耦,并降低对象依赖

2,工厂方法模式使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象

3,抽象工厂使用接口,若干个类实现该接口,用实现类中的方法创建对象

4,工厂模式可以让我们针对 抽象编程,而不是针对具体类编程

为了实现某个功能,我们需要去创建对象然后调用方法,但是在一个类中创建大量对象后,这个类就变得脆弱不堪了,稍微有点风吹草动就无法正常工作,为此最好的解决方法就是把功能实现和创建对象分开

使用工厂模式可以降低对象依赖,将功能实现和创建对象分开,使系统更有弹性

猜你喜欢

转载自blog.csdn.net/weixin_43541094/article/details/114305838