“new”一个实例
在技术上,用new来实例化一个具体类并没有错,但是当我们的代码涉及到“改变”的时候,问题出现了。
当我们实例化一些相关的具体类时,我们通常用“if/else"条件来决定在运行时究竟实例化哪一个类,然而当有变化或扩展时,我们必须打开这部分代码进行修改,这违背了”开-闭“原则,而且造成系统更难维护和更新,更容易犯错。
所以,我们提供了一种新的方式来实例化类。—— 工厂模式
工厂模式有三种形式:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
简单工厂模式
我们用一个购买pizza的例子来看一下这个模式
抽象产品
public interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
具体产品类
public class CheesePizza implements Pizza {
@Override
public void prepare() {
System.out.println("cheesePizza--准备中...");
}
@Override
public void bake() {
System.out.println("cheesePizza--烤制中...");
}
@Override
public void cut() {
System.out.println("cheesePizza--切片");
}
@Override
public void box() {
System.out.println("cheesePizza--装盒");
}
}
public class GreekPizza implements Pizza {
@Override
public void prepare() {
System.out.println("GreekPizza--准备中...");
}
@Override
public void bake() {
System.out.println("GreekPizza--烤制中...");
}
@Override
public void cut() {
System.out.println("GreekPizza--切片");
}
@Override
public void box() {
System.out.println("GreekPizza--装盒");
}
}
public class PepperoniPizza implements Pizza {
@Override
public void prepare() {
System.out.println("PepperoniPizza--准备中...");
}
@Override
public void bake() {
System.out.println("PepperoniPizza--烤制中...");
}
@Override
public void cut() {
System.out.println("PepperoniPizza--切片");
}
@Override
public void box() {
System.out.println("PepperoniPizza--装盒");
}
}
public class VeggiePizza implements Pizza {
@Override
public void prepare() {
System.out.println("VeggiePizza--准备中...");
}
@Override
public void bake() {
System.out.println("VeggiePizza--烤制中...");
}
@Override
public void cut() {
System.out.println("VeggiePizza--切片");
}
@Override
public void box() {
System.out.println("VeggiePizza--装盒");
}
}
简单工厂
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();
} else if (type.equals("pepperoni")){
pizza = new PepperoniPizza();
} else if (type.equals("veggie")){
pizza = new VeggiePizza();
} else {
System.out.println("没有该种类");
}
return pizza;
}
}
客户
public class PizzaStore {
private SimplePizzaFactory simplePizzaFactory;
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.simplePizzaFactory = simplePizzaFactory;
}
public Pizza orderPizza(String type){
Pizza pizza = simplePizzaFactory.createPizza(type);
if(pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
return pizza;
}
}
测试
public class Test {
public static void main(String[] args) {
SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
PizzaStore pizzaStore = new PizzaStore(simplePizzaFactory);
pizzaStore.orderPizza("cheese");
pizzaStore.orderPizza("veggie");
pizzaStore.orderPizza("pineapple");
}
}
我们确实完成了由客户决定生产什么产品,同时生产细节和客户解耦,客户不必关心具体的实现,只需要提出需求。
缺点: 我们可以看出,简单工厂包含了所有的生产逻辑,当我们新增或删减一个产品时,需要更改工厂里的代码, 这里就违背了我们的”开-闭“原则,由此,下面我们介绍一下工厂模式。
另: 其实简单工厂并不是一个设计模式,反而像是一种编程习惯,但是由于我们经常使用的缘故,很多开发人员将其误认为”工厂模式“。
同时,我们可以用一个静态方法定义一个简单工厂,使得不需要用new的方式来实例化工厂对象,但这也造成不能通过继承来改变创建方法的行为。
工厂方法模式
定义: 定义了一个创建对象的接口,子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
分析: 接口定义了创建对象的方法,子类重写方法具体创建对象,定义中的决定不是说允许子类在运行时做决定,而是在我们的创建者类中,选择了哪个子类,自然就决定了实际创建的产品是什么
UML类图
Product:抽象产品接口
ConcreteProduct:具体产品类
Creator:抽象工厂接口,实现了所有操作产品的方法,只有抽象的工厂方法factoryMethod必须由子类实现
ConcreteCreator:具体工厂子类,实现了factoryMethod方法,创造实际的产品
示例
现在,披萨店要扩大规模在全国开加盟店,各个地区的披萨口味都不相同,如果现在有三家加盟店NYPizzaStore,ChicagoPizzaStore,CaliforniaPizzaStore,如何实现?我们现在使用工厂模式来试一下
抽象工厂
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza = createPizza(type);
if(pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
return pizza;
}
//工厂方法,必须由子类实现
protected abstract Pizza createPizza(String type);
}
抽象产品
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
void prepare(){
System.out.println("准备:"+name);
System.out.println("揉面饼...");
System.out.println("添加酱汁...");
System.out.println("添加配料:");
for (int i = 0;i < toppings.size();i++){
System.out.println(" "+toppings.get(i));
}
}
void bake(){
System.out.println("烘烤25分钟");
}
void cut(){
System.out.println("切片");
}
void box(){
System.out.println("装盒");
}
public String getName(){
return name;
}
}
具体工厂
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new NYStyleCheesePizza();
}
return pizza;
}
}
public class ChicagoPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new ChicagoStyleCheesePizza();
}
return pizza;
}
}
具体产品
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "纽约芝士披萨";
dough = "薄面饼";
sauce = "意式红酱";
toppings.add("干奶酪");
}
}
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "芝加哥芝士披萨";
dough = "厚面饼";
sauce = "番茄酱";
toppings.add("白奶酪");
}
}
测试
public class Test {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("订单:"+pizza.getName()+"\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("订单:"+pizza.getName()+"\n");
}
}
结果
简单工厂和工厂方法模式的区别
简单工厂将所有的事情在一个地方做完了,而工厂方法则是创建一个框架,让子类决定要如何实现。
设计原则: 依赖倒置原则:要依赖抽象,不要依赖具体。
这个和”针对接口编程,不针对实现编程“看起来有点像,但这里更强调”抽象“。这个原则说明了不能让高层组件依赖底层组件,且两者都应该依赖于抽象。例如,示例中我们的PizzaStore类是高层,披萨子类是低层,而如果PizzaStore依赖于披萨子类,这就违背了这个原则,我们应该依赖与抽象类而不是具体类。那我们的PizzaStore类中,我们依赖的是Pizza这个抽象类,说明工厂方法模式是符合这个设计原则的。
几个指导方针帮你不违背此原则:
- 变量不可以持有具体类的引用——即不使用new来实例化
- 不要让类派生自具体类——请派生自抽象类或接口
- 不要覆盖基类中已经实现的方法——基类中已实现的方法应用于子类共享而不是用来重写的
想要完全遵守以上指导方针是不可能的,我们写代码时能尽量遵守即可
抽象工厂模式
定义: 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
分析: 允许客户使用抽象的接口来创建一组产品,而不需要知道产出的具体的产品是什么,这样一来,客户就和具体的产品解耦
UML类图
AbstractFactory:抽象工厂,提供一组用于创建产品的接口
ConcreteFactory:实际工厂子类,创建一组产品
AbstractProduct:抽象产品
ConcreteProduct:实际产品子类
示例
现在,要确保每家加盟店使用的原料,我们要在每个地区创建一家原料工厂,这些原料工厂的产品是一样的,区别在于使用不同地域的原料或口味。
抽象工厂
public interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
Cheese createCheese();
Pepperoni createPepperoni();
}
具体工厂
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThickDough();
}
@Override
public Sauce createSauce() {
return new CreamSauce();
}
@Override
public Cheese createCheese() {
return new OrdinaryCheese();
}
@Override
public Pepperoni createPepperoni() {
return new DongbeiPepperoni();
}
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinDough();
}
@Override
public Sauce createSauce() {
return new TomatoSauce();
}
@Override
public Cheese createCheese() {
return new FancyCheese();
}
@Override
public Pepperoni createPepperoni() {
return new ItalianPepperoni();
}
}
抽象产品族接口
public interface Cheese {
}
public interface Dough {
}
public interface Pepperoni {
}
public interface Sauce {
}
具体产品族
public class FancyCheese implements Cheese {
public FancyCheese() {
System.out.println("高级芝士");
}
}
public class OrdinaryCheese implements Cheese {
public OrdinaryCheese() {
System.out.println("普通芝士");
}
}
public class CreamSauce implements Sauce {
public CreamSauce() {
System.out.println("奶油酱汁");
}
}
public class TomatoSauce implements Sauce {
public TomatoSauce() {
System.out.println("番茄酱汁");
}
}
public class ItalianPepperoni implements Pepperoni {
public ItalianPepperoni() {
System.out.println("意大利香肠");
}
}
public class DongbeiPepperoni implements Pepperoni {
public DongbeiPepperoni() {
System.out.println("东北红肠");
}
}
public class ThickDough implements Dough {
public ThickDough() {
System.out.println("厚面饼");
}
}
public class ThinDough implements Dough {
public ThinDough() {
System.out.println("薄面饼");
}
}
客户接口
public abstract class Pizza {
String name;
Dough dough;
Cheese cheese;
Sauce sauce;
Pepperoni pepperoni;
abstract void prepare();
public void bake(){
System.out.println("烘烤25分钟");
}
public void cut(){
System.out.println("切片");
}
public void box(){
System.out.println("装盒");
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
具体客户
public class CheesePizza extends Pizza {
private PizzaIngredientFactory pizzaIngredientFactory;
public CheesePizza(PizzaIngredientFactory pizzaIngredientFactory) {
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
@Override
void prepare() {
System.out.println("准备中...");
dough = pizzaIngredientFactory.createDough();
cheese = pizzaIngredientFactory.createCheese();
sauce = pizzaIngredientFactory.createSauce();
pepperoni = pizzaIngredientFactory.createPepperoni();
}
}
public class VeggiePizza extends Pizza {
private PizzaIngredientFactory pizzaIngredientFactory;
public VeggiePizza(PizzaIngredientFactory pizzaIngredientFactory) {
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
@Override
void prepare() {
System.out.println("准备中...");
dough = pizzaIngredientFactory.createDough();
cheese = pizzaIngredientFactory.createCheese();
sauce = pizzaIngredientFactory.createSauce();
}
}
测试
public class Test {
public static void main(String[] args) {
PizzaStore pizzaStore = new NYPizzaStore();
PizzaStore pizzaStore2 = new ChicagoPizzaStore();
Pizza pizza = pizzaStore.orderPizza("cheese");
System.out.println("---------"+pizza.getName()+"----------");
pizza = pizzaStore.orderPizza("veggie");
System.out.println("---------"+pizza.getName()+"----------");
pizza = pizzaStore2.orderPizza("cheese");
System.out.println("---------"+pizza.getName()+"----------");
pizza = pizzaStore2.orderPizza("veggie");
System.out.println("---------"+pizza.getName()+"----------");
}
}
注意: 抽象工厂的方法实际上是使用工厂方法模式的形式,方法声明为抽象,由子类负责具体实例化对象。这样做的原因是,抽象工厂的任务是定义一个负责创造一组接产品的接口,接口内每个方法负责创建一个产品。
比较工厂方法和抽象工厂
工厂方法:继承工厂方法接口,重写工厂方法。通过子类来创建对象,客户只需要知道所使用的抽象类型,由子类来决定具体类型,也就是客户从具体类型中解耦。
抽象工厂:实现了一个创建一组产品的接口,子类重写方法,要使用工厂,我们使用组合的方式,客户持有一个抽象工厂引用,实际工厂类型由子类决定,所以,和工厂方法一样,达到将客户和具体类型解耦的目的,只不过前者用继承,后者用组合。