源码git地址 https://github.com/dlovetco/designMode
前面我们学过简单工厂,工厂方法两种“工厂”有关的设计模式。今天让我们来看看这个抽象工厂是怎么回事。
问题提出
小明和小红是夫妻,他们决定在装修的时候提出两种室内设计。因为谁也不能说服谁,所以这两种设计都要保留,不到最后一刻都说不准采用哪一种方法。此外需要小红和小明都给出一个装修模型。要求用代码实现上述场景,并且做到修改最少的代码就可以切换设计方法。
(设装修房子分为装修卧室,装修厨房两部分)
抽象工厂
package abstractfactory;
public class AbstractFactory {
public static void main(String[] args) {
//先使用小明的方法
Kitchen kitchen = new MingDecorate().decorateKitchen();
Bedroom bedroom = new MingDecorate().decorateBedroom();
System.out.println(kitchen);
System.out.println(bedroom);
//再使用小红的方法
kitchen = new HongDecorate().decorateKitchen();
bedroom = new HongDecorate().decorateBedroom();
System.out.println(kitchen);
System.out.println(bedroom);
}
}
/**
* 装饰方法
*/
interface DecorateFactory {
Bedroom decorateBedroom();
Kitchen decorateKitchen();
}
/**
* 未被装修的厨房
*/
interface Kitchen {
}
class MingKitchen implements Kitchen {
@Override
public String toString() {
return "小明装修出来的厨房";
}
}
class HongKitchen implements Kitchen {
@Override
public String toString() {
return "小红装修出来的厨房";
}
}
interface Bedroom {
}
class MingBedroom implements Bedroom {
@Override
public String toString() {
return "小明装修出来的卧室";
}
}
class HongBedroom implements Bedroom {
@Override
public String toString() {
return "小红装修出来的卧室";
}
}
class MingDecorate implements DecorateFactory {
@Override
public Bedroom decorateBedroom() {
return new MingBedroom();
}
@Override
public Kitchen decorateKitchen() {
return new MingKitchen();
}
}
class HongDecorate implements DecorateFactory {
@Override
public Bedroom decorateBedroom() {
return new HongBedroom();
}
@Override
public Kitchen decorateKitchen() {
return new HongKitchen();
}
}
这其实就是抽象工厂模式了。我们可以看到切换两种装修模式的代价 仅仅是修改工厂的实例类型(即MingDecorate还是HongDecorate)。这是依赖倒转好处的体现。
我们再简单分析一下上述代码。
1. 首先看题目,我们发现虽然有两种不同的装修方式,但都需要装修卧室和厨房。所以我们可以把这两种方法抽象出来作为一个接口(DecorateFactroy),这样的话通过依赖倒转,我们切换模式只需要修改接口的实例。
2. 有了装修方法,我们就可以通过装修方法来产生每种方法自定义的实例。这里比如:MingKitchen,HongKitchen。于是我们根据依赖倒转,也可以抽象出一个接口Kitchen。Washroom跟Kichen,此处不再赘述。
plantuml:
@startuml
interface DecorateFactory{
{abstract}Bedroom decorateBedroom()
{abstract}Kitchen decorateKitchen()
}
DecorateFactory <|.. MingDecorate
class MingDecorate{
Bedroom decorateBedroom()
Kitchen decorateKitchen()
}
DecorateFactory <|.. HongDecorate
class HongDecorate{
Bedroom decorateBedroom()
Kitchen decorateKitchen()
}
Kitchen <.. DecorateFactory
interface Kitchen{
}
Kitchen <|.. MingKitchen
class MingKitchen{
}
Kitchen <|.. HongKitchen
class HongKitchen{
}
Bedroom <.. DecorateFactory
interface Bedroom{
}
Bedroom <|.. MingBedroom
class MingBedroom{
}
Bedroom <|.. HongBedroom
class HongBedroom{
}
@enduml
利用简单工厂来优化抽象工厂
我们看抽象工厂其实是很像工厂方法的。区别在于工厂方法用于制造不同的对象。而抽象工厂用于制造同一个对象,而在制造过程中有不同的做法(即多种不同的实现方法)。
抽象工厂的缺点
- 与客户端的耦合过重,客户端中有许多具体的类。
- 虽然说已经在切换方法上我们所要付出的代价足够小,但是我们还是需要在实例化工厂的时候修改。如果在多个地方实例化了工厂对象,则我们还是要修改多个地方。
下面用简单工厂来优化
package abstractfactory.simplefactory;
public class SimpleFactory {
public static void main(String[] args) {
DecorateFactory decorateFactory = new DecorateFactory();
Kitchen kitchen = decorateFactory.createKitchen();
Bedroom bedroom = decorateFactory.createBedroom();
System.out.println(kitchen);
System.out.println(bedroom);
}
}
/**
* 未被装修的厨房
*/
interface Kitchen {
}
class MingKitchen implements Kitchen {
@Override
public String toString() {
return "小明装修出来的厨房";
}
}
class HongKitchen implements Kitchen {
@Override
public String toString() {
return "小红装修出来的厨房";
}
}
interface Bedroom {
}
class MingBedroom implements Bedroom {
@Override
public String toString() {
return "小明装修出来的卧室";
}
}
class HongBedroom implements Bedroom {
@Override
public String toString() {
return "小红装修出来的卧室";
}
}
class DecorateFactory {
private static String type = "小明";
Kitchen createKitchen() {
switch (type) {
case "小明":
return new MingKitchen();
case "小红":
return new HongKitchen();
default:
return null;
}
}
Bedroom createBedroom() {
switch (type) {
case "小明":
return new MingBedroom();
case "小红":
return new HongBedroom();
default:
return null;
}
}
}
这样的话如果要修改只需要,在DecorateFactory中修改String字段为“小红”。付出的代价比原版抽象工厂要小,且与客户端的耦合性大大减少。因为实例的switch判断移交给了简单工厂。
使用反射再次优化
反射是什么,在这里就不详细解释了。在java中,反射可以通过类名来实例化特定的类。所以我们可以通过传入类名参数,来抛弃掉switch。
我的目录结构如上图所示。
package abstractfactory.reflect;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
public class Reflect {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
DecorateFactory decorateFactory = new DecorateFactory();
Kitchen kitchen = decorateFactory.createKitchen();
Bedroom bedroom = decorateFactory.createBedroom();
System.out.println(kitchen);
System.out.println(bedroom);
}
}
/**
* 未被装修的厨房
*/
interface Kitchen {
}
class MingKitchen implements Kitchen {
@Override
public String toString() {
return "小明装修出来的厨房";
}
}
class HongKitchen implements Kitchen {
@Override
public String toString() {
return "小红装修出来的厨房";
}
}
interface Bedroom {
}
class MingBedroom implements Bedroom {
@Override
public String toString() {
return "小明装修出来的卧室";
}
}
class HongBedroom implements Bedroom {
@Override
public String toString() {
return "小红装修出来的卧室";
}
}
class DecorateFactory {
private static String type = "小明";
private Properties properties = new Properties();
Kitchen createKitchen() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
properties.load(new InputStreamReader(new FileInputStream("./src/abstractfactory/reflect/test.properties"), "utf-8"));
String className = properties.getProperty("kitchenMean");
return (Kitchen) Class.forName("abstractfactory.reflect." + className).newInstance();
}
Bedroom createBedroom() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
properties.load(new InputStreamReader(new FileInputStream("./src/abstractfactory/reflect/test.properties"), "utf-8"));
String className = properties.getProperty("bedRoomMean");
return (Bedroom) Class.forName("abstractfactory.reflect." + className).newInstance();
}
}
test.properties
# 卧室装修方法
bedRoomMean=HongBedroom
# 厨房装修方法
kitchenMean = HongKitchen
上述代码使用反射读取properties里面的参数。这样在切换的时候只要修改properties里面的参数就可以了。代码也不需要重新编译。