工厂模式
工厂模式
工厂模式中的相关概念
- 产品:类
- 抽象产品:抽象类,接口
- 产品簇:多个有内在联系,或者是有逻辑关系的产品。比如6mm的螺丝和6mm的螺母在一个产品簇中,6mm螺丝和8mm的螺母就不是一个产品簇。
- 产品等级:
简单工厂模式
案例
// 作者
interface Food{
void eat();
}
class HotDog implements Food{
@Override
public void eat() {
System.out.println("吃热狗");
}
}
class Apple implements Food{
@Override
public void eat() {
System.out.println("吃苹果");
}
}
// 客户
public class NegativeDesign {
public static void main(String[] args) {
Food hotDog=new HotDog();
hotDog.eat();
Food apple=new Apple();
apple.eat();
}
}
上述代码中,当实现类修改的时候,比如修改了类名称,客户是不知道的,一旦作者修改了类名称,那么客户的项目将会崩塌。
简单工厂
// 作者
// 抽象产品
interface SFood {
void eat();
}
// 具体产品
class SHotDog implements Food {
@Override
public void eat() {
System.out.println("吃热狗");
}
}
class SApple 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 SHotDog();
break;
case 2:
food = new SApple();
break;
}
return food;
}
}
public class SimpleFactory {
public static void main(String[] args) {
Food hotDog = FoodFactory.getFood(1);
hotDog.eat();
}
}
简单工厂的优点:
- 把具体产品的类型,从客户端代码中,解耦出来。
- 服务器端,如果修改了具体的类名,客户端不需要知道。
- 更加符合了“面向接口编程“的思想。
缺点:
- 客户端不得不记住那些与具体常量映射的常量,比如:1对应热狗,2对应苹果。
- 如果具体产品特别多,则简单工厂会变得十分臃肿。比如有100个具体产品,则需要在简单工厂中写100个case。
- 最重要的是,当需求或者变化来的时候,客户端需要拓展具体产品的时候,势必要修改简单工厂中的代码,这样就违反了“开闭原则”。
简单工厂模式的UML
工厂方法模式
// 抽象产品
interface Food {
void eat();
}
// 产品
class HotDog implements Food {
@Override
public void eat() {
System.out.println("吃热狗");
}
}
class Apple implements Food {
@Override
public void eat() {
System.out.println("吃苹果");
}
}
// 新增产品
class iceCream implements Food {
@Override
public void eat() {
System.out.println("吃冰淇淋");
}
}
interface FoodFactory {
public Food getFood();
}
class HotDogFactory implements FoodFactory {
@Override
public Food getFood() {
return new HotDog();
}
}
class AppleFactory implements FoodFactory {
@Override
public Food getFood() {
return new Apple();
}
}
class IceCreamFactory implements FoodFactory {
@Override
public Food getFood() {
return new iceCream();
}
}
// 和事先准备好的框架配套使用
class Business{
public static void taste(FoodFactory ff){
Food f = ff.getFood();
System.out.println("评委1,品尝");
f.eat();
Food f2 = ff.getFood();
System.out.println("评委2,品尝");
f2.eat();
Food f3 = ff.getFood();
System.out.println("评委3,品尝");
f3.eat();
}
}
/*
// 如果修改成这样的,那么调用该方法的时候,传入的就是食物类,就又会出现服务端的类名称修改了而导致客户端出错
class Business {
public static void taste(Food food) {
System.out.println("评委1,品尝");
food.eat();
System.out.println("评委2,品尝");
food.eat();
System.out.println("评委3,品尝");
food.eat();
}
}*/
public class FactoryMethodPattern {
public static void main(String[] args) {
// 这是当新增一个产品的时候,就不需要修改以前的代码,只需要再建立一个该产品的工厂就可以了
FoodFactory foodFactory = new IceCreamFactory();
Food food = foodFactory.getFood();
food.eat();
}
}
优点
-
仍然具有简单工厂的优点,服务端修改了具体产品的类名以后,客户端是不知道的。
-
当客户端需要拓展一个新的产品时,不需要修改作者原来的代码,只是拓展一个新的工厂而已。
抬杠
-
我们已经知道,简单工厂也好,工厂方法也好,都有一个优点,,就是服务器端的具体产品的类名变化以后,客户端是不需要知道的。
但是,反观我们现在的代码,客户端仍然需要依赖于具体的工厂的类名呀。此时,如果服务器端修改了具体工厂的类名,那么客户端耶要随之一起修改。
解释:
工厂的名字,是视为接口的,作者有责任,有义务,保证工厂的名字是稳定的。耶就是说,虽然客户端依赖于工厂的具体类名,可是在生产和开发中,所有工厂的名字都是趋于稳定的,至少工厂类的名字,要比具体产品雷的名字更加稳定。
-
既然产品是我们客户端拓展出来的,那为什么不直接自己实例化呢?毕竟这个拓展出来的IceCream这个产品,我们自己就是作者,我们想怎么改类名就改类名,为什么还要自己制作工厂呢?
解释:
因为作者在开发功能的时候,不仅仅只会发开一些抽象产品,具体产品,对应的工厂,还会配套的搭配一些提前做好的框架。
-
现在制作出IceCreamFactory,是为了把IceCreamFactory传入Business.taste方法,所有必须定义这个IceCreamFactory。那为什么不从一开始就让Business.taste方法直接接受Food参数呢?而不是现在的FoodFactory作为参数。
解释:
如果修改成Food作为参数,那么调用该方法的时候,传入的就是食物类,就又会出现服务端的类名称修改了而导致客户端出错。但用工厂的话,工厂一般很少进行修改。
工厂方法模式的UML
缺点
但出现多个产品簇的时候,使用工厂方法模式会产生类爆炸。
抽象工厂
// 抽象的工厂,什么都可以生产
interface Factory {
public Food getFood();
public Drink getDrink();
}
class KFCFactory implements Factory {
@Override
public Food getFood() {
return new HotDog();
}
@Override
public Drink getDrink() {
return new Colo();
}
}
class FruitFactory implements Factory {
@Override
public Food getFood() {
return new Apple();
}
@Override
public Drink getDrink() {
return new XueBi();
}
}
class IceCreamFactory implements Factory {
@Override
public Food getFood() {
return new iceCream();
}
@Override
public Drink getDrink() {
return new Colo();
}
}
// 和事先准备好的框架配套使用
class Business {
public static void taste(Factory ff) {
Food f = ff.getFood();
Drink drink = ff.getDrink();
System.out.println("评委1,品尝");
drink.drink();
f.eat();
Food f2 = ff.getFood();
Drink drink2 = ff.getDrink();
System.out.println("评委2,品尝");
drink2.drink();
f2.eat();
Food f3 = ff.getFood();
Drink drink3 = ff.getDrink();
System.out.println("评委1,品尝");
drink3.drink();
f3.eat();
}
}
public class AbstractFactory {
public static void main(String[] args) {
Factory kfcFactory = new KFCFactory();
Business.taste(kfcFactory);
}
}
抽象工厂优点
- 仍然具有简单工厂和工厂方法的优点
- 抽象工厂把工厂类的数量减少了,无论有多少个产品等级,工厂就一套。
抬杠
为什么在KFC工厂中,就必须是可乐+热狗呢?为什么不能是可乐+苹果呢?
解释:
在抽象工厂中,可以生产多个产品,这多个产品之间,必须要有内置联系。
同一个工厂中的产品属于同一个产品簇!!不能把不同产品簇中的产品混合到一个抽象工厂中的实现类去。
缺点
当产品等级发生变化时(增加或删除产品等级),都要引起所有工厂等级的代码的修改,这就违反了开闭原则。
结论
- 如果产品中的产品等级固定时,可以考虑抽象工厂。
- 如果产品簇经常变化时,则不建议使用抽象工厂。