工厂模式解决的不是"怎么 new 一个对象"这么简单。它真正解决的是:对象创建逻辑变化时,业务代码要不要跟着改。
如果对象类型很少,直接 new 没什么问题;一旦产品类型变多、创建过程变复杂、调用方到处写 if-else,代码就会开始腐烂。工厂模式就是把"创建什么对象"的决策从业务流程里拆出去。
为什么需要工厂模式
先看一个咖啡下单场景。咖啡店根据类型创建不同咖啡:
java
public Coffee orderCoffee(String type) {
Coffee coffee;
if ("latte".equals(type)) {
coffee = new LatteCoffee();
} else if ("american".equals(type)) {
coffee = new AmericanCoffee();
} else {
throw new IllegalArgumentException("unsupported coffee type");
}
coffee.addMilk();
coffee.addSugar();
return coffee;
}
这段代码的问题不在于能不能跑,而在于它把两件事揉在了一起:
- 根据类型创建咖啡。
- 对咖啡做统一加工。
当你新增摩卡咖啡时,orderCoffee 必须修改。新增越多,这个方法越像一张对象创建清单。
#mermaid-svg-JRyuJ12aB57GXxxs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JRyuJ12aB57GXxxs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JRyuJ12aB57GXxxs .error-icon{fill:#552222;}#mermaid-svg-JRyuJ12aB57GXxxs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JRyuJ12aB57GXxxs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JRyuJ12aB57GXxxs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JRyuJ12aB57GXxxs .marker.cross{stroke:#333333;}#mermaid-svg-JRyuJ12aB57GXxxs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JRyuJ12aB57GXxxs p{margin:0;}#mermaid-svg-JRyuJ12aB57GXxxs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JRyuJ12aB57GXxxs .cluster-label text{fill:#333;}#mermaid-svg-JRyuJ12aB57GXxxs .cluster-label span{color:#333;}#mermaid-svg-JRyuJ12aB57GXxxs .cluster-label span p{background-color:transparent;}#mermaid-svg-JRyuJ12aB57GXxxs .label text,#mermaid-svg-JRyuJ12aB57GXxxs span{fill:#333;color:#333;}#mermaid-svg-JRyuJ12aB57GXxxs .node rect,#mermaid-svg-JRyuJ12aB57GXxxs .node circle,#mermaid-svg-JRyuJ12aB57GXxxs .node ellipse,#mermaid-svg-JRyuJ12aB57GXxxs .node polygon,#mermaid-svg-JRyuJ12aB57GXxxs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JRyuJ12aB57GXxxs .rough-node .label text,#mermaid-svg-JRyuJ12aB57GXxxs .node .label text,#mermaid-svg-JRyuJ12aB57GXxxs .image-shape .label,#mermaid-svg-JRyuJ12aB57GXxxs .icon-shape .label{text-anchor:middle;}#mermaid-svg-JRyuJ12aB57GXxxs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JRyuJ12aB57GXxxs .rough-node .label,#mermaid-svg-JRyuJ12aB57GXxxs .node .label,#mermaid-svg-JRyuJ12aB57GXxxs .image-shape .label,#mermaid-svg-JRyuJ12aB57GXxxs .icon-shape .label{text-align:center;}#mermaid-svg-JRyuJ12aB57GXxxs .node.clickable{cursor:pointer;}#mermaid-svg-JRyuJ12aB57GXxxs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JRyuJ12aB57GXxxs .arrowheadPath{fill:#333333;}#mermaid-svg-JRyuJ12aB57GXxxs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JRyuJ12aB57GXxxs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JRyuJ12aB57GXxxs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JRyuJ12aB57GXxxs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JRyuJ12aB57GXxxs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JRyuJ12aB57GXxxs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JRyuJ12aB57GXxxs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JRyuJ12aB57GXxxs .cluster text{fill:#333;}#mermaid-svg-JRyuJ12aB57GXxxs .cluster span{color:#333;}#mermaid-svg-JRyuJ12aB57GXxxs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JRyuJ12aB57GXxxs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JRyuJ12aB57GXxxs rect.text{fill:none;stroke-width:0;}#mermaid-svg-JRyuJ12aB57GXxxs .icon-shape,#mermaid-svg-JRyuJ12aB57GXxxs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JRyuJ12aB57GXxxs .icon-shape p,#mermaid-svg-JRyuJ12aB57GXxxs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JRyuJ12aB57GXxxs .icon-shape .label rect,#mermaid-svg-JRyuJ12aB57GXxxs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JRyuJ12aB57GXxxs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JRyuJ12aB57GXxxs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JRyuJ12aB57GXxxs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} latte
american
mocha
CoffeeStore.orderCoffee
type 是什么
new LatteCoffee
new AmericanCoffee
new MochaCoffee
加奶和加糖
工厂模式的方向是:让咖啡店只关心点单流程,把创建咖啡的细节交给工厂。
简单工厂
简单工厂不是 GoF 设计模式里的标准模式,更像一种常用编码习惯。它把对象创建逻辑集中到一个工厂类里。
java
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
if ("latte".equals(type)) {
return new LatteCoffee();
}
if ("american".equals(type)) {
return new AmericanCoffee();
}
throw new IllegalArgumentException("unsupported coffee type");
}
}
咖啡店变成这样:
java
public class CoffeeStore {
private final SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
public Coffee orderCoffee(String type) {
Coffee coffee = factory.createCoffee(type);
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
结构图如下:
#mermaid-svg-yhLvuq0g2s5y4G7j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yhLvuq0g2s5y4G7j .error-icon{fill:#552222;}#mermaid-svg-yhLvuq0g2s5y4G7j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yhLvuq0g2s5y4G7j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yhLvuq0g2s5y4G7j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yhLvuq0g2s5y4G7j .marker.cross{stroke:#333333;}#mermaid-svg-yhLvuq0g2s5y4G7j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yhLvuq0g2s5y4G7j p{margin:0;}#mermaid-svg-yhLvuq0g2s5y4G7j .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j .cluster-label text{fill:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j .cluster-label span{color:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j .cluster-label span p{background-color:transparent;}#mermaid-svg-yhLvuq0g2s5y4G7j .label text,#mermaid-svg-yhLvuq0g2s5y4G7j span{fill:#333;color:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j .node rect,#mermaid-svg-yhLvuq0g2s5y4G7j .node circle,#mermaid-svg-yhLvuq0g2s5y4G7j .node ellipse,#mermaid-svg-yhLvuq0g2s5y4G7j .node polygon,#mermaid-svg-yhLvuq0g2s5y4G7j .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yhLvuq0g2s5y4G7j .rough-node .label text,#mermaid-svg-yhLvuq0g2s5y4G7j .node .label text,#mermaid-svg-yhLvuq0g2s5y4G7j .image-shape .label,#mermaid-svg-yhLvuq0g2s5y4G7j .icon-shape .label{text-anchor:middle;}#mermaid-svg-yhLvuq0g2s5y4G7j .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yhLvuq0g2s5y4G7j .rough-node .label,#mermaid-svg-yhLvuq0g2s5y4G7j .node .label,#mermaid-svg-yhLvuq0g2s5y4G7j .image-shape .label,#mermaid-svg-yhLvuq0g2s5y4G7j .icon-shape .label{text-align:center;}#mermaid-svg-yhLvuq0g2s5y4G7j .node.clickable{cursor:pointer;}#mermaid-svg-yhLvuq0g2s5y4G7j .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yhLvuq0g2s5y4G7j .arrowheadPath{fill:#333333;}#mermaid-svg-yhLvuq0g2s5y4G7j .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yhLvuq0g2s5y4G7j .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yhLvuq0g2s5y4G7j .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yhLvuq0g2s5y4G7j .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yhLvuq0g2s5y4G7j .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yhLvuq0g2s5y4G7j .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yhLvuq0g2s5y4G7j .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yhLvuq0g2s5y4G7j .cluster text{fill:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j .cluster span{color:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yhLvuq0g2s5y4G7j .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yhLvuq0g2s5y4G7j rect.text{fill:none;stroke-width:0;}#mermaid-svg-yhLvuq0g2s5y4G7j .icon-shape,#mermaid-svg-yhLvuq0g2s5y4G7j .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yhLvuq0g2s5y4G7j .icon-shape p,#mermaid-svg-yhLvuq0g2s5y4G7j .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yhLvuq0g2s5y4G7j .icon-shape .label rect,#mermaid-svg-yhLvuq0g2s5y4G7j .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yhLvuq0g2s5y4G7j .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yhLvuq0g2s5y4G7j .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yhLvuq0g2s5y4G7j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CoffeeStore
SimpleCoffeeFactory
LatteCoffee
AmericanCoffee
Coffee
简单工厂的好处是调用方简单,创建逻辑集中。缺点也明显:新增产品时仍然要改工厂类。
| 优点 | 缺点 |
|---|---|
| 调用方不直接关心具体类 | 新增产品要修改工厂 |
| 创建逻辑集中管理 | 工厂类可能越来越大 |
| 适合产品类型少的场景 | 不完全符合开闭原则 |
如果只有两三个产品,并且变化不频繁,简单工厂很实用。不要为了"设计模式"硬上更复杂的结构。
工厂方法
工厂方法进一步拆分工厂:每个产品有自己的工厂,调用方通过抽象工厂创建产品。
java
public interface CoffeeFactory {
Coffee createCoffee();
}
java
public class LatteCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
java
public class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
咖啡店依赖抽象工厂:
java
public class CoffeeStore {
private final CoffeeFactory coffeeFactory;
public CoffeeStore(CoffeeFactory coffeeFactory) {
this.coffeeFactory = coffeeFactory;
}
public Coffee orderCoffee() {
Coffee coffee = coffeeFactory.createCoffee();
coffee.addMilk();
coffee.addSugar();
return coffee;
}
}
#mermaid-svg-eG0RwPmzqnWzOYb7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eG0RwPmzqnWzOYb7 .error-icon{fill:#552222;}#mermaid-svg-eG0RwPmzqnWzOYb7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eG0RwPmzqnWzOYb7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .marker.cross{stroke:#333333;}#mermaid-svg-eG0RwPmzqnWzOYb7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eG0RwPmzqnWzOYb7 p{margin:0;}#mermaid-svg-eG0RwPmzqnWzOYb7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .cluster-label text{fill:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .cluster-label span{color:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .cluster-label span p{background-color:transparent;}#mermaid-svg-eG0RwPmzqnWzOYb7 .label text,#mermaid-svg-eG0RwPmzqnWzOYb7 span{fill:#333;color:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .node rect,#mermaid-svg-eG0RwPmzqnWzOYb7 .node circle,#mermaid-svg-eG0RwPmzqnWzOYb7 .node ellipse,#mermaid-svg-eG0RwPmzqnWzOYb7 .node polygon,#mermaid-svg-eG0RwPmzqnWzOYb7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .rough-node .label text,#mermaid-svg-eG0RwPmzqnWzOYb7 .node .label text,#mermaid-svg-eG0RwPmzqnWzOYb7 .image-shape .label,#mermaid-svg-eG0RwPmzqnWzOYb7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-eG0RwPmzqnWzOYb7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .rough-node .label,#mermaid-svg-eG0RwPmzqnWzOYb7 .node .label,#mermaid-svg-eG0RwPmzqnWzOYb7 .image-shape .label,#mermaid-svg-eG0RwPmzqnWzOYb7 .icon-shape .label{text-align:center;}#mermaid-svg-eG0RwPmzqnWzOYb7 .node.clickable{cursor:pointer;}#mermaid-svg-eG0RwPmzqnWzOYb7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .arrowheadPath{fill:#333333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eG0RwPmzqnWzOYb7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eG0RwPmzqnWzOYb7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eG0RwPmzqnWzOYb7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eG0RwPmzqnWzOYb7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .cluster text{fill:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 .cluster span{color:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eG0RwPmzqnWzOYb7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eG0RwPmzqnWzOYb7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-eG0RwPmzqnWzOYb7 .icon-shape,#mermaid-svg-eG0RwPmzqnWzOYb7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eG0RwPmzqnWzOYb7 .icon-shape p,#mermaid-svg-eG0RwPmzqnWzOYb7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eG0RwPmzqnWzOYb7 .icon-shape .label rect,#mermaid-svg-eG0RwPmzqnWzOYb7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eG0RwPmzqnWzOYb7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eG0RwPmzqnWzOYb7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eG0RwPmzqnWzOYb7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CoffeeStore
CoffeeFactory
LatteCoffeeFactory
AmericanCoffeeFactory
LatteCoffee
AmericanCoffee
Coffee
新增摩卡咖啡时,不需要修改已有工厂,只要新增:
MochaCoffeeMochaCoffeeFactory
#mermaid-svg-DhzULBzdfehovgpA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DhzULBzdfehovgpA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DhzULBzdfehovgpA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DhzULBzdfehovgpA .error-icon{fill:#552222;}#mermaid-svg-DhzULBzdfehovgpA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DhzULBzdfehovgpA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DhzULBzdfehovgpA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DhzULBzdfehovgpA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DhzULBzdfehovgpA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DhzULBzdfehovgpA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DhzULBzdfehovgpA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DhzULBzdfehovgpA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DhzULBzdfehovgpA .marker.cross{stroke:#333333;}#mermaid-svg-DhzULBzdfehovgpA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DhzULBzdfehovgpA p{margin:0;}#mermaid-svg-DhzULBzdfehovgpA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DhzULBzdfehovgpA .cluster-label text{fill:#333;}#mermaid-svg-DhzULBzdfehovgpA .cluster-label span{color:#333;}#mermaid-svg-DhzULBzdfehovgpA .cluster-label span p{background-color:transparent;}#mermaid-svg-DhzULBzdfehovgpA .label text,#mermaid-svg-DhzULBzdfehovgpA span{fill:#333;color:#333;}#mermaid-svg-DhzULBzdfehovgpA .node rect,#mermaid-svg-DhzULBzdfehovgpA .node circle,#mermaid-svg-DhzULBzdfehovgpA .node ellipse,#mermaid-svg-DhzULBzdfehovgpA .node polygon,#mermaid-svg-DhzULBzdfehovgpA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DhzULBzdfehovgpA .rough-node .label text,#mermaid-svg-DhzULBzdfehovgpA .node .label text,#mermaid-svg-DhzULBzdfehovgpA .image-shape .label,#mermaid-svg-DhzULBzdfehovgpA .icon-shape .label{text-anchor:middle;}#mermaid-svg-DhzULBzdfehovgpA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DhzULBzdfehovgpA .rough-node .label,#mermaid-svg-DhzULBzdfehovgpA .node .label,#mermaid-svg-DhzULBzdfehovgpA .image-shape .label,#mermaid-svg-DhzULBzdfehovgpA .icon-shape .label{text-align:center;}#mermaid-svg-DhzULBzdfehovgpA .node.clickable{cursor:pointer;}#mermaid-svg-DhzULBzdfehovgpA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DhzULBzdfehovgpA .arrowheadPath{fill:#333333;}#mermaid-svg-DhzULBzdfehovgpA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DhzULBzdfehovgpA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DhzULBzdfehovgpA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DhzULBzdfehovgpA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DhzULBzdfehovgpA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DhzULBzdfehovgpA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DhzULBzdfehovgpA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DhzULBzdfehovgpA .cluster text{fill:#333;}#mermaid-svg-DhzULBzdfehovgpA .cluster span{color:#333;}#mermaid-svg-DhzULBzdfehovgpA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DhzULBzdfehovgpA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DhzULBzdfehovgpA rect.text{fill:none;stroke-width:0;}#mermaid-svg-DhzULBzdfehovgpA .icon-shape,#mermaid-svg-DhzULBzdfehovgpA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DhzULBzdfehovgpA .icon-shape p,#mermaid-svg-DhzULBzdfehovgpA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DhzULBzdfehovgpA .icon-shape .label rect,#mermaid-svg-DhzULBzdfehovgpA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DhzULBzdfehovgpA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DhzULBzdfehovgpA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DhzULBzdfehovgpA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新增产品
新增 MochaCoffee
新增 MochaCoffeeFactory
不修改原有产品
不修改原有工厂
工厂方法的核心价值是扩展性。它更符合开闭原则:对扩展开放,对修改关闭。
但它也有代价:每新增一个产品,就要新增一个产品类和一个工厂类。如果产品很多,类数量会明显变多。
抽象工厂
工厂方法解决的是"同一类产品"的创建,比如只生产咖啡。
抽象工厂解决的是"一个产品族"的创建。产品族可以理解为一组应该一起使用的产品。
比如咖啡店不仅卖咖啡,还卖甜点:
| 风味产品族 | 咖啡 | 甜点 |
|---|---|---|
| 意大利风味 | 拿铁咖啡 | 提拉米苏 |
| 美式风味 | 美式咖啡 | 抹茶慕斯 |
抽象工厂可以这样设计:
java
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
java
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
#mermaid-svg-HM8nr22YUyDEx9JJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HM8nr22YUyDEx9JJ .error-icon{fill:#552222;}#mermaid-svg-HM8nr22YUyDEx9JJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HM8nr22YUyDEx9JJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HM8nr22YUyDEx9JJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HM8nr22YUyDEx9JJ .marker.cross{stroke:#333333;}#mermaid-svg-HM8nr22YUyDEx9JJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HM8nr22YUyDEx9JJ p{margin:0;}#mermaid-svg-HM8nr22YUyDEx9JJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ .cluster-label text{fill:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ .cluster-label span{color:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ .cluster-label span p{background-color:transparent;}#mermaid-svg-HM8nr22YUyDEx9JJ .label text,#mermaid-svg-HM8nr22YUyDEx9JJ span{fill:#333;color:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ .node rect,#mermaid-svg-HM8nr22YUyDEx9JJ .node circle,#mermaid-svg-HM8nr22YUyDEx9JJ .node ellipse,#mermaid-svg-HM8nr22YUyDEx9JJ .node polygon,#mermaid-svg-HM8nr22YUyDEx9JJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HM8nr22YUyDEx9JJ .rough-node .label text,#mermaid-svg-HM8nr22YUyDEx9JJ .node .label text,#mermaid-svg-HM8nr22YUyDEx9JJ .image-shape .label,#mermaid-svg-HM8nr22YUyDEx9JJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-HM8nr22YUyDEx9JJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HM8nr22YUyDEx9JJ .rough-node .label,#mermaid-svg-HM8nr22YUyDEx9JJ .node .label,#mermaid-svg-HM8nr22YUyDEx9JJ .image-shape .label,#mermaid-svg-HM8nr22YUyDEx9JJ .icon-shape .label{text-align:center;}#mermaid-svg-HM8nr22YUyDEx9JJ .node.clickable{cursor:pointer;}#mermaid-svg-HM8nr22YUyDEx9JJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HM8nr22YUyDEx9JJ .arrowheadPath{fill:#333333;}#mermaid-svg-HM8nr22YUyDEx9JJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HM8nr22YUyDEx9JJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HM8nr22YUyDEx9JJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HM8nr22YUyDEx9JJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HM8nr22YUyDEx9JJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HM8nr22YUyDEx9JJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HM8nr22YUyDEx9JJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HM8nr22YUyDEx9JJ .cluster text{fill:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ .cluster span{color:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HM8nr22YUyDEx9JJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HM8nr22YUyDEx9JJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-HM8nr22YUyDEx9JJ .icon-shape,#mermaid-svg-HM8nr22YUyDEx9JJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HM8nr22YUyDEx9JJ .icon-shape p,#mermaid-svg-HM8nr22YUyDEx9JJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HM8nr22YUyDEx9JJ .icon-shape .label rect,#mermaid-svg-HM8nr22YUyDEx9JJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HM8nr22YUyDEx9JJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HM8nr22YUyDEx9JJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HM8nr22YUyDEx9JJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} DessertFactory
ItalyDessertFactory
AmericanDessertFactory
LatteCoffee
Tiramisu
AmericanCoffee
MatchaMousse
抽象工厂的关键是保证产品族一致。你选择了意大利风味工厂,就会得到拿铁咖啡和提拉米苏,而不是拿铁咖啡配一个美式甜点。
#mermaid-svg-ddevK7tvW3MjNylD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ddevK7tvW3MjNylD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ddevK7tvW3MjNylD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ddevK7tvW3MjNylD .error-icon{fill:#552222;}#mermaid-svg-ddevK7tvW3MjNylD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ddevK7tvW3MjNylD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ddevK7tvW3MjNylD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ddevK7tvW3MjNylD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ddevK7tvW3MjNylD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ddevK7tvW3MjNylD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ddevK7tvW3MjNylD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ddevK7tvW3MjNylD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ddevK7tvW3MjNylD .marker.cross{stroke:#333333;}#mermaid-svg-ddevK7tvW3MjNylD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ddevK7tvW3MjNylD p{margin:0;}#mermaid-svg-ddevK7tvW3MjNylD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ddevK7tvW3MjNylD .cluster-label text{fill:#333;}#mermaid-svg-ddevK7tvW3MjNylD .cluster-label span{color:#333;}#mermaid-svg-ddevK7tvW3MjNylD .cluster-label span p{background-color:transparent;}#mermaid-svg-ddevK7tvW3MjNylD .label text,#mermaid-svg-ddevK7tvW3MjNylD span{fill:#333;color:#333;}#mermaid-svg-ddevK7tvW3MjNylD .node rect,#mermaid-svg-ddevK7tvW3MjNylD .node circle,#mermaid-svg-ddevK7tvW3MjNylD .node ellipse,#mermaid-svg-ddevK7tvW3MjNylD .node polygon,#mermaid-svg-ddevK7tvW3MjNylD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ddevK7tvW3MjNylD .rough-node .label text,#mermaid-svg-ddevK7tvW3MjNylD .node .label text,#mermaid-svg-ddevK7tvW3MjNylD .image-shape .label,#mermaid-svg-ddevK7tvW3MjNylD .icon-shape .label{text-anchor:middle;}#mermaid-svg-ddevK7tvW3MjNylD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ddevK7tvW3MjNylD .rough-node .label,#mermaid-svg-ddevK7tvW3MjNylD .node .label,#mermaid-svg-ddevK7tvW3MjNylD .image-shape .label,#mermaid-svg-ddevK7tvW3MjNylD .icon-shape .label{text-align:center;}#mermaid-svg-ddevK7tvW3MjNylD .node.clickable{cursor:pointer;}#mermaid-svg-ddevK7tvW3MjNylD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ddevK7tvW3MjNylD .arrowheadPath{fill:#333333;}#mermaid-svg-ddevK7tvW3MjNylD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ddevK7tvW3MjNylD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ddevK7tvW3MjNylD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ddevK7tvW3MjNylD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ddevK7tvW3MjNylD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ddevK7tvW3MjNylD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ddevK7tvW3MjNylD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ddevK7tvW3MjNylD .cluster text{fill:#333;}#mermaid-svg-ddevK7tvW3MjNylD .cluster span{color:#333;}#mermaid-svg-ddevK7tvW3MjNylD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ddevK7tvW3MjNylD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ddevK7tvW3MjNylD rect.text{fill:none;stroke-width:0;}#mermaid-svg-ddevK7tvW3MjNylD .icon-shape,#mermaid-svg-ddevK7tvW3MjNylD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ddevK7tvW3MjNylD .icon-shape p,#mermaid-svg-ddevK7tvW3MjNylD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ddevK7tvW3MjNylD .icon-shape .label rect,#mermaid-svg-ddevK7tvW3MjNylD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ddevK7tvW3MjNylD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ddevK7tvW3MjNylD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ddevK7tvW3MjNylD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 选择一个产品族工厂
创建同一风格咖啡
创建同一风格甜点
客户端拿到一组能配合使用的对象
三种工厂怎么选
| 模式 | 适合场景 | 主要问题 |
|---|---|---|
| 简单工厂 | 产品少,变化少 | 新增产品要改工厂 |
| 工厂方法 | 单一产品等级,经常新增产品 | 类数量增加 |
| 抽象工厂 | 多个产品等级组成产品族 | 新增产品等级很麻烦 |
这里的"产品等级"和"产品族"要分清:
| 概念 | 例子 |
|---|---|
| 产品等级 | 咖啡、甜点、手机、电脑 |
| 产品族 | 意大利风味、美式风味、华为、小米 |
如果只是新增一个咖啡类型,工厂方法更合适。
如果要保证"同一个品牌或风味的一组产品一起创建",抽象工厂更合适。
项目里怎么回答
面试问"你项目里用过工厂模式吗",不要只说"用过"。可以结合业务说:
我们在多类型对象创建场景里用过工厂模式。比如不同登录方式、不同支付渠道、不同消息发送渠道,它们都有统一接口,但具体实现不一样。如果直接在业务方法里写大量
if-else,新增一种类型就要改核心流程。后来把对象创建放到工厂里,业务层只根据类型拿到对应处理器,再执行统一方法。简单工厂适合类型少的场景;如果类型经常扩展,会进一步结合 Spring 容器,把每个实现注册成 Bean,再由工厂按类型获取。
小结
工厂模式的重点不是写一个类叫 Factory,而是把对象创建从业务流程里拆出来。
可以这样记:
简单工厂集中创建对象,工厂方法把每个产品的创建交给独立工厂,抽象工厂创建一整组能配合使用的产品族。
真正写代码时,别为了模式而模式。对象类型少、变化少,用简单工厂就够;扩展频繁,再升级成工厂方法或结合 Spring 管理。