对于工厂的用法其实并没有可说的,就是当一个抽象类或接口有多个实现类,在应用时需要实例化某个实现类对象时,不是直接new,而是用工厂类获取对象实例。但是根据实际场景的复杂程度也需要选择最优方案,就是简单工厂、工厂方法、抽象工厂。
下面就通过业务场景来这三个模式和他们的关系、区别和演进。
由于篇幅较大,分成两篇,第一篇聊工厂方法和简单工厂,第二篇聊抽象工厂,抽象工厂中在研究一下Mybatis中对工厂模式的应用。
关于下面两条设计原则,我们先做简单阐述,在应用具体案例之后在根据案例详细讲解。
要求:我准备开一个披萨店,有各种风味的披萨。用户订购是根据披萨的名称制作。
类:PizzaStore(披萨店)、Pizza(抽象的披萨)、各种风味的具体的披萨、Client(客户端)
Pizza.class
:披萨抽象类
public abstract class Pizza {
/**pizza名称**/
String name;
/**面团**/
String dough;
/**酱**/
String sauce;
/**配料**/
ArrayList<String> toppings = new ArrayList<>();
void prepare(){
System.out.println("准备"+name);
System.out.println("揉面...");
System.out.println("制作酱料...");
System.out.println("添加配料: ");
for (String topping : toppings) {
System.out.println(topping+" ");
}
}
void bake(){
System.out.println("350℃烘烤25分钟");
}
void cut(){
System.out.println("对角切");
}
void box(){
System.out.println("装盒");
}
public String getName(){
return this.name;
}
}
AStyleCheesePizza.class
:A风味的披萨
public class AStyleCheesePizza extends Pizza {
public AStyleCheesePizza() {
name = "A风味的pizza";
dough = "脆脆的面团";
sauce = "大蒜蘸料";
toppings.add("配料是意大利高级干酪");
}
}
BStyleCheesePizza.class
:B风味的披萨
public class BStyleCheesePizza extends Pizza {
public BStyleCheesePizza() {
name = "B风味的pizza";
dough = "脆脆的面团";
sauce = "大蒜蘸料";
toppings.add("配料是意大利高级干酪");
}
}
CStyleCheesePizza.class
:C风味的披萨
public class CStyleCheesePizza extends Pizza {
public CStyleCheesePizza() {
name = "C风味的pizza";
dough = "脆脆的面团";
sauce = "大蒜蘸料";
toppings.add("配料是意大利高级干酪");
}
}
PizzaStore.class
:
public class PizzaStore {
Pizza pizza = null;
public Pizza orderPizza(String type){
switch (type){
case "A":
pizza = new AStyleCheesePizza();
break;
case "B":
pizza = new BStyleCheesePizza();
break;
case "C":
pizza = new CStyleCheesePizza();
break;
default:
break;
}
pizza = SimplePizzaFctory.getPizza(type);
pizza.prepare();
pizza.bake();
pizza.box();
pizza.cut();
//其他相关代码,比如处理订单等
return pizza;
}
}
Client.class
:
public class Client {
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
Pizza pizza = store.orderPizza("A");
System.out.println();
System.out.println();
System.out.println(pizza.getName()+"制作完成了");
}
}
既然要改造,肯定是有不足之处。上述代码的问题在PizzaStore中,如果现在我们要增加一种口味的披萨,那么就要打开PizzaStore类的orderPizza方法进行修改,PizzaStrore中的业务是非常多的,应该尽量避免修改它。同时我们看到在pizzaStore中创建Pizza实例时是使用的new关键字,这是针对一个具体的实现类,虽然PizzaStrore中使用的是抽象类Pizza,但是在new时确实使用的具体类。问题就是,如果有一种披萨要改名,那仍然要去改PizzaStore。上述的就是代码中会发生变化的部分,除了变化的部分其他的是相对稳定的,这个时候就要把变化的部分抽离出来,也就是说将创建Pizza实例的这部分被代码抽离出来。
各种类型的披萨类(AStyleCheesePizza
,BStyleCheesePizza
,CStyleCheesePizza
)和Pizza
抽象类,包括客户端(Client
)都不需要变。
SimpleFactory.java
:工厂类
public class SimplePizzaFctory {
public static Pizza createPizza(String type){
Pizza pizza = null;
switch (type){
case "A":
pizza = new AStyleCheesePizza();
break;
case "B":
pizza = new BStyleCheesePizza();
break;
case "C":
pizza = new CStyleCheesePizza();
break;
default:
break;
}
return pizza;
}
}
PizzaStore.java
:披萨店类
public class PizzaStore {
Pizza pizza = null;
public Pizza orderPizza(String type){
pizza = SimplePizzaFctory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.box();
pizza.cut();
//其他相关代码,比如处理订单等
return pizza;
}
}
解析:有的同学说了,之前改PizzaStore现在改的话要改SimpleFactory,有什么区别?NoNoNo,这样就实现了,PizzaStore和具体Pizaa类之间的解耦,两者不在紧密相连,谁有问题就该谁,另一方不用动,这不好很多了吗?当然,还不够好,我们继续往下看工厂方法和抽象工厂。
上面使用简单工厂对代码进行更改就是为了遵循开放封闭原则,以及解耦。将可能产生变化的代码从相对稳定的代码中提取出来,使得代码尽量不去改动。但是我们也看到了,SimpleFactory类并没有对修改封闭,这也正是将要写到的,无论模块多么封闭,都会存在一些无法对其封闭的变化,既然不可能完全封闭,那么设计人员就要对其设计的模块应该对那些变化封闭做出选择,你必须看到那些最有可能变化的部分,并采取办法如构造抽象去隔离它。
SimpleFactory.java
:工厂类public class SimplePizzaFctory {
public static Pizza createPizza(Class<?> type) {
Pizza pizza = null;
try {
pizza = (Pizza)type.getConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return pizza;
}
}
PizzaStore.java
:披萨店类
public class PizzaStore {
Pizza pizza = null;
public Pizza orderPizza(Class<?> type) {
pizza = SimplePizzaFctory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.box();
pizza.cut();
//其他相关代码,比如处理订单等
return pizza;
}
}
Client.java
:披萨店类
public class Client {
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
Pizza pizza = store.orderPizza(AStyleCheesePizza.class);
System.out.println();
System.out.println();
System.out.println(pizza.getName()+"制作完成了");
}
}
披萨店经营的不错,要开加盟店了,我希望加盟店中能继续用我目前的软件系统,以保证披萨的流程能一致。同时t由于各地区人们对披萨口为的喜好不同,因此各个加盟店所制作的披萨口味不尽相同。所以我希望能够创建一个框架,能把加盟店和披萨制造绑定到一起,但是又不能像最初的代码那样(制造披萨的代码放到PizzaStore的orderPizza()中),披萨制作和订购紧紧耦合在一起,我想即绑定到一起,又有一定的弹性处理变化。
根据需求我们可以采用工厂方法模式,把PizzaStore编程一个抽象类,将披萨的创建工作放到PizzaStore的的一个方法中,并且将这个方法抽象化,交给子类去实现,来决定制作什么风味的披萨。这样做的好处是稳定的代码都在父类中,子类只需要实现工厂方法,我们仍然把变化限制了在了一个很小的范围中,并且把Pizza的创建个披萨店绑定到了一起。
PizzaStore.java
:抽象披萨工厂NYPizzaStore.java
:纽约披萨工厂ChicagoPizzaStore.java
:芝加哥的披萨工厂Pizza.java
:抽象披萨AStylePizza.java
:A口味的披萨NYStylePizza.java
:纽约口味的披萨ChicagoStylePizza.java
:芝加哥口味的披萨Client.java
:客户端PizzaStore.java
:抽象披萨工厂
public abstract class PizzaStore {
Pizza pizza = null;
public Pizza orderPizza(String type) {
pizza = this.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.box();
pizza.cut();
//其他相关代码,比如处理订单等
return pizza;
}
public abstract Pizza createPizza(String type);
}
NYPizzaStore.java
:纽约披萨工厂
public class NYPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
switch (type){
case "A":
pizza = new AStyleCheesePizza();
break;
case "B":
pizza = new BStyleCheesePizza();
break;
case "C":
pizza = new CStyleCheesePizza();
break;
case "NY":
pizza = new NYStyleCheesePizza();//新增纽约口为的披萨
break;
default:
break;
}
return pizza;
}
}
ChicagoPizzaStore.java
:芝加哥的披萨工厂
public class ChicagoPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
switch (type){
case "A":
pizza = new AStyleCheesePizza();
break;
case "B":
pizza = new BStyleCheesePizza();
break;
case "C":
pizza = new CStyleCheesePizza();
break;
case "Chicago":
pizza = new ChicagoStyleCheesePizza();//新增芝加哥口为的披萨
break;
default:
break;
}
return pizza;
}
}
Pizza.java
:抽象披萨
public abstract class Pizza {
/**pizza名称**/
String name;
/**面团**/
String dough;
/**酱**/
String sauce;
/**配料**/
ArrayList<String> toppings = new ArrayList<>();
void prepare(){
System.out.println("准备"+name);
System.out.println("揉面...");
System.out.println("制作酱料...");
System.out.println("添加配料: ");
for (String topping : toppings) {
System.out.println(topping+" ");
}
}
void bake(){
System.out.println("350℃烘烤25分钟");
}
void cut(){
System.out.println("对角切");
}
void box(){
System.out.println("装盒");
}
public String getName(){
return this.name;
}
}
AStylePizza.java
:A口味的披萨
public class AStyleCheesePizza extends Pizza {
public AStyleCheesePizza() {
name = "A风味的pizza";
dough = "脆脆的面团";
sauce = "大蒜蘸料";
toppings.add("配料是意大利高级干酪");
}
}
NYStylePizza.java
:纽约口味的披萨
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "纽约风味的pizza";
dough = "脆脆的面团";
sauce = "大蒜蘸料";
toppings.add("配料是意大利高级干酪");
}
}
ChicagoStylePizza.java
:芝加哥口味的披萨
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "芝加哥风味的pizza";
dough = "蘸料是小番茄";
sauce = "面团的";
toppings.add("配料是意大利白干酪");
}
@Override
void cut() {
System.out.println("切成方的");
}
}
Client.java
:客户端
public class Client {
public static void main(String[] args) {
PizzaStore store = new NYPizzaStore();
Pizza pizza = store.orderPizza("NY");
System.out.println();
System.out.println();
System.out.println(pizza.getName()+"制作完成了");
}
}
这样我们就实现了披萨制作和订购绑定到一起,又有一定的弹性处理变化。但又新增pizza的需求的时候,只需要打开相应的披萨店(子类披萨店只决定如何生产pizza)修改代码就行了,披萨店父类中的orderPizza()并不知道订单中披萨具体的种类,它只是对一个披萨(pizza父类)做操作,所以即使父类需要用到子类创造的对象,但由于抽象的存在,子类的改变任不会影响到父类。