工厂模式
先来看一个案例
需求:实现一个咖啡点餐功能。
类图:
主函数方法
public class App {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
Coffee coffee = coffeeStore.orderCoffee("AmericanCoffee");
System.out.println(coffee.getName());
// 输出:
// add mike...
// add sugar...
// AmericanCoffee
}
}
CoffeeStore
public class CoffeeStore {
// 点咖啡
public Coffee orderCoffee(String type) {
Coffee coffee = null;
if ("AmericanCoffee".equals(type)) {
coffee = new AmericanCoffee();
} else if ("LatteCoffee".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起, 本店没有您所点的咖啡");
}
coffee.addMike();
coffee.addSugar();
return coffee;
}
}
Coffee
public abstract class Coffee {
public abstract String getName();
public void addMike() {
System.out.println("add mike...");
}
public void addSugar() {
System.out.println("add sugar...");
}
}
AmericanCoffee
public class AmericanCoffee extends Coffee {
@Override
public String getName() {
return "AmericanCoffee";
}
}
LatteCoffee
public class LatteCoffee extends Coffee {
@Override
public String getName() {
return "LatteCoffee";
}
}
上述案例中存在的问题?
如果此时需要新增一种咖啡品种,那么我们就需要去修改CoffeeStore中的代码,与Coffee实现类严重耦合,违反了设计原则中的开闭原则。
开闭原则: 对扩展打开,对修改关闭。也就是说在有新的需求时,只通过扩展代码来满足,而不是去修改代码。
简单工厂模式
类图:
在原有的代码上新增一个工厂类SimpleCoffeeFactory,将创建咖啡的工作完全交给这个工厂类。
public class SimpleCoffeeFactory {
// 根据咖啡类型创建咖啡
public Coffee createCoffee(String type) {
Coffee coffee = null;
if ("AmericanCoffee".equals(type)) {
coffee = new AmericanCoffee();
} else if ("LatteCoffee".equals(type)) {
coffee = new LatteCoffee();
} else {
throw new RuntimeException("对不起, 本店没有您所点的咖啡");
}
coffee.addMike();
coffee.addSugar();
return coffee;
}
}
修改CoffeeStore
public class CoffeeStore {
// 点咖啡
public Coffee orderCoffee(String type) {
SimpleCoffeeFactory simpleCoffeeFactory = new SimpleCoffeeFactory();
Coffee coffee = simpleCoffeeFactory.createCoffee(type);
return coffee;
}
}
这样做虽然解除了CoffeeStore与Coffee实现类之间的耦合,但是又新增了耦合关系,比如我们在新增咖啡品种时,还是要去修改工厂类中的方法,导致工厂类与Coffe实现类耦合了,这种方式也违反了开闭原则,因此,23中设计模式中不包括简单工厂模式。
这种方式在实际中还是会经常被用到的,它是有一定的好处的,想象一下,当有多个CoffeeStore出现时,或者举个例子,出现了一个DessertStore甜品店,它也需要点咖啡的功能,如果没有工厂,我们还需要在DessertStore中去写一大堆的if else代码去创建咖啡,不如将创建咖啡的工作完全交给一个工厂类,这样一来,不管出现多少个这种店,需要咖啡对象我们直接从工厂从拿就可以了。
工厂模式
正儿八经的工厂模式来了。
类图:
在原有代码基础上,做以下操作:
创建CoffeeFactoty接口
public interface CoffeeFactory {
Coffee createCoffee();
}
创建AmericanCoffeeFactory实现CoffeeFactory
public class AmericanCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
创建LatteCoffeeFactory实现CoffeeFactory
public class LatteCoffeeFactory implements CoffeeFactory {
@Override
public Coffee createCoffee() {
return new LatteCoffee();
}
}
修改CoffeeStore
public class CoffeeStore {
// 点咖啡
public Coffee orderCoffee(CoffeeFactory coffeeFactory) {
Coffee coffee = coffeeFactory.createCoffee();
coffee.addMike();
coffee.addSugar();
return coffee;
}
}
主函数方法
public class App {
public static void main(String[] args) {
CoffeeStore coffeeStore = new CoffeeStore();
Coffee coffee = coffeeStore.orderCoffee(new AmericanCoffeeFactory());
System.out.println(coffee.getName());
}
}
优点:
- 我们只需要知道具体工厂类的名称就可以拿到其返回的对象,而不需要关注具体的创建过程;
- 在有新的咖啡品种出现时,我们只需要创建对应的咖啡品种类和其对应的工厂类即可,满足开闭原则。
缺点:
- 每次有新的咖啡品种出现,我们都需要创建其实体与工厂类,随着需求越来越多,这种方式会极大增加系统的复杂性。
抽象工厂模式
在普通工厂模式中,我们发现它只针对一个产品类型 ,比如上述案例中,咖啡工厂就只能去生产咖啡。
在实际应用中往往有一些别的需求,比如在甜品工厂中需要生产咖啡和甜品,而咖啡又分为美式咖啡和意式咖啡,甜品又分为巧克力蛋糕、奶酪等;在比如,电子产品工厂需要制造手机和电脑,而手机又分为华为手机和苹果手机,电脑也分为华为电脑和苹果电脑,这个时候就出现了抽象工厂模式。
工厂模式是只针对一个产品等级,而抽象产品模式针对多个产品等级。
产品族: 比如华为品牌下有华为手机、华为电脑、手表等,它们称为一个产品族。
产品等级: 多个品牌下的同类产品,比如华为手机、小米手机等等,手机就称为一个产品等级。
下面用上述电子产品的例子来实现案例。
准备工作
public abstract class Computer {
public abstract String getName();
}
public class HuaWeiComputer extends Computer {
@Override
public String getName() {
return "华为电脑";
}
}
public class AppleComputer extends Computer {
@Override
public String getName() {
return "苹果电脑";
}
}
public abstract class Phone {
public abstract String getName();
}
public class ApplePhone extends Phone {
@Override
public String getName() {
return "苹果手机";
}
}
public class HuaWeiPhone extends Phone {
@Override
public String getName() {
return "华为手机";
}
}
创建ElectronicProductFactory,超级工厂
public interface ElectronicProductFactory {
Phone createPhone();
Computer createComputer();
}
HuaWeiBrandFactory
public class HuaWeiBrandFactory implements ElectronicProductFactory {
@Override
public Phone createPhone() {
return new HuaWeiPhone();
}
@Override
public Computer createComputer() {
return new HuaWeiComputer();
}
}
AppleBrandFactory
public class AppleBrandFactory implements ElectronicProductFactory {
@Override
public Phone createPhone() {
return new ApplePhone();
}
@Override
public Computer createComputer() {
return new AppleComputer();
}
}
主函数
public class App {
public static void main(String[] args) {
HuaWeiBrandFactory huaWeiBrandFactory = new HuaWeiBrandFactory();
Phone phone = huaWeiBrandFactory.createPhone();
Computer computer = huaWeiBrandFactory.createComputer();
System.out.println(phone.getName()); // 华为手机
System.out.println(computer.getName()); // 华为电脑
}
}
工厂模式 + 配置文件
这种方式是在工厂类中加载配置文件,读取其中的全类名,再通过反射 的方式来创建其实例,有效的解除了原工厂模式中工厂类和其对应的产品对象的耦合。
AmericanCoffee、Coffee、LatteCoffee这些基础的类不变。
创建properties配置文件
american=com.sunao.factory.ultimate.AmericanCoffee
latte=com.sunao.factory.ultimate.LatteCoffee
创建CoffeeFactory
public class CoffeeFactory {
public static HashMap<String, Coffee> map = new HashMap<>();
// 加载配置文件,只需要加载一次,所以用静态代码块来完成
static {
Properties properties = new Properties();
InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
properties.load(is);
// 获取配置文件中的键名
Set<Object> keys = properties.keySet();
for (Object key : keys) {
// 获取对应key的value:全类名
String className = properties.getProperty((String) key);
// 通过反射来创建该类实例
Coffee coffee = (Coffee) Class.forName(className).newInstance();
map.put((String) key, coffee);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static Coffee createCoffee(String name) {
return map.get(name);
}
}
主函数方法
public class App {
public static void main(String[] args) {
Coffee americanCoffee = CoffeeFactory.createCoffee("american");
System.out.println(americanCoffee.getName()); // AmericanCoffee
Coffee latteCoffee = CoffeeFactory.createCoffee("latte");
System.out.println(latteCoffee.getName()); // LatteCoffee
Coffee americanCoffee02 = CoffeeFactory.createCoffee("american");
System.out.println(americanCoffee == americanCoffee02); // true
}
}
再去对比以下我们上述原来的共产模式,当我们需要新添加caffee品种时,还需要创建一大堆对应的实体和其工厂类吗?现在你只需要:
- 创建新添加品种的实体;
- 在配置文件中添加其全类名;
- 工厂类中的代码完全不需要变动,即可获取其实例。
从上述结果中我们发现,在多次获取同一个对象实例时,获取到的是同一个对象,这是因为通过静态代码块来加载配置文件,该配置文件只被加载了一次,从而实现了单例模式。是不是联想到了Spring中的IOC?