Spring 设计模式之工厂模式
- 用到的场景
- 一、简单工厂模式(静态工厂模式)
- Spring框架中的体现
- 举例说明
- Java代码实现
- 二、工厂方法模式
- 举例说明
- Java代码示例
- 三、抽象工厂模式
- 举例说明
- Java代码示例
在软件开发领域,设计模式 是解决常见问题的最佳实践。
Spring 框架作为 Java 生态中的佼佼者,其成功在很大程度上 归功于对设计模式的巧妙运用。
“Spring 中用到了哪些设计模式?”,这个问题,在 面试 中也比较常见,在此进行整理。
用到的场景
要基于 不同条件(大量的if-else或switch-case)创建不同对象的情况时。可以考虑使用工厂模式
一、简单工厂模式(静态工厂模式)
简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类(是类的形式,而不是接口),对实现了同一接口的一些类进行实例的创建。
其核心是 由一个工厂类根据传入的参数,动态决定创建哪一个产品类的实例。
优点
- 责任分割:工厂类包含判断逻辑,决定何时创建哪个产品实例,客户端仅负责“消费”产品。
- 灵活性:根据外界信息决定创建哪个具体对象,客户端无需直接创建对象。
- 低耦合:外界与具体产品类隔离,降低耦合性。
- 结构优化:明确区分职责和权力,利于软件体系结构优化。
缺点
- 扩展困难:增加或删除产品需修改工厂逻辑,系统扩展性受限。
- 违背OCP:修改工厂逻辑可能导致过于复杂,违背开放-封闭原则(对新增开放,对修改关闭)。
- 无法继承:静态工厂方法使得工厂角色无法形成基于继承的等级结构。
Spring框架中的体现
举例说明
您走进了一家名为“万能奶茶店”的饮品店。这家店里只有一位店员(相当于工厂类),他负责制作店里所有种类的奶茶。当您告诉店员您想喝的奶茶类型(比如珍珠奶茶、抹茶拿铁或草莓奶茶)时,店员会根据您的要求为您精心制作一杯(即创建奶茶对象)。
然而,如果这家奶茶店想要推出新口味的奶茶(比如芒果奶茶),店员就需要学习新口味的制作方法,并且还要在菜单上添加这个新选项。这就意味着店员(即工厂类)的职责范围需要扩大,因此其代码也需要相应地进行修改,以支持这种新口味的奶茶。
这种模式下,每当有新产品加入时,都需要对工厂类进行修改,这可能会影响到系统的稳定性和可维护性。
Java代码实现
下面是一个使用简单工厂模式来实现上述场景的Java示例。
知识小贴士:
咖啡对象也可以用类来创建,但是如果是类的话,就必须把咖啡类定义为抽象类(public abstract class MilkTea )是为了强制子类实现必要的方法,防止直接实例化,以及(可选地)提供共享代码。
// 奶茶类,作为所有奶茶的基类
interface MilkTea { // 制作奶茶的方法 public abstract void make();
} // 珍珠奶茶类
class PearlMilkTea implements MilkTea { @Override public void make() { System.out.println("制作珍珠奶茶"); }
} // 抹茶拿铁类
class MatchaLatte implements MilkTea { @Override public void make() { System.out.println("制作抹茶拿铁"); }
} // 草莓奶茶类
class StrawberryMilkTea implements MilkTea { @Override public void make() { System.out.println("制作草莓奶茶"); }
} // 简单工厂类,负责创建所有种类的奶茶
class SimpleFactory { // 根据奶茶类型创建对应的奶茶对象 public static MilkTea createMilkTea(String type) { switch (type) { case "pearl": return new PearlMilkTea(); case "matcha": return new MatchaLatte(); case "strawberry": return new StrawberryMilkTea(); default: throw new IllegalArgumentException("未知的奶茶类型"); } }
} // 客户端测试类
public class SimpleFactoryClient { public static void main(String[] args) { MilkTea tea1 = SimpleFactory.createMilkTea("pearl"); tea1.make(); // 输出:制作珍珠奶茶 MilkTea tea2 = SimpleFactory.createMilkTea("matcha"); tea2.make(); // 输出:制作抹茶拿铁 // 如果添加新口味的奶茶,比如芒果奶茶,需要修改SimpleFactory类 }
}
如果这家奶茶店想要推出新口味的奶茶(比如芒果奶茶),店员就需要学习新口味的制作方法,并且还要在菜单上添加这个新选项。这就意味着店员(即工厂类)的职责范围需要扩大,因此其代码也需要相应地进行修改,以支持这种新口味的奶茶。
// 新增芒果奶茶类
class MangoMilkTea implements MilkTea { @Override public void make() { System.out.println("制作芒果奶茶"); }
} // 简单工厂类,负责创建所有种类的奶茶(已修改以支持芒果奶茶)
class SimpleFactory { // 根据奶茶类型创建对应的奶茶对象(已修改以支持芒果奶茶) public static MilkTea createMilkTea(String type) { switch (type) { case "pearl": return new PearlMilkTea(); case "matcha": return new MatchaLatte(); case "strawberry": return new StrawberryMilkTea(); case "mango": // 新增的芒果奶茶选项 return new MangoMilkTea(); default: throw new IllegalArgumentException("未知的奶茶类型"); } }
} // 客户端测试类(可以添加测试芒果奶茶的代码)
public class SimpleFactoryClient { public static void main(String[] args) { MilkTea tea1 = SimpleFactory.createMilkTea("pearl"); tea1.make(); // 输出:制作珍珠奶茶 MilkTea tea2 = SimpleFactory.createMilkTea("matcha"); tea2.make(); // 输出:制作抹茶拿铁 MilkTea tea3 = SimpleFactory.createMilkTea("strawberry"); tea3.make(); // 输出:制作草莓奶茶 // 测试新口味的芒果奶茶 MilkTea tea4 = SimpleFactory.createMilkTea("mango"); tea4.make(); // 输出:制作芒果奶茶 }
}
如果 类存在@Autowired 的使用时,new 对象的操作就需要换成 ApplicationContext 去获取
// 珍珠奶茶类,现在是一个 Spring 组件
@Component
class PearlMilkTea implements MilkTea { @Autowired private PearMapper pearMapper; @Override public void make() { // 使用 pearMapper 做一些事情(例如查询数据库) pearMapper.select()// ... System.out.println("制作珍珠奶茶"); }
} class SimpleFactory implements ApplicationContextAware {ApplicationContext applicationContext;// 根据奶茶类型创建对应的奶茶对象 public static MilkTea createMilkTea(String type) {switch (type) {case "pearl":return applicationContext.getBean(PearlMilkTea.Class);case "matcha":return new MatchaLatte();case "strawberry":return new StrawberryMilkTea();default:throw new IllegalArgumentException("未知的奶茶类型");}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext=applicationContext;}
}
二、工厂方法模式
工厂方法模式:定义了一个用于创建对象的接口,让子类决定实例化哪一个类。
工厂方法使一个类的实例化延迟到子类。
优点
- 单一职责:每个具体工厂类只负责创建一种产品,代码更加简洁。
- 扩展性强:完全满足开闭原则(OCP:对新增开放,对修改关闭),新增产品类时无需修改已有代码,只需新增具体产品类和对应工厂类。
缺点
- 增加开发量:每增加一个新产品,都需要新增一个具体产品类和一个对应的具体工厂类,增加了额外的开发工作量。
- 维护复杂:当需要修改多个产品类时,可能需要同时修改多个对应的工厂类,维护成本增加。
举例说明
您来到了一个奶茶一条街,这里有多家不同品牌的奶茶店,比如“A品牌奶茶店”和“B品牌奶茶店”, 奶茶店(品牌) 本身就相当于工厂。每家店都有自己的特色奶茶(比如珍珠奶茶、抹茶拿铁和草莓奶茶),并且每家店都有自己的店员(相当于具体工厂类)来负责制作这些奶茶。
您可以根据自己的喜好选择一家奶茶店,然后走进店里告诉店员您想喝的奶茶类型。
店员会根据自己店铺的特色为您制作一杯奶茶。
如果某家店想要推出新口味的奶茶(比如芒果奶茶),他们只需要在自己的店铺里添加这种新口味的奶茶,并培训店员掌握新的制作方法。
这样,其他店铺就不会受到影响,因为每家店都有自己的店员和独特的制作流程。
这种模式下,新增产品时只需要修改对应的具体工厂类,而无需修改工厂接口或现有的其他具体工厂类,从而提高了系统的扩展性和灵活性。
Java代码示例
// 奶茶接口
interface MilkTea { void make();
} // 珍珠奶茶类
class PearlMilkTeaImpl implements MilkTea { @Override public void make() { System.out.println("制作珍珠奶茶"); }
} // 抹茶拿铁类
class MatchaLatteImpl implements MilkTea { @Override public void make() { System.out.println("制作抹茶拿铁"); }
} // 草莓奶茶类
class StrawberryMilkTeaImpl implements MilkTea { @Override public void make() { System.out.println("制作草莓奶茶"); }
} // 工厂接口
interface MilkTeaFactory { MilkTea createMilkTea();
} // A品牌奶茶店工厂类
class BrandAMilkTeaFactory implements MilkTeaFactory { @Override public MilkTea createMilkTea() { return new PearlMilkTeaImpl(); // 假设A品牌只卖珍珠奶茶 }
} // B品牌奶茶店工厂类
class BrandBMilkTeaFactory implements MilkTeaFactory { @Override public MilkTea createMilkTea() { return new MatchaLatteImpl(); // 假设B品牌只卖抹茶拿铁 }
} // 客户端测试类
public class FactoryMethodClient { public static void main(String[] args) { MilkTeaFactory factoryA = new BrandAMilkTeaFactory(); MilkTea teaA = factoryA.createMilkTea(); teaA.make(); // 输出:制作珍珠奶茶 MilkTeaFactory factoryB = new BrandBMilkTeaFactory(); MilkTea teaB = factoryB.createMilkTea(); teaB.make(); // 输出:制作抹茶拿铁 // 如果A品牌想要推出新口味的奶茶,// 比如芒果奶茶,只需要在BrandAMilkTeaFactory中添加新的实现 }
}
如果A品牌奶茶店想要推出新口味的奶茶,比如芒果奶茶
// 奶茶接口
interface MilkTea { void make();
} // 珍珠奶茶类
class PearlMilkTeaImpl implements MilkTea { @Override public void make() { System.out.println("制作珍珠奶茶"); }
} // 抹茶拿铁类
class MatchaLatteImpl implements MilkTea { @Override public void make() { System.out.println("制作抹茶拿铁"); }
} // 草莓奶茶类
class StrawberryMilkTeaImpl implements MilkTea { @Override public void make() { System.out.println("制作草莓奶茶"); }
} // 新增芒果奶茶类
class MangoMilkTeaImpl implements MilkTea { @Override public void make() { System.out.println("制作芒果奶茶"); }
} // 奶茶类型枚举
enum MilkTeaType { PEARL, MATCHA_LATTE, STRAWBERRY, MANGO // 新增芒果奶茶类型
} // 工厂接口(稍作修改以支持类型参数)
interface MilkTeaFactory { MilkTea createMilkTea(MilkTeaType type); // 引入类型参数
} // A品牌奶茶店工厂类(修改以支持多种奶茶)
class BrandAMilkTeaFactory implements MilkTeaFactory { @Override public MilkTea createMilkTea(MilkTeaType type) { switch (type) { case PEARL: return new PearlMilkTeaImpl(); case MANGO: // 新增对芒果奶茶的支持 return new MangoMilkTeaImpl(); default: throw new IllegalArgumentException("Unsupported MilkTeaType: " + type); } }
} // B品牌奶茶店工厂类(保持不变,但也可以按类似方式扩展)
class BrandBMilkTeaFactory implements MilkTeaFactory { @Override public MilkTea createMilkTea(MilkTeaType type) { // 这里为了简单起见,我们只提供一种奶茶,但可以根据需要扩展 if (type == MilkTeaType.MATCHA_LATTE) { return new MatchaLatteImpl(); } else { throw new IllegalArgumentException("Unsupported MilkTeaType: " + type); } }
} // 客户端测试类
public class FactoryMethodClient { public static void main(String[] args) { MilkTeaFactory factoryA = new BrandAMilkTeaFactory(); MilkTea teaA1 = factoryA.createMilkTea(MilkTeaType.PEARL); teaA1.make(); // 输出:制作珍珠奶茶 MilkTea teaA2 = factoryA.createMilkTea(MilkTeaType.MANGO); // 新增对芒果奶茶的创建 teaA2.make(); // 输出:制作芒果奶茶 MilkTeaFactory factoryB = new BrandBMilkTeaFactory(); MilkTea teaB = factoryB.createMilkTea(MilkTeaType.MATCHA_LATTE); teaB.make(); // 输出:制作抹茶拿铁 }
}
三、抽象工厂模式
定义了一个接口用于创建相关或依赖对象的家族,而无需明确指定具体类。
该模式是对工厂方法模式的进一步升级,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
优点:
- 高内聚低耦合:抽象工厂模式将产品的创建与使用分离,降低了客户端代码与产品类之间的耦合度,同时产品族内的产品以高内聚的形式存在,有助于代码的维护和管理。
- 扩展性好:完全满足开闭原则(OCP:对新增开放,对修改关闭),当需要添加新的产品族时,只需要添加一个新的具体工厂类和相应的产品类,而不需要修改原有的客户端代码。
- 灵活性高:通过抽象工厂模式设计,系统可以更加灵活地应对需求的变化,新增组件或平台时,只需新增新的抽象组件和具体平台工厂,无需修改已有代码。
缺点:
- 增加复杂性:抽象工厂模式增加了系统的抽象性和复杂性,可能导致系统难以理解和维护。
- 扩展新产品困难:如果只需要添加新的产品而不是新的产品族,那么抽象工厂模式可能会变得笨拙,因为需要修改所有的工厂接口和工厂实现类。
举例说明
您来到了一个更加高级的奶茶广场,这里不仅有奶茶店,还有卖杯子、吸管等配件的店铺。而且,这些店铺都是连锁经营的,比如“热带风味奶茶店”和“经典风味奶茶店”,它们不仅提供奶茶,还提供与奶茶风格相匹配的杯子和吸管等配件。
您可以选择自己喜欢的奶茶店品牌,然后走进这家店。在这里,您不仅可以喝到他们特色的奶茶,还可以买到与奶茶风格相匹配的杯子和吸管等配件。这些产品都是一系列相互关联的,共同构成了一个产品族。
如果某家店想要推出新口味的奶茶或者新风格的杯子和吸管(比如蓝莓奶茶或陶瓷杯子),他们只需要在自己的品牌内部进行更新和调整。这样,您就可以在不同的品牌之间自由选择,并且每次都能得到一整套风格一致的奶茶及其配件。
这种模式下,新增产品族时只需要在对应的具体工厂类中添加新的产品创建方法,而无需修改抽象工厂接口或现有的其他具体工厂类(除非新增的产品引入了新的产品等级)。这进一步提高了系统的扩展性和灵活性。
Java代码示例
// 奶茶接口
interface MilkTea { void make();
} // 杯子接口
interface Cup { void show();
} // 珍珠奶茶类
class PearlMilkTeaImpl implements MilkTea { @Override public void make() { System.out.println("制作珍珠奶茶"); }
} // 抹茶拿铁类
class MatchaLatteImpl implements MilkTea { @Override public void make() { System.out.println("制作抹茶拿铁"); }
} // 塑料杯类
class PlasticCupImpl implements Cup { @Override public void show() { System.out.println("这是塑料杯"); }
} // 陶瓷杯类
class CeramicCupImpl implements Cup { @Override public void show() { System.out.println("这是陶瓷杯"); }
} // 抽象工厂接口
interface AbstractFactory { MilkTea createMilkTea(); Cup createCup();
} // 热带风味奶茶店工厂类
class TropicalFlavorFactory implements AbstractFactory { @Override public MilkTea createMilkTea() { //制作珍珠奶茶return new PearlMilkTeaImpl(); } @Override public Cup createCup() { //塑料杯return new PlasticCupImpl(); }
} // 经典风味奶茶店工厂类
class ClassicFlavorFactory implements AbstractFactory { @Override public MilkTea createMilkTea() { //制作抹茶拿铁return new MatchaLatteImpl(); } @Override public Cup createCup() { //陶瓷杯return new CeramicCupImpl(); }
} // 客户端测试类
public class AbstractFactoryClient { public static void main(String[] args) { AbstractFactory factory1 = new TropicalFlavorFactory(); MilkTea tea1 = factory1.createMilkTea(); Cup cup1 = factory1.createCup(); tea1.make(); // 输出:制作珍珠奶茶 cup1.show(); // 输出:这是塑料杯 AbstractFactory factory2 = new ClassicFlavorFactory(); MilkTea tea2 = factory2.createMilkTea(); Cup cup2 = factory2.createCup(); tea2.make(); // 输出:制作抹茶拿铁 cup2.show(); // 输出:这是陶瓷杯 // 如果热带风味奶茶店想要推出新口味的奶茶或新风格的杯子,// 只需要在自己的工厂类中添加新的实现 }
}