工厂模式
学习工厂设计模式,必须知道的相关概念
-
产品:
类 -
抽象产品:
抽象类、接口 -
产品蔟
多个有内在关系,或者是有逻辑关系的产品
比如: 8mm螺丝与8mm螺母, 如果不是有关系的,根本就匹配不上 -
产品等级
没有使用工厂模式:
//抽象产品
interface Food {
void eat();
}
//具体产品
class Hamburger implements Food {
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
//=======上面为服务端(提供服务,作者),下面为客户端(使用服务)================
public class AppTest {
public static void main(String[] args) {
//创建对象
Food f = new Hamburger();
//调用方法
f.eat();
}
}
-
这种设计相当脆弱! 为什么呢?
-
因为 只要作者修改了具体产品的类名, 那么客户端代码,也要随之一起改变.
这样服务端代码,和客户端代码就是耦合的!
例如: 把作者把Hamburger
改成Hburger
(分割线上下是两个不同主体) -
我们希望的效果是,无论服务器端代码如何修改,客户端代码都应该不知道,不用修改客户端的代码!
工厂模式 – 简单工厂
//抽象产品
interface Food {
void eat();
}
//具体产品
class Hamburger implements Food {
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
class RiceNoodle implements Food {
@Override
public void eat() {
System.out.println("过桥米线");
}
}
//简单工厂
class FoodFactory {
public static Food getFood(int n) {
Food food = null;
switch (n) {
case 1:
food = new Hamburger();
break;
case 2:
food = new RiceNoodle();
break;
}
return food;
}
}
//=======上面为服务端(提供服务,作者),下面为客户端(使用服务)================
public class AppTest {
public static void main(String[] args) {
Food food = FoodFactory.getFood(1);
food.eat();
}
}
-
这样看似已经解决了作者修改名字时,自己本身写的 FoodFactory 里面的case里面的名字也要修改.
-
客户端(用户)只需要知道第1个是汉堡包,第2个是过桥米线就行
-
简单工厂
- 优点:
- 把具体产品的类型,从客户端代码中,解耦出来
- 服务端,如果修改了具体产品的类名,客户端也知道!
- 这便符合了"面向接口编程"的思想
- 缺点:
- 客户端不得不死记硬背那些常量于具体产品的映射关系,
比如: 1对于汉堡包, 2对应米线 - 如果具体产品特别多, 则简单工厂,就会变得十分臃肿,
比如有100个具体产品,则需要在简单工厂的switch中写出100个case! - 最重要的是, 变化来了,客户端需要扩展具体产品的时候,势必要修改简单工厂中的代码,这样便违反了开闭原则
但是有时我们并没有服务端的具体代码,而是字节文件,就算有也不能改!!!
- 客户端不得不死记硬背那些常量于具体产品的映射关系,
- 优点:
工厂模式 – 工厂方法
- 针对于简单工厂的问题
- 修改代码如下, 使用工厂方法设计模式.
//抽象产品
interface Food {
void eat();
}
//具体产品
class Hamburger implements Food {
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
class RiceNoodle implements Food {
@Override
public void eat() {
System.out.println("过桥米线");
}
}
interface FoodFactory {
public Food getFood();
}
class HamburgerFactory implements FoodFactory {
@Override
public Food getFood() {
return new Hamburger();
}
}
class RiceNoodleFactory implements FoodFactory {
@Override
public Food getFood() {
return new RiceNoodle();
}
}
class Business {
public static void taste(FoodFactory ff) {
Food food = ff.getFood();
System.out.println("评委1,品尝");
food.eat();
Food food2 = ff.getFood();
System.out.println("评委2,品尝");
food2.eat();
Food food3 = ff.getFood();
System.out.println("评委3,品尝");
food3.eat();
}
}
//=======上面为服务端(提供服务,作者),下面为客户端(使用服务)================
public class AppTest {
public static void main(String[] args) {
FoodFactory ff = new HamburgerFactory();
Business.taste(ff);
}
}
- 工厂方法:
-
优点:
- 仍然具有简单工厂的优点,服务器修改了具体的类名以后,客户端不知道
- 当客户端需要扩展一个新的产品时,不需要修改作者原来的代码,只是扩展一个新的工厂而已!
-
缺点:
- 如果有多个产品等级,那么工厂类的数量,就会爆炸式增长!
-
class Lp implements Food {
@Override
public void eat() {
System.out.println("从小就吃凉皮");
}
}
class LpFactory implements FoodFactory {
@Override
public Food getFood() {
return new Lp();
}
}
杠点
-
杠点1:
- 我们已经知道简单工厂也好,工厂方法也好, 都有一个优点,就是服务器端的具体产品类名变化以后,客户端不知道!
- 但是,反观我们现在的代码,客户端依然依赖于具体的工厂的类名呀! 此时,如果服务器端修改了具体工厂的类名
- 那么客户端也要随之一起修改!
- 感觉折腾了一圈,又回到了原点
-
解释1:
- 工厂的名字,是被视为接口的.作者有责任,有义务,保证工厂的名字是稳定的
- 也就说,虽然客户端依赖于工厂的具体类名
- 可是在IT业内,所有工厂的名字都是趋向于稳定(并不是100%不会变). 至少工厂类的名字,要比具体产品类的名字更加稳定!
-
杠点2:
- 既然产品是我们客户端扩展出来的,那为什么不直接实例化呢?
- 毕竟这个扩展出来的Lp这个产品,我们自己就是作者.
- 我们想怎么改类名自己都能把控! 为什么还要为自己制作的产品做工厂呢?
-
解释2:
- 因为,作者在开发功能时,不仅仅只会开发一些抽象产品,具体产品、
- 对应的工厂、还会搭配一些提前做好的框架
-
杠点3:
- 现在制作出LpFactory,是为了能把LpFactory传入到Business.taste方法,所有必须定义这个LpFactory
- 那为什么不从一开始 就让Business.taste方法就直接接受Food参数呢?而不是现在FoodFactory作为参数
-
解释3:
- 那会又会回最初的问题: 只要作者修改了具体产品的类名, 那么客户端代码,也要随之一起改变
工厂模式 – 抽象工厂
- 针对于工厂方法的问题,当有多个产品等级时(食物,饮料,甜品,…) 工厂类就会很多
- 修改代码如下, 使用抽象工厂设计模式.
- 抽象工厂就是从原本一个接口一个方法,变成一个接口多个方法
//抽象产品
interface Food {
void eat();
}
//具体产品
class Hamburger implements Food {
@Override
public void eat() {
System.out.println("吃汉堡包!");
}
}
class RiceNoodle implements Food {
@Override
public void eat() {
System.out.println("过桥米线");
}
}
interface Drink {
public void drink();
}
class Cola implements Drink {
@Override
public void drink() {
System.out.println("可口可乐, 你值得拥有!");
}
}
class IcePeak implements Drink {
@Override
public void drink() {
System.out.println("从小就喝冰峰");
}
}
interface Factory {
public Food getFood();
public Drink getDrink();
}
class KFCFactory implements Factory {
@Override
public Food getFood() {
return new Hamburger();
}
@Override
public Drink getDrink() {
return new Cola();
}
}
class Business {
public static void taste(Factory ff) {
Food food = ff.getFood();
Drink drink = ff.getDrink();
System.out.println("评委1,品尝");
food.eat();
drink.drink();
Food food2 = ff.getFood();
Drink drink2 = ff.getDrink();
System.out.println("评委2,品尝");
food2.eat();
drink2.drink();
Food food3 = ff.getFood();
Drink drink3 = ff.getDrink();
System.out.println("评委3,品尝");
food3.eat();
drink3.drink();
}
}
//=======上面为服务端(提供服务,作者),下面为客户端(使用服务)================
//扩展
class Lp implements Food {
@Override
public void eat() {
System.out.println("宝鸡擀面皮");
}
}
class Fenta implements Drink {
@Override
public void drink() {
System.out.println("芬达,你值得拥有!");
}
}
class BaoJiFactory implements Factory {
@Override
public Food getFood() {
return new Lp();
}
@Override
public Drink getDrink() {
return new Fenta();
}
}
//运行
public class AppTest {
public static void main(String[] args) {
Business.taste(new KFCFactory());
}
}
- 抽象工厂
-
优点:
- 仍然有简单工厂和工厂方法的优点
- 更重要的是,抽象工厂类的数量减少了, 无论有多少个产品等级,工厂就一套
-
杠点:
- 为什么三秦工厂中,就必须米线搭配冰峰呢?为什么就不能是米线搭配可乐?
-
解释:
- 抽象工厂中,可以生产多个产品,这多个产品之间,必须有内在联系.
- 同一个工厂中的产品都属于同一个产品蔟!不能把不同产品蔟的产品混合到一个抽象工厂的实现类中
-
缺点:
- 当产品等级发生变化时,(增加产品等级,删除产品等级),都要引起所有以前工厂代码的修改.
这就违反了"开闭原则"
- 当产品等级发生变化时,(增加产品等级,删除产品等级),都要引起所有以前工厂代码的修改.
-
结论:
- 当工厂不需要扩展,且数量不多时,可以使用简单工厂.,如果需要扩展,则不建议使用简单工厂
- 当需要扩展,并且数量不多时,可以使用工厂方法,当数量比较多时,不建议使用工厂方法
- 当产品等级比较固定,可以考虑使用抽象工厂, 如果产品等级经常变化,则不建议使用抽象工厂