有一个问题很困惑:我们在进行一些初始化操作的时候,需要去new对象,但是new的使用不正是针对实现编程吗?违背了针对 接口编程的原则。
针对接口编程可以隔绝掉后来系统的发生的改变,如果代码针对接口编写,我们可以通过多态,组合,使新类实现该接口,但是代码使用大量具体类的时候,一旦加入一个新的具体类,就必须修改原来的代码,也违背了对修改关闭的原则。这时候我们从基本入手“找出会变化的部分,把他们从不变的部分分离出来”。
引用《HeadFirst》中的经典场景。我们现在拥有一个披萨店,我们需要设计一套系统。
一 寻找变化与相同点
1.我们建一个pizza类,类里有pizza制作的过程。并实现pizza商店,里面可以制作pizza(new一个实例对象)。
public class Pizza {
public void prepare() {}
public void bake() {}
public void cut() {}
public void box() {}
}
public class PizzaStore {
Pizza pizza=new Pizza();
public Pizza orderPizza(){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
2.我们会有多种pizza,所有的特色Pizza都继承PIzza类。增加一些代码,根据不同的选择,我们能做出多种多样的pizza。
public class PizzaStore {
Pizza pizza=new Pizza();
public Pizza orderPizza(String type){
if (type.equals("cheese")) {
pizza=new CheesePizza();
} else if(type.equals("greek")){
pizza=new GreekPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
二 定义一个简单工厂
上面的代码当写出来那一刻,就发现了一个问题,如果这样写,每次增加/删除一个新种类就要修改一次商店的代码。“找出会变化的部分,把他们从不变的部分分离出来” ,开始封装。
我们把披萨对象创建的代码单独拉入一个类,姑且就当一个简单的披萨工厂。
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if (type.equals("cheese")) {
pizza=new CheesePizza();
} else if(type.equals("greek")){
pizza=new GreekPizza();
}
return pizza;
}
}
我们的SimplePizzaFactoy类只用来帮客户创建披萨,所有的客户都通过createPizza方法实例化新对象。
我们的PIzzaStore同时也发生了改变。
public class PizzaStore {
SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
//构造器引入一个工厂
this.simplePizzaFactory = simplePizzaFactory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
//通过传入类型来创建特殊披萨
pizza = simplePizzaFactory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
我们把new操作符替换成工厂对象的创建方法,不在使用具体的实例化。
用我们的类图来梳理下。
恩,基本搞定了有个简单工厂。但是,但是,其实简单工厂并不是一个设计模式,而是一种编程习惯。
三 真正的工厂模式
如果我们的披萨店设了分店,一个简单的Factory显然满足不了需求,所以我们就会有很多商店,初步构想是子商店类都继承自 父级商店。
假设我们有家纽约的披萨加工厂,我们会出现新的方式制作披萨。
NYPizzaFactory nyPizzaFactory=new NYPizzaFactory();
PizzaStore nyStore=new PizzaStore(nyPizzaFactory);
nyStore.orderPizza("Veggie");
但是我们在子工厂继承SimpleFactory生产的时候,新的商店的制造披萨的方法(creatPizza)会发生差异,子类工厂需要修改creatPizza(),这样就导致创建PIzza的过程无法控制。这时候我们的工厂类就显得比较多余了。我们需要建造一个框架,将新的披萨商店对象与创建披萨的过程捆绑在一起,但是又保持一定的弹性,做到部分的质量控制。
我们可以把工厂类概括为一个方法。
public abstract class PizzaStore {
SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
// public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
// //构造器引入一个工厂
// this.simplePizzaFactory = simplePizzaFactory;
// }
public Pizza orderPizza(String type) {
Pizza pizza;
//通过传入类型来创建特殊披萨
pizza = simplePizzaFactory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//实例化Pizza的方法从一个类转为一个方法(工厂方法引入)
protected abstract Pizza createPizza();
}
新的工厂方法用来处理对象的创建,并且因为抽象方法的原因,子类的继承需要处理此方法,也就是说工厂方法封装到了子类中。所以程序中父类(PizzaStore)的代码就和子类(NYPizzaStore)的创建对象代码解耦了。
1.我们的PIzzaStore也需要修改了。
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
//通过传入类型来创建特殊披萨
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//子类自定义实现创建细节
protected abstract Pizza createPizza(String type);
}
2.然后创建NY子类商店创建的独特NYPizza
public class NYPizzaStore extends PizzaStore{
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
//独特的NY类型Pizza
if (type.equals("NYcheese")) {
pizza=new NYCheesePizza();
} else if(type.equals("NYgreek")){
pizza=new NYGreekPizza();
}
return pizza;
}
}
3.修改Pizza类。给披萨加上三种属性。名称,面团类型,酱料类型,一套佐料。为了更好展示效果,代码写的丰满一点。
public abstract class Pizza {
// Pizza基本属性
String name;
String dough;// 面团类型
String sauce;// 酱汁类型
ArrayList<String> toppingsArrayList = new ArrayList<String>();
void prepare() {
System.out.println(name + "准备中...");
System.out.println(dough + "加入...");
System.out.println(sauce + "加入...");
for (String toppingName : toppingsArrayList) {
System.out.println(toppingName);
}
System.out.println("佐料加入...");
}
public void bake() {
System.out.println("烘烤中...");
}
public void cut() {
System.out.println("切块中...");
}
public void box() {
System.out.println("打包中...");
}
public String getName() {
return name;
}
}
4.定义独特的NYPizza。继承Pizza类。
public class NYCheesePizza extends Pizza {
public NYCheesePizza() {
name = "NYCheesePizza";
dough = "NYDough";
sauce = "NYSauce";
toppingsArrayList.add("NYTopping");
}
public void cut() {// 可以覆盖父类方法,形成自己独特的风格
System.out.println("NY独特的切割技巧...");
}
}
OK,大功告成,测试代码,上~
工厂模式的使用为了new对象的封装,是为了让子类决定具体怎么创建对象。
代码敲完,暂时抛开该死的教材。画个通用类图重新整理下思路。
我们通过oderProduct方法和工厂抽象方法链接起来,同事也把生产知识(类型匹配)封装到了子类,形成了一个最简单的工厂框架。
搬下官方定义。工厂方法模式定义了一个创建对象的接口,但由子类决定具体的实例化对象。工厂方法让类把实例化推迟到子类。
真正的工厂模式与我们上面的简单工厂的区别在于:
1.简单工厂把全部的事情,在一个地方处理完了,然后工厂方法是创建一个框架,让子类完成创建。
2.工厂方法中,orderPizza()方法提供了一般的框架,并且以抽象的工厂方法创建具体类。可以利用继承,让子类决定创建的实例类型。简单工厂仅仅是把创建对象代码拉出来(封装)。但是因为简单工厂不能变更正在创建的产品,所以不具有弹性,落了下乘。
看下第一部分我们不适用任何工厂的代码,类中代码有具体类的实例化,也就是说依赖某种具体类。代码中应该尽量减少对具体类的依赖,恩,这个在OO设计原则中有一条专门阐述了。依赖倒置原则(Dependency Inversion Principle),要依赖抽象,不要依赖具体类。跟针对接口编程,不针对实现编程也很像。不能让商店依赖产品,不能让创建者依赖创建对象,不能让高层依赖底层,俩者都依赖抽象最好了。
遵循下面几个原则可能会做的更好。(书上说的)
1.变量不可以持有具体类的的引用(new)
2。不要让类派生自具体类(选择接口和抽象类)
3.不要覆盖基类已经实现的方法。(如果覆盖已经实现的方法,那么基类就不是一个真正被适合继承的对象。基类中实现的方法,所有子类应该共享)
PS:表示抽象工厂后来再补吧,暂时我也有点晕。