文章目录
- 0、案例:咖啡屋
- 1、简单工厂模式 + 静态工厂(不属于23种之列)
- 2、工厂方法模式
- 3、抽象工厂模式
- 4、简单工厂模式 + 配置文件解除耦合
- 5、JDK源码中对工厂模式的应用
0、案例:咖啡屋
模拟咖啡店点餐。咖啡有多种,抽象类,子类为各种咖啡。咖啡店类聚合咖啡类。类图如下:
定义咖啡抽象类:
public abstract class Coffee {
//获取咖啡种类名称
public abstract String getName();
//加奶
public void addMilk() {
System.out.println("加奶");
}
//加糖
public void addSugar() {
System.out.println("加糖");
}
}
各种咖啡:
public class AmericanCoffee extends Coffee{
@Override
public String getName(){
return "美式";
}
}
public class LatteCoffee extends Coffee{
@Override
public String getName(){
return "拿铁";
}
}
咖啡屋类,聚合咖啡抽象类:
public class CoffeeStore {
public Coffee orderCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("店里没这种咖啡");
}
return coffee;
}
}
以上代码的缺陷是咖啡类和 + 咖啡屋内耦合太高。下面用工厂模式解耦合。
1、简单工厂模式 + 静态工厂(不属于23种之列)
即由一个工厂决定创建哪一种产品类型的实例。 包括:
抽象产品(抽象类)
具体产品(子类)
具体工厂(创建产品并提供方法给调用者)
改进上面的咖啡案例,引入工厂类,让咖啡屋不再自己创建咖啡对象,而是直接从工厂获取,类图:
/**
* 咖啡工厂类
*/
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
//新的咖啡屋类
public class CoffeeStore {
public Coffee orderCoffee(String type) {
SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
Coffee coffee = factory.createCoffee(type);
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
到这儿,有个疑惑,咖啡抽象类或子类变时,SimpleCoffeeFacroty类不还得变?这和直接咖啡屋类有啥区别?不都是改一个类?多此一举?其实不然,如果有一百家咖啡屋,而你没有工厂,那需求变更时你就得改一百次代码,而有了工厂,你只需改工厂一个类就行。本质还是这个工厂类带来了解耦。简单工厂可扩展为静态工厂(即把创建对象的方法改为静态的):
public class SimpleCoffeeFactory {
//静态的
public static Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffe;
}
}
但这种模式下,工厂类还是得修改,并不符合开闭原则。
2、工厂方法模式
- 定义一个接口或者一个抽象的工厂类,让它的实现类(也是一个工厂)来决定创建哪一个实例对象。
- 根据每个工厂不同的方法,来产生不同的所需要的对象
角色有:
抽象工厂:只提供创建产品的接口给外界调用
具体工厂:实现抽象工厂,完成具体产品的创建
- 抽象产品:咖啡类
- 具体产品:美式、拿铁
继续完善案例:
抽象工厂,只提供一个方法:
public interface CoffeeFactory {
Coffee createCoffee(); //生产咖啡对象
}
具体的工厂类,实现抽象工厂:美式咖啡工厂、拿铁咖啡工厂
//美式咖啡工厂,专门用来生产美式咖啡
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
//拿铁咖啡工厂,专门用来生产拿铁咖啡
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
注意现在的咖啡店类:1)、它依赖于抽象,聚合的是抽象工厂对象 2)、创建咖啡店对象,需要set传一个咖啡工厂对象
public class CoffeeStore {
private CoffeeFactory factory;
//通过构造方法来赋值
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
//也可set
public void setFactory(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee(String type) {
Coffee coffee = factory.createCoffee(); //直接调抽象类的方法,到时是哪个子工厂,就能创建出哪种咖啡
//加配料
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
测试类:
public class Client {
public static void main(Stirng[] args) {
//创建咖啡店对象
CoffeeStore store = new CoffeeStore();
//创建具体的咖啡工厂
CoffeeFactory factory = new AmericanCoffeeFactory();
store.setFactory(factory);
//点咖啡
Coffee coffee = store.orderCoffee();
//获取咖啡名称
System.out.println(coffee.getName());
}
}
此时,再有新品种咖啡进来,只需新增代码NewCoffeeFactory去实现CoffeeFactory,以及新增Coffee的子类NewCoffee。测试类中自然就是:
//创建具体的咖啡工厂
CoffeeFactory factory = new NewCoffeeFactory();
store.setFactory(factory);
//....
以上无须对原有的工厂做任何修改,符合开闭原则,并不会修改之前的代码。而缺点则是每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度(类太多,类爆炸)。
3、抽象工厂模式
前面的工厂模式,生产的都是相同系列的对象,如咖啡工厂类只生产各种咖啡,课程工厂只生产Java课程、python课程等。抽象工厂模式则是提供创建一系列相关或相互依赖对象的接口。比如华为除了手机,还有笔记本。骆驼除了外套还有鞋子、裤子。换句话:工厂模式处理的是同一级别的产品制造,而抽象工厂模式则是用来处理同一产品族对象的生产的。
角色有:
- 抽象工厂:提供创建
多个
产品的接口给外界调用 - 具体工厂:实现抽象工厂的多个抽象方法,完成具体产品的创建
- 抽象产品:咖啡类、甜品类
- 具体产品:美式、拿铁,甜品1、甜品2
继续完善咖啡案例,现在咖啡店除了咖啡外,还要售卖甜品,包括提拉米苏甜品、抹茶慕斯甜品。如果用上面的工厂模式,则需要定义甜品抽象类、两个甜品类(实现类)、两个甜品的工厂类。考虑用抽象工厂,以产品族的视角看,拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。类图:
先定义抽象工厂类,可对外返回咖啡、甜品这一个产品族的对象:
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
不同的公司或品牌,实现抽象工厂(具体工厂):
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee(); //美式甜点工厂生产美式
}
public Dessert createDessert() {
return new MatchaMousse(); //美式甜点工厂生产
}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
产品实体类的定义同上,跳过。测试:
public class Client {
public static void main(Stirng[] args) {
//创建工厂对象
DessertFactory store = new AmericanDessertFactory();
//生产咖啡
Coffee coffee = store.createCoffee();
//生产甜品
Dessert dessert = store.createDessert();
}
}
以后再增加一个公司或者品牌,只需新增一个具体工厂类与对应的产品类。抽象工厂的优缺点:
- 优点:引入产品族的概念后,不容易类爆炸。且客户端可获取到同一个产品族的对象。不会得到西装公司的西裤 + 运动风公司的运动鞋对象这种奇葩组合。
- 缺点:产品族中新加一种产品时,就得修改所有的工厂类。比如现在除了咖啡、甜品外,还有新产品汉堡,就得改所有的抽象工厂和具体工厂
当需要创建的对象是一系列相互关联或相互依赖的产品族时,考虑抽象工厂模式,如电器工厂中的电视机、洗衣机、空调等。
4、简单工厂模式 + 配置文件解除耦合
前面提到简单工厂模式下,工厂对象和产品对象耦合度太高。这里用配置文件垫一下来解除耦合。类路径下创建文件bean.properties:
american=com.domain.bean.AmericanCoffee
latte=com.plat.domain.bean.LatteCoffee
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次(注意下面createCoffee方法获取map中的对象时,获取到的是同一个对象,属于单例,不想单例就别把反射创建对象放静态代码块里)。
public class CoffeeFactory {
//定义Map充当容器
private static Map<String,Coffee> map = new HashMap();
//加载properties文件 ⇒ 拿到类名 ⇒ 反射创建对象 ⇒ 存map
static {
Properties p = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
p.load(is);
//遍历Properties集合对象
Set<Object> keys = p.keySet();
for (Object key : keys) {
//根据键获取值(全类名)
String className = p.getProperty((String) key);
//获取字节码对象
Class clazz = Class.forName(className);
Coffee obj = (Coffee) clazz.newInstance();
map.put((String)key,obj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//对外的静态方法
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
测试:
public class Client {
public static void main(Stirng[] args) {
Coffee coffee = CoffeeFactory.createCoffee("american");
System.out.println(coffee);
}
}
如此,也符合开闭原则,改配置文件即可。
5、JDK源码中对工厂模式的应用
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("123");
//获取迭代器对象
Iterator<String> it = list.iterator();
//使用迭代器遍历
while(it.hasNext()) {
String ele = it.next();
System.out.println(ele);
}
}
}
以上,获取迭代器对象,用到了工厂模式。
Collection接口为抽象工厂:
ArrayList为创建具体产品(迭代器)的具体工厂:
Iterator接口为抽象产品类,定义了该产品对象(迭代器对象)有的方法。Iter内部类则是具体的产品。