场景
(本例子来自于Head First 设计模式)
假如我们现在经营着一家披萨店,那么我们需要提供多种不同口味的披萨供用户选择,那么我们可能会这样做:
enum PizzaEnum{
CHEESE("奶酪"),
VEGGIE("蔬菜");
public String getItem() {
return item;
}
PizzaEnum(String item) {
this.item = item;
}
private String item;
static PizzaEnum fromItem(String item){
for (PizzaEnum pizzaEnum : PizzaEnum.values()){
if (pizzaEnum.item.equals(item))
return pizzaEnum;
}
throw new IllegalArgumentException("没有找到对应口味的披萨");
}
}
这个枚举类就代表披萨的不同口味了,那么有了不同口味我们还需要有制作的流程:
class Pizza{
void prepare(){
System.out.println(name()+" 准备制作");
}
void bake(){
System.out.println(name()+" 开始烘烤");
}
private String name(){
return this.getClass().getName();
}
}
这样就可以把自己的披萨卖给用户了,当然用户是可以自己挑选那种口味的披萨,所以还需要让用户来挑:
class PizzaStore{
void orderPizza(PizzaEnum item){
Pizza pizza;
switch (item){
case CHEESE:
pizza = new CheesePizza();
case VEGGIE:
pizza = new VeggiePizza();
default:
pizza = new Pizza();
pizza.prepare();
pizza.bake();
System.out.println("披萨制作完成!");
}
}
这里CheesePizza
和VeggiePizza
就代表制作不同口味的披萨了,那么我们的披萨店整体就大致完成了,可以开始售卖了.
如果某一天我们的厨师有了新想法,想要做一种新口味的披萨要怎么办呢?肯定就是在orderPizza
里面添加新的条件,再添加对应的类;但是这种方式是不是比较麻烦呢?每次有新口味就需要在订披萨的方法里面添加新的条件,这样是不是违反了设计程序的单一职责呢,这个类理应来说只负责做披萨就可以了,具体要做哪种口味的其实我们可以交给其他方法或类来做,这就是工厂方法发挥的时候了,我们定义一个方法:
Pizza createPizza(PizzaEnum item) {
switch (item){
case CHEESE:
return new CheesePizza();
case VEGGIE:
return new VeggiePizza();
default:
return new Pizza();
}
}
可以看出,这个方法的功能就是根据参数返回对应的一个披萨的实例,而orderPizza
就不用操心要哪种口味的,只需利用一个抽象类表达所有口味的披萨即可,而且有新口味的话只需要在createPizza
方法中添加新的条件即可.
口味的问题解决了,那么如果某一天有其他地方的人想加盟我们的披萨店,且他们自己可能由于位置原因有他们口味的披萨,就像中国四川一般喜欢吃辣,云南较为清淡,为了适应不同的地方,我们需要做不同口味的披萨,那要怎么做?
其实我们可以按照前面学习的装饰者模式,设置一个基础类作为抽象类,有基本的功能,不同地方都继承自这个类,有口味需求的话只需要重写相应的方法即可,那么总结起来我们可以把这个场景写成下面这样:
/**
* 抽象披萨店,只具有返回不同披萨的抽象方法和订披萨的基本方法
*/
abstract class PizzaStore{
abstract Pizza createPizza(PizzaEnum item);
void orderPizza(PizzaEnum item){
Pizza pizza = createPizza(item);
pizza.prepare();
pizza.bake();
System.out.println("披萨制作完成!");
}
}
/**
* 纽约开的披萨店,可以看出纽约店有他们自己口味的披萨
*/
class NYPizzaStore extends PizzaStore{
@Override
Pizza createPizza(PizzaEnum item) {
switch (item){
case CHEESE:
return new NYCheesePizza();
case VEGGIE:
return new NYVeggiePizza();
default:
return new Pizza();
}
}
}
/**
* 华盛顿的店也是同样道理
*/
class WSPizzaStore extends PizzaStore{
@Override Pizza createPizza(PizzaEnum item) {
switch (item){
case CHEESE:
return new WSCheesePizza();
case VEGGIE:
return new WSVeggiePizza();
default:
return new Pizza();
}
}
}
/**
* 这就是我们的披萨类
*/
class Pizza{
void prepare(){
System.out.println(name()+" 准备制作");
}
void bake(){
System.out.println(name()+" 开始烘烤");
}
private String name(){
return this.getClass().getName();
}
}
/**
* 作为学习,这些类里面就没有写其他东西了
*/
class NYCheesePizza extends Pizza{
}
class NYVeggiePizza extends Pizza{
}
class WSVeggiePizza extends Pizza{
}
class WSCheesePizza extends Pizza{
}
/**
* 披萨的口味
*/
enum PizzaEnum{
CHEESE("奶酪"),
VEGGIE("蔬菜");
public String getItem() {
return item;
}
PizzaEnum(String item) {
this.item = item;
}
private String item;
static PizzaEnum fromItem(String item){
for (PizzaEnum pizzaEnum : PizzaEnum.values()){
if (pizzaEnum.item.equals(item))
return pizzaEnum;
}
throw new IllegalArgumentException("没有找到对应口味的披萨");
}
}
来做下实验:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("请输入您想去的城市:WS/NY");
String name = in.nextLine();
PizzaStore pizzaStore;
if (name.equals("WS")){
pizzaStore = new WSPizzaStore();
}else {
pizzaStore = new NYPizzaStore();
}
System.out.println("请输入您想吃的披萨类型:cheese/veggie");
String item = in.nextLine();
pizzaStore.orderPizza(PizzaEnum.fromItem(item));
}
结果:
看到结果跟我们所期望的一样,接下来正式介绍一下工厂方法
定义
工厂方法模式(FACTORY METHOD)是一种常用的对象创建型设计模式,此模式的核心精神是封装类中不变的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。它的核心结构有四个角色,分别是抽象工厂;具体工厂;抽象产品;具体产品
定义里面提到了依赖注入,相关的还有一个叫做控制反转(IoC)的东西,都简单介绍下:
- 依赖注入(DI):Dependency Injection,其实与工厂方法定义很像,组件不负责查找资源或其他依赖的对象,这个工作交给IoC容器来做,由容器来返回需要的对象
- 控制反转(Ioc):Inversion of Control,指的就是上面的容器,本来创建对象的工作是组件自己实例化的,现在我们利用IoC容器,将这个事情交给固定的容器来负责,组件将更多精力放在其他的逻辑上面,管理起来更方便,不会时常出现一些忘记初始化的错误了
Spring的核心思想就是依赖注入和控制反转,通过定义Bean将实例化Bean的操作交给配置文件来管理,其他类需要的时候配置文件将bean的实例传给需要的类即可,关于这方面后续会有相应的笔记记录
优缺点
优点
- 我们在创建实例时不用关心具体创建了哪种实例,怎么创建的,只需把参数传入工厂即可
- 很明显,当我们有新的种类添加进来时只需要在具体工厂里面添加相应的条件即可,满足"开闭原则"
缺点
- 有新的种类加入时需要添加新的类,数目会较多